Reputation: 61
I want to perform a conditional include in XSLT, but xsl:include is a top level element. You can only use xsl:if or xsl:choose inside of a template. Is there any kind of hack or work around which allows a conditional include of an external file? I tried to use the document() function, but it fails to load my external file ( probably because it doesn't conform to some set of rules which would make it "valid" ).
My external xml file is a bunch of xslt code fragments. depending on the value of a variable in the main XSLT file, the corisponding code from the external file should be "copy/pasted" in place ( like conditional include in C or PHP ).
The flow of my main XSLT file should proceed in the following manner:
$configurationMode
if ( $configurationMode = Standard ) { xsl:include="standard.xml" } else { xsl:include="alt.xml" }
Obviously I cannot do it as simply as the above, hence why I am asking if there is a hack or workaround.
Upvotes: 6
Views: 10722
Reputation: 429
There is one approach I have found to conditionally include files using use-when feature in XSLT 2.0.
You have to create one system property which is passed through the Application at runtime, like in my case I am using Java so I have created "Debug" property using command to run my application:
java -DDebug=yes myAppPath
then in XSLT use this property to include conditional files. For example
<xsl:include href="file1.xslt" use-when="system-property('DEBUG') = 'yes'"/>
<xsl:include href="file2.xslt" use-when="system-property('DEBUG') != 'yes'"/>
like that.
Don't know if it fits for asked scenario but YES there is way to do it in XSLT 2.0. In XSLT 3.0, support for local variable is also added. So this can be easily done there using local variables.
Thanks, Happy Coding
Upvotes: 0
Reputation: 52878
With the addition of static parameters, this is now possible in XSLT 3.0. Static parameters can be used in the use-when
attribute of the xsl:include
.
Now we can declare parameters with default values of false()
and then override the ones we need at run time...
<xsl:param name="someparam" as="xs:boolean" select="false()"
static="yes" required="no"/>
<xsl:include href="include_me.xsl" use-when="$someparam"/>
Here is a full working example tested with Saxon-HE v9.7 (also tested with Saxon-PE 9.5).
XML Input (test.xml)
<doc>
<foo/>
</doc>
Main XSLT 3.0 (test_main.xsl)
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="inc1" as="xs:boolean" select="false()"
static="yes" required="no"/>
<xsl:param name="inc2" as="xs:boolean" select="false()"
static="yes" required="no"/>
<xsl:include href="test_inc1.xsl" use-when="$inc1"/>
<xsl:include href="test_inc2.xsl" use-when="$inc2"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
First possible included XSLT 3.0 (test_inc1.xsl)
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="foo">
<xsl:copy>INCLUDE FILE 1!!!</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Second possible included XSLT 3.0 (test_inc2.xsl)
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="foo">
<xsl:copy>INCLUDE FILE 2!!!</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Command line (setting inc2
to true)
java -cp "saxon9he.jar" net.sf.saxon.Transform -s:"test.xml" -xsl:"test_main.xsl" inc2="true"
Output
<doc>
<foo>INCLUDE FILE 2!!!</foo>
</doc>
Upvotes: 0
Reputation: 389
Not sure if this would apply to your scenario, but I'm going to throw this out there anyways.
I've done similar things in the past, but instead of doing a conditional include, I call a template in either one of the two files based on what the xsl:when evaluates to. If you don't want to use templates in your case, disregard this answer.
For instance, say the "parent" xslt file is called root.xslt, and the two conditionally-used ones are child1.xslt and child2.xslt. Say I want to run conditional logic against the value of a node called status. If the value is "CURRENT", I want to call a template called isCurrent in child1.xslt, otherwise call a template called isNotCurrent in child2.xslt. For the sake of brevity, in each case I'm simply passing the template the root node and all children.
It'd look something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:import href="child1.xslt"/>
<xsl:import href="child2.xslt"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="rootNode/status = 'CURRENT'">
<!-- template isCurrent defined in child1.xslt -->
<xsl:apply-templates select="rootNode" mode="isCurrent" />
</xsl:when>
<xsl:otherwise>
<!-- template isNotCurrent defined in child2.xslt -->
<xsl:apply-templates select="rootNode" mode="isNotCurrent" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Hope this at least helps a little bit.
Upvotes: 2
Reputation: 1
<xsl:variable name="CONDITIONAL" select="ELEMENT/CONDITION"/>
<xsl:variable name="DOCUMENTNAME" select="document(concat($CONDITIONAL,'-DOCUMENT.xml'))/ELEMENT"/>
This example is how I did a conditional use of an external file. I had the flag in the base XML that the XSL uses as a variable to help specify the name of the other file to pull into the template. Once that variable is defined as in my case I use it to pull other XML data into the generated output. The concat command forces the data together to give a file name.
So When I want info from the external XML file I use '$DOCUMENTNAME' for referencing the external data.
Upvotes: 0
Reputation: 39738
In addition to what already has been said, a possible solution would be to make the supplementary files to be plain, content-providing XML files (instead of XSLT files). That way, you can include them with XPath's document()
function (which will be evaluated at run-time rather than compile-time).
You can then alter the behavior of your transformation based on the contents of the loaded XML document; however, you cannot provide executable XSLT fragments in the included documents.
It depends on your use-case whether this is a solution - if the additional documents have a strong impact on the control flow of your transformation, you don't want to define them in plain XML, because you basically would re-implement something like XSLT. However, when your documents serve as kind of configuration files, you might want to think about providing them als pure XML.
When you're having problems with document()
, use an XML validator on your files. An error implies that your files are not valid XML.
Upvotes: 0
Reputation: 163458
Try inverting the structure: if you have two special purpose modules pink.xsl and blue.xsl, and a general-purpose module baby.xsl, then instead of trying to import/include one of pink.xsl or blue.xsl into baby.xsl, instead use pink.xsl or blue.xsl as the top-level entry stylesheet, and have each of these two import baby.xsl. That's the way it was designed to be used, it's nto a hack or a workaround.
Alternatively, given this description of your scenario "My external xml file is a bunch of xslt code fragments", a better approach in your case might be to assemble the stylesheet from these fragments as a separate step, using an XSLT tranformation rather than using xsl:include/xsl:import.
Upvotes: 3
Reputation: 891
As I understand things, 'include' happens when the xml parser is parsing and compiling the style sheet. This means that no logic or expression evaluation can happen before the include gets processed and therefore there's no way to make it conditional as such. You need to make the conditional behavior happen outside of the style sheet.
Have a look at this http://www.dpawson.co.uk/xsl/sect2/N4760.html#d6065e100
In particular does this comment by Mike Kay help:
This has been raised a number of times. On a previous thread we came to the conclusion that the user was trying to write a general-purpose stylesheet G and then specialize it by conditionally including a special-purpose stylesheet A or B. The way to meet this requirement is to have A and B include G, not the other way around, and then you conditionally select A or B as the principal stylesheet when starting the transformation.
Upvotes: 3
Reputation: 243529
This cannot be done with XSLT 1.0 and can be done (to a very limited extent) in XSLT 2.0 using the use-when
attribute.
There exist non-xslt ways of achieving the wanted dynamic altering of an xsl:include
or an xsl:import
directive.
One such method is to load the XSLT stylesheet as an XmlDocument, and using the available DOM methods for access to and modification of attributes, to set the href
attribute to the desired value. Then initiate the transformation from this in-memory-modified XMLDocument-contained XSLT stylesheet.
Upvotes: 6