xPath oddity where predicates are somehow necessary - xslt

This may seem like an odd question as I did find a working solution, but can anyone tell me why my first xPath below works and the second one does not? I did include the enes namespace in my XSLT.
Solution A works:
<xsl:copy-of select="document('my_document_of_citations.xml')//node()[namespace-uri()='enes' and local-name()='section' and position() = $section-pos]/node()[namespace-uri()='enes' and local-name()='litref']" />
Solution B does not work:
<xsl:copy-of select="document('my_document_of_citations.xml')//enes:section[position() = $section-pos]/enes:litref" />
Here is the stylesheet, with only the code in a non-germain template and function omitted:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="enes thieme xhtml xlink xs" version="2.0" xmlns:enes="http://www.thieme.de/enes" xmlns:thieme="http://www.thieme.de/enes" xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="enesfunc">
<xsl:output method="xml" indent="yes" version="1.1" omit-xml-declaration="no" encoding="UTF-8" />
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<!-- Identity Transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="part[#type eq 'content']/section[#level eq '1']">
<xsl:element name="section" xmlns="enes">
<xsl:attribute name="level" select="1" />
<xsl:attribute name="id"><xsl:value-of select="./#id"/></xsl:attribute>
<xsl:attribute name="counter"><xsl:value-of select="./#counter"/></xsl:attribute>
<xsl:apply-templates />
<!-- Insert a level 2 section with the references for this level 1 section from references_by_chapter.xml here. -->
<xsl:element name="section" xmlns="enes">
<xsl:attribute name="level" select="2" />
<xsl:attribute name="type">
<xsl:text>literature</xsl:text>
</xsl:attribute>
<!-- Get the absolute position of this section within the document. -->
<xsl:variable name="section-pos" select="count(./preceding-sibling::section) + count(ancestor::node()/preceding-sibling::node()[local-name() eq 'part']/node()[local-name() eq 'section']) + 1" />
<!-- Copy extracted references from xml here -->
<xsl:copy-of select="document('references_by_chapter.xml')//node()[namespace-uri()='enes' and local-name()='section' and position() = $section-pos]/node()[namespace-uri()='enes' and local-name()='litref']" />
<!-- <xsl:copy-of select="document('references_by_chapter.xml')//enes:section[position() = $section-pos]/enes:litref" /> -->
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template match="section[#level eq '2']//text()">
<!-- code to transform level 2 sections here. -->
</xsl:template>
<xsl:function name="func:myStrFunc">
<!-- more code here. -->
</xsl:function>

Simplifying the code to remove irrelevant detail, consider
child::x[position()=2]
versus
child::*[name()='x' and position()=2]
The meaning of these two constructs is quite different. The first expression considers all the child elements whose name is 'x', and then returns the second of these. The second construct considers all the child elements, and then selects the second child provided that its name is 'x'.
Which of these "works" depends of course on what your requirements are. Both of them are correct, they just do different things.

Related

XSLT modify attribute value at EXACT element by passing the original value to a template

I'm struggling to get this abomination called XSLT to work. I need to get an EXACT attribute at EXACT path, pass its original value to a template and rewrite this value with the result from the template.
I'm having a file like this:
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="17">
...
...
</Document>
</File>
So I made an XSLT like this:
<?xml version="1.0" encoding="windows-1251"?>
<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" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="formatYear">
<xsl:param name="year" />
<xsl:value-of select="$year + 2000" />
</xsl:template>
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear" />
</xsl:call-template>
</xsl:attribute>
</xsl:copy>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
This works fine except it closes the <Document> tag immediately and places its content immediately after itself.
Also, can I address the ReportYear attribute value without repeating it twice? I tried current() but it didn't work.
If you're closing <xsl:copy> before applying templates to the remainder of the content of <Document>, then of course <Document> will be closed before the remainder of the content of <Document> appears in the output.
<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" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:value-of select="#ReportYear + 2000" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
outputs
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="2017">
...
...
</Document>
</File>
I don't think an extra template just for adding 2000 to #ReportYear is necessary. But if you must, you can streamline the whole thing like so
<xsl:template name="formatYear">
<xsl:param name="year" select="#ReportYear" /> <!-- you can define a default value -->
<xsl:value-of select="$year + 2000" />
</xsl:template>
and
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear" /> <!-- ...and can use it implicitly here -->
</xsl:attribute>
If you need to process the contents of the Document element with apply-templates and want to keep the result of the applied templates as the children then you need to move the apply-templates inside of the copy:
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear"/>
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Not sure why you haven't simply used
<xsl:template match="File/Document/#ReportYear">
<xsl:attribute name="{name()}">
<xsl:value-of select=". + 2000"/>
</xsl:attribute>
</xsl:template>
together with the identity transformation template.

Avoid repetition in XSLT template rules using XSLT 1.0

Given the following XML document:
<document>
<a>123</a>
<foo_1>true</foo_1>
<foo_2>false</foo_2>
<foo_3>true</foo_3>
<foo_4>true</foo_4>
<foo_5>false</foo_5>
<b/>
<bar_1>false</bar_1>
<bar_2>false</bar_2>
<bar_3>true</bar_3>
<bar_4>false</bar_4>
<bar_5>true</bar_5>
<c>some text</c>
</document>
I want to transform this document by eliminating all enumerated elements containing false and by converting all enumerated elements containing true into the form <prefix_n>value</prefix_n>, where value is the number after the underscore.
For the example given above the result should look like this:
<document>
<a>123</a>
<foo_n>1</foo_n>
<foo_n>3</foo_n>
<foo_n>4</foo_n>
<b/>
<bar_n>3</bar_n>
<bar_n>5</bar_n>
<c>some text</c>
</document>
For this I am using the following transformation, which works fine.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<!-- standard copy template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[substring(name(), 1, 4) = 'foo_']">
<xsl:if test=". = 1 or . = 'true'">
<xsl:element name="foo_n">
<xsl:value-of select="substring-after(name(), 'foo_')"/>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="*[substring(name(), 1, 4) = 'bar_']">
<xsl:if test=". = 1 or . = 'true'">
<xsl:element name="bar_n">
<xsl:value-of select="substring-after(name(), 'bar_')"/>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Now I'm facing the problem that I have to deal with a multitude of different prefixes, not just foo_ and bar_.
Is there a way to turn the template rules into a named template where I can pass in the prefix as an argument, thereby avoiding to write a lot of repetitive template rules?
Here's one way you could look at it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(), 'foo_') or starts-with(name(), 'bar_')]">
<xsl:if test=". = 1 or . = 'true'">
<xsl:element name="{substring-before(name(), '_')}-n">
<xsl:value-of select="substring-after(name(), '_')"/>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Here's another:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[contains(translate(name(), '123456789', '000000000'), '_0')]">
<xsl:if test=". = 1 or . = 'true'">
<xsl:variable name="prefix" select="substring-before(translate(name(), '123456789', '000000000'), '_0')" />
<xsl:element name="{$prefix}-n">
<xsl:value-of select="substring(name(), string-length($prefix) + 2)"/>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This last one should work with any prefix followed by _[1-9] - including prefixes that contain an additional underscore and including prefixes that contain other prefixes.
Is there a way to turn the template rules into a named template where I can pass in the prefix as an argument
Well, sure:
<xsl:template name="if-true">
<xsl:param name="prefix"/>
<xsl:if test=". = 1 or . = 'true'">
<xsl:element name="{concat($prefix, 'n')}">
<xsl:value-of select="substring-after(name(), $prefix)"/>
</xsl:element>
</xsl:if>
</xsl:template>
<!-- example using the above: -->
<xsl:template match="*[starts-with(name(), 'foo_')]">
<xsl:call-template name="if-true">
<xsl:with-param name="prefix" select="'foo_'"/>
</xsl:call-template>
</xsl:template>
But it's not clear whether that would really achieve your objective:
, thereby avoiding to write a lot of repetitive template rules?
because you would still need a bunch of templates of the second kind to match the specific elements you want to transform. A solution along the lines #michael.hor257k suggested, that avoids duplication by making the same template match all the elements you want to transform, is a better alternative.

Add child elements in particular order

I am working on an xml to xml transform via XSLT. I Have the following:
stylesheet.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.org"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"> <!-- xs namespace allows typed functions and parameters -->
<xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*" />
<xsl:param name="other-id" select="Request/Order/OtherId" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" copy-namespaces="no" />
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
<xsl:template match="#*|text()|comment()|processing-instruction()">
<xsl:copy />
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="Details">
<xsl:element name="{local-name()}">
<xsl:element name="Signon>
<xsl:element name="SignonDt>2017-01-01</xsl:element>
<xsl:element name="MessageQuantity">3</xsl:element>
<xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="NotificationRq">
<xsl:element name="{local-name()}">
<xsl:element name="RqUID">Test</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Policy/SDDCd" />
<xsl:template match="Policy">
<xsl:element name="{local-name()}">
<xsl:element name="RFDCd">
<xsl:call-template name="getRFDCd" />
</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template name="getRFDCd">
<xsl:choose>
<xsl:when test="contains($other-id, 'RFD 2')">
<xsl:text>AUB</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>CL</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Contact/Addr" >
<xsl:element name="{local-name()}">
<xsl:element name="AddrTypeCd">StreetAddress</xsl:element>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<!-- other templates structured similar to these -->
input.xml
<Request>
<Details>
<NotificationRq>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
</NotificationRq>
<Policy>
<PolNumber>1234567890</PolNumber>
<SDDCd>T35</SDDCd>
</Policy>
<Contact>
<Addr>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
</Request>
output.xml
<Details xmlns="http://test.org">
<Signon>
<SignonDt>2017-01-01</Signon>
<MessageQuantity>3</MessageQuantity>
</Signon>
<NotificationRq>
<RqUId>Test</RqUID>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
</NotificationRq>
<Policy>
<RFDCd>CL</RFDCd>
<PolNumber>1234567890</PolNumber>
</Policy>
<Contact>
<Addr>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
Note: I am using <xsl:element name="{local-name()}"> due to an earlier issue; i am copying source xml which has no namespace to new output which does, and this solution, along with the extra template alongside the identity transform fixed the issue of a namespace attribute being added to all elements
The problem is, this xml is sent downstream where schema validation occurs, and because of this, position of newly created child elements matter. As shown in the snippets above, new child elements are added as the first child of the parent. For some cases this is fine and this is where they should be, but for around half of the created elements, they must appear last(with the odd one or two requiring insertion at a specified position). Still using input.xml as an example, here is how the desired output should look:
desired-output.xml
<Details xmlns="http://test.org">
<NotificationRq>
<RqDate>2017-01-01</RqDate>
<RqDetails>
<!-- other children -->
<RqDetails>
<RqUId>Test</RqUID>
</NotificationRq>
<Signon>
<SignonDt>2017-01-01</Signon>
<MessageQuantity>3</MessageQuantity>
</Signon>
<Policy>
<PolNumber>1234567890</PolNumber>
<RFDCd>CL</RFDCd>
</Policy>
<Contact>
<Addr>
<AddrLn1>Test address line 1</AddrLn1>
<AddrLn2>Test address line 2</AddrLn2>
<AddrTypeCd>StreetAddress</AddrTypeCd>
<PostCode>AX12D3</PostCode>
</Addr>
</Contact>
<!-- other children -->
</Details>
Is there a way to specify where in the existing child order of an element the new child should appear? Is one of my templates causing the insertion order to always be in the first position?
Additional Info: I have seen a few questions on specific order insertion, but they usually seem to be for a sequence of elements, or a series of repeating elements, e.g. how do I insert another author element in a series of author elements, and the solutions tend to make use of a position function to determine if one has looped to the right index, and then insert. The xml I am working with is made up of unique elements which may hold a value or may contain several children(with some of those containing children etc). There are no repeating elements in the xml, so I don't think I can make use of a solution as described above(unless someone knows how to do such a thing for non repeating children of an element). Also, I am using Saxon HE version 9.7.0-8
Why can't you do simply:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.test.org">
<xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:copy-of select="#*" />
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="NotificationRq">
<NotificationRq>
<xsl:apply-templates/>
<RqUId>Test</RqUId>
</NotificationRq>
</xsl:template>
</xsl:stylesheet>

How to pass parameters between xsl templates

I have the following xsl file, that joins two xml files by the element "id":
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" />
<xsl:key name="trans" match="Transaction" use="id" />
<!-- Identity template to copy everything we don't specifically override -->
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<!-- override for Mail elements -->
<xsl:template match="Mail">
<xsl:copy>
<!-- copy all children as normal -->
<xsl:apply-templates select="#*|node()" />
<xsl:variable name="myId" select="id" />
<Transaction_data>
<xsl:for-each select="document('transactions.xml')">
<!-- process all transactions with the right ID -->
<xsl:apply-templates select="key('trans', $myId)" />
</xsl:for-each>
</Transaction_data>
</xsl:copy>
</xsl:template>
<!-- omit the id element when copying a Transaction -->
<xsl:template match="Transaction/id" />
</xsl:stylesheet>
Now I want id to be a parameter. I will obtain the parameter from an xml file:
<xsl:param name="usenode" select="*[name() = document('params.xml')/*/join_node]"/>
<xsl:key name="trans" match="Transaction" use="*[name() = document('params.xml')/*/join_node]" />
I can print the value of that variable right after these lines, but further, when I want to use that, it seems the value of the variable vanishes. What to do to make the variable visible for the entire file?
EDIT:
OK, I'm trying to ask the question more specificly.
So my goal is that I want the "id" in use attribute of the
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" />
<xsl:param name="usenode" select="*[name() = document('params.xml')/*/join_node]"/>
<xsl:key name="trans" match="Transaction" use="*[name() = document('params.xml')/*/join_node]" />
<!-- Identity template to copy everything we don't specifically override -->
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<!-- override for Mail elements -->
<xsl:template match="Mail">
<xsl:copy>
<!-- copy all children as normal -->
<xsl:apply-templates select="#*|node()" />
<xsl:variable name="myId" select="$usenode" />
<Transaction_data>
<xsl:for-each select="document('transactions.xml')">
<!-- process all transactions with the right ID -->
<xsl:apply-templates select="key('trans', $myId)" />
</xsl:for-each>
</Transaction_data>
</xsl:copy>
</xsl:template>
<!-- omit the id element when copying a Transaction -->
<xsl:template match="Transaction/id" />
</xsl:stylesheet>
When I referring to $usenode, it is not visible. Can anyone help me?
You haven't said where you put the xsl:param. If it's a child of xsl:stylesheet, then it's visible throughout the stylesheet. If it's a child of xsl:template, then it's visible only within that template. The title of your post (passing parameters between templates) suggests you want a template parameter (ie. xsl:param as child of xsl:template) - but frankly, you haven't made it at all clear what you are trying to achieve, what you attempted, or how it failed. So I'm downvoting the question.

Count Nodes and Duplicate with no values in XSLT

I am super rusty at XSLT and was wondering if someone can give me some pointers.
Edit: Using XSLT 1.0
Original XML:
<gic>
<application>
<agent>
...child nodes
</agent>
<client>
...child nodes
</client>
<bank>
...child nodes
</bank>
</application>
</gic>
I need to transform the given XML INPUT to have 5 client nodes. The input can contain 1-5 client nodes populated. I need to ensure there is always 5 in the output. In this case, one is provide, so I need to insert 4 client nodes with all child nodes. Values for all child nodes need to be empty. Output in XML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="application" as="node()*">
<xsl:copy>
<xsl:apply-templates select="agent" />
<!-- copy existing cliens -->
<xsl:apply-templates select="client" />
<!-- add new clients -->
<xsl:call-template name="AddClients">
<xsl:with-param name="times" select="10 - count(client)" />
</xsl:call-template>
<!-- copy banks -->
<xsl:apply-templates select="bank" />
</xsl:copy>
</xsl:template>
<xsl:template name="AddClients">
<xsl:param name="times" select="1" />
<xsl:if test="number($times) > 0">
<!-- new element here -->
<xsl:element name="client">
<xsl:attribute name="a1">
<xsl:value-of select="asas" />
</xsl:attribute>
</xsl:element>
<xsl:call-template name="AddClients">
<xsl:with-param name="times" select="$times - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- default -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>