XSLT 1.0: Passing _all_ parameters in 'pass-through' template (ie tunneling) - xslt

Please forgive the poor nomenclature in this, probably why I'm struggling to google an answer.
Basically I am asking if parameter tunneling from xslt 2.0 has any analogue in 1.0, or if there is a clever way to get 'some way there'. I'm using msxsl
Suppose I have a template like so:
<xsl:template name="outer">
<xsl:apply-templates>
<xsl:with-param name="x" select="y"/>
</xsl:apply-templates>
</xsl:template>
If the apply-templates call gets picked up directly by a template that knows about the parameter then all is good, but suppose we have some general templates to ignore certain elements and process their children:
<xsl:template match="tag_to_ignore">
<xsl:apply-templates/>
</xsl:template>
if these are 'hit' OR when the tag_to_ignore is not explicitly matched and XSLT does its default apply templates to children behaviour
the parameters are 'lost'
is there anyway to tell a template to 'accept' all parameters it was passed and pass them down?
(ie the real code there are many, many, params that could come in and potentially need to be passed out, trying to avoid a maintenance issue of having to accept every possible para and explicitly pass it on)

In XSLT 2.0 you can use tunnel parameters but in XSLT 1.0 there is no such feature.

Actually, I had a similar issue before I heard about tunnelling parameters and solved it by adding the same parameters to the one-off template, then passing it back out in that apply-templates. For instance, in your case
<xsl:template match="tag_to_ignore">
<xsl:param name="x"/>
<xsl:apply-templates>
<xsl:with-param name="x" select="$x" />
</xsl:apply-templates>
</xsl:template>
I THINK this will work in XSLT 1.0, but I'm fairly new to all this myself.

Related

Passing a node as parameter to a XSL stylesheet

I need to pass a node as a parameter to an XSL stylesheet. The issue is that the parameter gets sent as a string. I have seen the several SO questions regarding this topic, and I know that the solution (in XSLT 1.0) is to use an external node-set() function to transform the string to a node set.
My issue is that I am using eXist DB I cannot seem to be able to get its XSLT processor to locate any such function. I have tried the EXSLT node-set() from the namespace http://exslt.org/common as well as both the Saxon and Xalan version (I think eXist used to use Xalan but now it might be Saxon).
Are these extensions even allowed in the XSLT processor used by eXist? If not, is there something else I can do?
To reference or transform documents from the database, you should pass the path as a parameter to the transformation, and then refer to it using a parameter and variable
(: xquery :)
let $path-to-document := "/db/test/testa.xml"
let $stylesheet :=
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="source" required="no"/>
<xsl:variable name="error"><error>doc not available</error></xsl:variable>
<xsl:variable name="theDoc" select="if (doc-available($source)) then doc($source) else $error"/>
<xsl:template match="/">
<result><xsl:value-of select="$source"/> - <xsl:value-of select="node-name($theDoc/*)"/></result>
</xsl:template>
</xsl:stylesheet>
return transform:transform(<dummy/>,$stylesheet, <parameters><param name="source" value="xmldb:exist://{$path-to-document}"/></parameters>)
As per Martin Honnen's comments I don't think it is possible to pass an XML node via the <parameters> structure of the transform:transform() function in eXist. The function seems to strip away any XML tags passed to it as a value.
As a workaround I will wrap both my input XML and my parameter XML into a root element and pass that as input to the transform function.

Scope (root node, context) of XSLT "key" Element

I have an XSLT key defined. I need to access the key from within a for-each loop, where that loop is processing a node-set that is outside the scope of where the key was defined.
Snippet, where I've marked two lines, one which works and one which does not:
<xsl:value-of select="key('name', 'use')"/> <!-- works -->
<xsl:for-each select="$outOfScopeNodeSet">
<xsl:value-of select="key('name', 'use')"/> <!-- does not work -->
</xsl:for-each>
Is there a way to access the key from within the for-each loop?
XSLT 1.0, msxsl engine.
(I could not think of a reasonable way to provide a full working example for this. I'm also not sure of the correct terminology, such as "scope" - perhaps if I knew the correct terminology I'd be able to find my answer already. If the question is not clear enough please let me know and I'll try to edit it into better shape.)
In XSLT 1.0, keys do not work across documents. It seems that your $outOfScopeNodeSet contains a node-set whose root node is different from the root node of the XML document being processed (probably created by the exsl:node-set() function?) - while the key is supposed to fetch a value from the processed XML document.
To resolve this problem, you need to return the context back to the processed XML document before calling the key() function, for example:
<xsl:variable name="root" select="/" />
<xsl:for-each select="$outOfScopeNodeSet">
<xsl:variable name="use" select="some-value" />
<xsl:for-each select="$root">
<xsl:value-of select="key('name', $use)"/>
</xsl:for-each>
</xsl:for-each>

Value-of select in <a href=> (XSLT)

I try to concstruct link with
<xsl:element name="a">
<xsl:attribute name="href">
<xsl:value-of select="concat('file:///', substring-before('%RolesPath%', 'roles'),'Flores.chm')"/>
</xsl:attribute>
Help
</xsl:element>
but I get error:
File file:///Flores.chm not found
I'm pretty sure, that variable %RolesPath% works fine. I'm using it in code normally. And if I use in code only
<xsl:value-of select="concat('file:///', substring-before('%RolesPath%', 'roles'),'Flores.chm')"/>
I get
file:///C:\Flores\Flores.chm
which is right path. Where I'm doing mistake please?
edit. %RolesPath% stores path to specify folder of program, which works with this code. In my case %RolesPath% stores "C:\Flores\roles\".
To specify my problem. I need open file(Flores.chm) in root folder of program. Program can be install everywhere in PC and prapably only way, how I can get the path is via %RolesPath%.
What you are passing to substring-before() is just a string ('%RolesPath%'). It appears that you are trying to use a Windows environment variable. This isn't going to work the way you're using it.
I think you have 2 options:
Option 1
Pass the value of the environment variable as an xsl:param when you call the stylesheet. This would work in either XSLT 1.0 or 2.0.
You would need the xsl:param:
<xsl:param name="RolesPath"/>
and this is how you would reference it:
<a href="{concat('file:///', substring-before($RolesPath, 'roles'),'Flores.chm')}"/>
Option 2
Use the environment-variable() function. This would only work with an XSLT 3.0 processor, such as Saxon-PE or EE.
Example:
<a href="{concat('file:///', substring-before(environment-variable('RolesPath'), 'roles'),'Flores.chm')}"/>
Here's another example of environment-variable() to show the function actually working:
XSLT 3.0
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<environment-variable name="TEMP" value="{environment-variable('TEMP')}"/>
</xsl:template>
</xsl:stylesheet>
Output (when applied to any well-formed XML)
<environment-variable name="TEMP" value="C:\Users\dhaley\AppData\Local\Temp"/>
Use this shorter expression:
<a href="file:///{substring-before($RolesPath, 'roles')}Flores.chm"/>
where $RolesPath is passed as an external, global parameter to the transformation.
How exactly to pass an external parameter to the transformation varies from one XSLT processor to another -- read your XSLT processor documentation. Some XSLT processors also allow string-typed parameters to be passed to the transformation from a command-line execution utility.

How to get template name inside template

Is it possible to get template name while inside template?
Example:
<xsl:template name="list">
<!-- Get name value (in this case "list") -->
</xsl:template>
As far as I am aware, neither XSLT 1.0 nor XSLT 2.0 offer such a feature.
As Martin says, there's probably no way to do this.
Even if such a capacity existed, surely using it couldn't be any simpler than just defining a variable with the value you want:
<xsl:template name="list">
<xsl:variable name="class" select="'list'" />
and then using that variable where you need it. This also has the benefit that you can change the template name without rewriting all your CSS, or change the class name without rewriting your XSLT.
One of the benefits of the fact that XSLT uses XML syntax is that it's easy to transform stylesheets. Modifying every named template to contain a variable holding the template name is dead easy.
#JLRishe's answer is a good approach. However, there is a trap. Any <xsl:param> must be declared before any <xsl:variable>.
This will not work:
<xsl:template name="foobarsnafu">
<xsl:variable name="foo" value="bar">
<xsl:param name="snafu"/>
But this will:
<xsl:template name="foobarsnafu">
<xsl:param name="snafu"/>
<xsl:variable name="foo" value="bar">
This might be a problem if, for example, you want the default value of your snafu <xsl:param> to be the value of your foo <xsl:variable>. The way around it would be to use <xsl:param> with a default value for your template name, i.e.:
<xsl:template name="foobarsnafu">
<xsl:param name="template_name" select="foobarsnafu"/>

Does adding this root match template interfere with existing transformations?

Say you have an XSLT which is currently in use for a myriad of cases. Given that it does not currently have any <xsl:template match="/">; would adding the following template interfere with anything?
<xsl:template match="/">
<xsl:choose>
<xsl:when test="some condition which is only true for new cases"></xsl:when>
<xsl:otherwise>
<xsl:apply-templates />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Reason I'm asking is that we have a huge XSLT file which is currently in use and should not be broken, but we need to add some new transformations to it. Making some decisions on the root would make things a lot easier and cleaner.
I was just told that if there is no template matching /, then a default template equivalent to <xsl:apply-templates /> will be applied. So, just wanting to check if my understand is correct, and that this then (as long as the test is only true for new cases) would not break any existing transformations.
That's correct, you can handle your conditions within the root template "/" and just apply-templates for the "normal" treatement.
But check existing root templates matching "/" - maybe it is already doing more than just apply the next templates.