can you branch in xslt depending on output format? - xslt

I'm creating an xlst script and was wondering if it is possible to branch some code depending on the output format?
On top of my xlst file I have this:
<xsl:output
version="4.0"
method="html"
indent="no"
encoding="UTF-8"
use-character-maps="spaces"/>
So I suppose there is something out there to inquire some sort of global to do this:
<xsl:if test='global_output is html'>
do this
</xsl:if>
Thank you!

If you want to create variants of a stylesheet for use in different situations, don't put if/then/else code inside the template rules to test the condition at run-time. That way you end up with spaghetti. Create two stylesheet modules to-html.xsl and to-xml.xsl, and have both import a module common.xsl that contains the shared code. The common.xsl module can call back to the importing module when it needs to invoke functionality that varies between the two cases. One of the differences between the two cases is of course the xsl:output declaration itself.

In 1.0 one can use:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output version="4.0"
method="html" indent="no" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:if test="document('')/*/xsl:output/#method = 'html'">
Output method is HTML
</xsl:if>
<xsl:if test="document('')/*/xsl:output/#method = 'xml'">
Output method is XML
</xsl:if>
</xsl:template>
</xsl:stylesheet>
May be there is a classier way to do it in XSLT 2.0.

Related

Best way to include xslt inside another xslt when just a variable needs redefining

I've got 4 almost identical stylesheets (in XSLT 1.0), that are copy pasted, so 2 of them would look like this.
<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:output method="xml" indent="yes"/>
<xsl:variable name="foo" select="input[#name = 'foo1']"/>
<xsl:template match="/">
<foo>
<xsl:value-of select="$foo"/>
</foo>
</xsl:template>
</xsl:stylesheet>
and another
<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:output method="xml" indent="yes"/>
<xsl:variable name="foo" select="input[#name = 'foo2']"/>
<xsl:template match="/">
<foo>
<xsl:value-of select="$foo"/>
</foo>
</xsl:template>
</xsl:stylesheet>
as you see the only difference is the definition of the variable foo (and in reality the xslt itself is much much more complex).
whats the best way to just define the xslt once, and have 4 wrappers that simply decorate this shared one with a different definition of the variable.
There seems to be at least 3 options, include, import and then XML option XInclude etc?
Is there an idiomatic way to do this? (I assume this is a pretty common requirement).
The basic approach is to put the common code in a shared module that's imported by all the 4 wrappers.
In the shared module you can either declare the variable with a dummy value, or not declare it at all.
In the wrapper module, you just need two declarations:
<xsl:import href="common.xsl"/>
<xsl:variable name="foo" select="input[#name = 'foo2']"/>
With XSLT 3.0 you can get a lot more elaborate using packages, in which declarations can be annotated as private, abstract, final etc.

Use XSLT to copy XML without the xml declaration

I have the following xml and want the output to not contain the xml declaration
i.e.
FROM
<?xml version="1.0" encoding="UTF-8"?>
<tns:MFTRNS xmlns:tns="MFTRNS" recordState="New" msgVersion="13.0">
<OSCONO>100</OSCONO>
<OSINOU>1</OSINOU>
<OSDLIX>155379</OSDLIX>
<OSPANR>AAG44780</OSPANR>
<OSWHLO>AAG</OSWHLO>
</tns:MFTRNS>
TO
<tns:MFTRNS xmlns:tns="MFTRNS" recordState="New" msgVersion="13.0">
<OSCONO>100</OSCONO>
<OSINOU>1</OSINOU>
<OSDLIX>155379</OSDLIX>
<OSPANR>AAG44780</OSPANR>
<OSWHLO>AAG</OSWHLO>
</tns:MFTRNS>
Can you get an xslt to do this and if so how?
The reason for doing this is that I want to wrap the xml in an envelope which cannot be done if the declaration is a part of the XML as it does not create a valid xml file
Thanks
If you only want to remove the declaration then a stylesheet as simple as this will do it:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:copy-of select="node()" />
</xsl:template>
</xsl:stylesheet>
But if your ultimate aim is to "wrap the xml in an envelope" then you might be better doing that directly in your XSLT, for example:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" />
<xsl:template match="/">
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope">
<xsl:copy-of select="node()" />
</soap:Envelope>
</xsl:template>
</xsl:stylesheet>
which will be safer than trying to combine the two files using non-XML-aware textual operations. For example, if your envelope declares a default namespace xmlns="http://example.com" then simply inserting the text of another XML document inside the envelope would change the semantics as it would move the non-prefixed elements like OSCONO into the envelope's default namespace when they should really be in no namespace. XSLT will spot this case and add the necessary xmlns="" overrides.
It's simple: you have to set the omit-xml-declaration attribute of your xsl:output element to yes.

Saxon line break issue in output XML

I am using Saxon HE 9.5 as my XSLT processor. Since the source is a large-sized XML, I need to minimize the size of output. However, using the Saxon HE will add line breaks between each element tags. Such as the following example:
<Element1>
<attr1>
test1
</attr1>
</Element1>
I want it to be like:
<Element1> <attr1> test1 </attr1> </Element1>
so that I can minimize the size of the output XML. Is there any way to do it?
I have tried to set indent="no", but the output XML is failed to open.
Thank you!
You can use <xsl:output indent="no"/> to turn off the indenting, but your line breaks in elements that contain text will still be there (even with <xsl:strip-space elements="*"/>). You can use normalize-space() to remove them.
Example...
XML Input
<Element1>
<attr1>
test1
</attr1>
</Element1>
XSLT 2.0 (works as 1.0 too)
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|*|processing-instruction()|comment()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>
</xsl:stylesheet>
XML Output
<Element1><attr1>test1</attr1></Element1>
The option indent="no" is the default. If you are getting indented output, then either (a) you have asked for it using indent="yes", or (b) the whitespace is present in the result tree before serialization. If the whitespace is present in the result tree, then either (b1) the stylesheet added it to the result tree, or (b2) it was copied from the source document. If (b2) is the cause, then putting <xsl:strip-space elements="*"/> in your stylesheet might be the answer (assuming you don't have any significant whitespace in the source document that needs to be preserved).
We can't give anything other than general advice unless you show us your code.

How to "genericize" an XML-to-XML XSL transform to work with files having different tags but same basic structure

I have a set of XML files similar to this:
Example Input File:
<a-list>
<a-item key_field="unique1" other="foo"/>
<a-item key_field="unique2" other="foo"/>
...
</a-list>
and I have a transform that will merge these files into a single file of the same structure. It generates the same outer level element (<a-list>) containing all of the <a-item> elements from the files, and discards duplicate entities (entities having the same key_field). I am now faced with several other sets of files, each very similar in its top level structure, that I need to perform almost the same exact kind of merge.
For example, these files might look like this:
<b-list>
<b-item different_key_field="unique93" other="foo"/>
<b-item different_key_field="unique94" other="foo"/>
...
</b-list>
These other files have different data in them, but the transform is generically the same: I need to take multiple input files that have the same structure (a root level containing a list of items, where the items are uniquely identified by a key attribute), and produce an output file that has the top level element containing all of the (unique) items from each of the input files.
Unfortunately, my "merge" transform operates specifically on the named elements in the input files (e.g. it has templates for <a-item> nodes, and it specifically looks for duplicated key_field attributes). It is fairly trivial to copy the transform, search/replace the element names and the key attribute name, but it seems horribly inefficient to duplicate the same code once for each type of input file. The only things that are different are the name of the root element, the name of the item element, and the name of the key attribute. What I want to do (merge) remains the same.
How can I write a transformation that can perform this kind of merge operation without specifying the exact names of the elements/attribute, so that I can invoke the same transform for each type of file? Is there any way to accept the root element name, item element name, and key attribute name as parameters?
As additional constraints, I'm limited to using Xalan, so I believe that means XSL 1.0 only, and it would be best to avoid any extensions.
I thought about transforming an XSL with another XSL, but that seems rather convoluted for such a simple thing.
I've tried searching here and around the internet via Google, but I'm somewhat new to XSL, and all of the words I can think to search in association with XSL or XSLT seem to have specific meanings in the XSL context that make them less useful as search terms (e.g. XSL template, generic XSL, etc.). Some vocab and some good links would be great, an example would be amazing.
Many Thanks,
Russ
Here is a complete solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pDoc1File" select="'b-list-file1.xml'"/>
<xsl:param name="pDoc2File" select="'b-list-file2.xml'"/>
<xsl:param name="pElemName" select="'b-item'"/>
<xsl:param name="pKeyAttrName" select="'different_key_field'"/>
<xsl:variable name="vDoc1" select="document($pDoc1File)"/>
<xsl:variable name="vDoc2" select="document($pDoc2File)"/>
<xsl:template match="/">
<xsl:apply-templates select="$vDoc1/node()"/>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select=
"*[name()=$pElemName]
[not(#*[name()=$pKeyAttrName]
=
$vDoc2/*/*[name()=$pElemName]
/#*[name()=$pKeyAttrName])
]"/>
<xsl:copy-of select=
"$vDoc2/*/*[name()=$pElemName]
[not(#*[name()=$pKeyAttrName]
=
$vDoc1/*/*[name()=$pElemName]
/#*[name()=$pKeyAttrName])
]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
File: b-list-file1.xml
<b-list>
<b-item different_key_field="unique93" other="foo"/>
<b-item different_key_field="unique94" other="foo"/> ...
</b-list>
File: b-list-file2.xml
<b-list>
<b-item different_key_field="unique92" other="foo"/>
<b-item different_key_field="unique93" other="foo"/> ...
</b-list>
When this transformation is applied on any XML document (not used / ignored), the wanted, correct result is produced:
<b-list>
<b-item different_key_field="unique94" other="foo"/>
<b-item different_key_field="unique92" other="foo"/>
</b-list>
Explanation:
The filepaths (URLs) to the documents to be processed are provided as global (external to the transformation) parameters.
The name of the elements is provided in a global parameter.
The name of the "key" attribute is provided as a global/external parameter.
Finally, the XPath expressions in the select attributes of the two <xsl:copy-of> instructions, use the values of the parameters for selecting the appropriately-named elements and attributes.
Do note: Every XSLT processor has its own implementation of setting and passing parameters to the transformation. Read this in the Xalan documentation.
Well you can solve your problem with parameters. There is no notion of templated xsl like C++ templates for example, but with parameters you can achieve this effect. For example one of my xslt files looks like this :
<xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xml:space="preserve" version="2.0">
<!--Output type to be used with the xsl:result-document-->
<xsl:output name="html" encoding="utf-8" method="html" indent="yes" doctype- system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"/>
<!--Global output type-->
<xsl:output encoding="utf-8" method="html" indent="yes" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"/>
<!--xml output-->
<xsl:output name="xml" encoding="utf-8" method="xml" indent="yes"/>
<xsl:param name="sapFile" as="xs:string" required="yes"/>
<xsl:param name="automaticDate" as="xs:string" required="yes"/>
<xsl:param name="generatePDF" as="xs:string" required="yes"/>
<xsl:param name="fileName" as="xs:string" required="yes"/>
.
.
.
The important element here is xsl:param. You can pass these parameter when you "call" your xsl transform. These parameters in your case would be for example : in one case a-list and in the other case b-list etc. Then you can use these parameters like $param wherever you want in your xslt code so that you can achieve genericity.
I hope that give you some pointers :)

How to use a function from one xsl in another

I have two xsl files: "one.xsl" and "two.xsl"
one.xsl:
<xsl:function name="x:trans" as="xs:string">
<xsl:param name="str"></xsl:param>
<xsl:variable name="res1" select="x:translate_string($str)"/>
<xsl:sequence select="$res1"/>
</xsl:function>
</xsl:stylesheet>
I want to use function "x:trans" in "one.xsl"
How do i reference the function to another file?
The problem is that when i try to call for this function this way:
< xsl:value-of select="x:trans('Hello World')"/>
I get the following error message from browser:
Reference to undeclared namespace prefix: 'x'
Apart from the correct replies that you need to <xsl:include> or <xsl:import> (I'd recommend the latter as the former can often result in duplication errors), your other problem is the following:
A function name must belong to a namespace.
The namespace must be declared (defined and bound to a prefix) in the same file in which the function is defined.
Any call to the function has to prefix the name of the function and that prefix must be bound to the same namespace to which the function name belongs
Here is a simple example:
I. File deleteA.xsl defines the function my:double
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my"
>
<xsl:function name="my:double" as="xs:double">
<xsl:param name="pArg" as="xs:double"/>
<xsl:sequence select="2*$pArg"/>
</xsl:function>
</xsl:stylesheet>
II. File deleteB.xsl imports file deleteA.xsl and uses the function my:double :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:import href="deleteA.xsl"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="my:double(.)"/>
</xsl:template>
</xsl:stylesheet>
III. The transformation contained in deleteB.xsl is applied on the following XML document:
<t>1</t>
and the correct result is produced:
2
Additional comment: At present no browser supports XSLT 2.0 transformations -- xsl:function is only available in XSLT 2.0 +.
You want to either do <xsl:include /> or <xsl:import />. <xsl:include /> is simpler (it just drags everything in) while <xsl:import /> is more flexible (if there are templates colliding between the two, the over-ride of the called by the calling is better defined and generally sensible).
Edit for added info:
You need to make sure you call the templates in the imported stylesheet using the appopriate namespace. The easiest way is to make sure you have matching xmlns:foo declarations in the stylesheets, though you could call foo:template in one stylesheet as bar:template in the other if it had xmlns:bar instead.
In two.xsl:
<xsl:include href="one.xsl" />
Also see the description of include in the XSLT 2.0 spec.