Add a node inside a node tree - XSL - xslt

I'm actually performing a transformation with XSL, and I need to create a template that manage to add a node inside a node tree and return all this node tree with the new node.
In my exemple, I would like to add the node <node3>HIJ</node3> after </node2>, it's working at some point but i'm loosing the upper node.
The node tree :
<xsl:variable name="A">
<node>
<node1>ABC</node1>
<node2>DEF</node2>
<node4>KLM</node4>
</node>
</xsl:variable>
The call of the template
<xsl:apply-templates select="$A" mode="copy">
<xsl:with-param name="ValueOfTheNode" select="'HIJ"/>
</xsl:apply-templates>
My template that actually doesn't work
<xsl:template match="node()/*" mode="copy">
<xsl:param name="ValueOfTheNode"/>
<xsl:copy-of select="."/>
<xsl:if test="./name() = 'node2'">
<node3>
<xsl:value-of select="$ValueOfTheNode"/>
</node3>
</xsl:if>
</xsl:template>
Current result :
<node1>ABC</node1>
<node2>DEF</node2>
<node3>HIJ</node3>
<node4>KLM</node4>
Expected result
<node>
<node1>ABC</node1>
<node2>DEF</node2>
<node3>HIJ</node3>
<node4>KLM</node4>
</node>
I don't know how to say to the code to keep the upper node, or maybe i'm wrong from the start on the way to treat it. I'm using xsl 2.0.
Thank you

I would simply write a template matching node2:
<xsl:template match="node2" mode="copy">
<xsl:param name="value" tunnel="yes"/>
<xsl:next-match/>
<node3>
<xsl:value-of select="$value"/>
</node3>
</xsl:template>
then use
<xsl:apply-templates select="$A" mode="copy">
<xsl:with-param name="value" select="'HIJ'" tunnel="yes"/>
</xsl:apply-templates>
and have the default template for the mode set up the identity transformation e.g. <xsl:mode name="copy" on-no-match="shallow-copy"/> in XSLT 3 or spelled out in XSLT 2 as
<xsl:template match="#* | node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="#* | node()" mode="#current"/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/gWEaSuS

Related

Forward all param from xsl:template to another

Is it possible to forward all param received in a template to another without knowing them ?
Example :
<xsl:template match="foo">
<xsl:apply-templates select="bar">
<xsl:with-param name="father-id" select="#id"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" priority="9">
<!-- do some things -->
<xsl:next-match/>
</xsl:template>
<xsl:template match="bar">
<xsl:param name="father-id"/>
<!-- do some things with my param -->
</xsl:template>
Here my param father-id is lost because of my xsl:template match="*".
So, is there a way to forward it at the <xsl:next-match /> step but not using the following code because there can be more cases than this one and with different params ?
<xsl:template match="*" priority="9">
<xsl:param name="father-id"/>
<!-- do some things -->
<xsl:next-match>
<xsl:with-param name="father-id" select="{father-id}"/>
</xsl:next-match>
</xsl:template>
Thank you in advance.
Gerald.
Try it this way:
<xsl:template match="foo">
<xsl:apply-templates select="bar">
<xsl:with-param name="father-id" select="#id" tunnel="yes"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" priority="9">
<!-- do some things -->
<xsl:next-match/>
</xsl:template>
<xsl:template match="bar">
<xsl:param name="father-id" tunnel="yes"/>
<!-- do some things with my param -->
</xsl:template>
XSLT 2.0 only - but then so is xsl:next-match.
http://www.w3.org/TR/xslt20/#tunnel-params

XSLT Gouping, for-each and implicated loop. How to eliminate duplicates?

Hope find a guru's help to figure out the next problem.
I have two xml files. Firts one here (text.xml):
<text>
<ref>Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref>Author2, Title2, Date2</ref>
<ref>Author3, Title3, Date3</ref>
<text>
And the second one like this (list.xml):
<list>
<bibl xml:id="1"><author>Author1</author><date>Date1</date></bibl>
<bibl xml:id="2"><author>Author2</author><date>Date2</date></bibl>
<bibl xml:id="3"><author>Author3</author><date>Date3</date></bibl>
</list>
I want to query text.xml and check against list.xml to add #xml:id (from list.xml) to <ref> (from text.xml) wich contain same Author and Date. If not, then just copy original <ref>.
So I want to obtain:
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2>Author2, Title2, Date2</ref>
etc.
My XSLT identify well all correpondence:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<ref>
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
<xsl:value-of select="$ref"/>
</ref>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$ref"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
But, then there aren't correpondence it's not just copy right <ref>, but copy all <ref> the number of time I have <bibl> nodes in the second file.
So problem is in <xsl:otherwise><xsl:copy-of select="$ref"/></xsl:otherwise>.
Any ideas how I can obtain only this distinct value I need? I know it's must be very simple actually and I try key, generate-id, for-each-group, distinct-values, but can't figure it out.
The problem is that you are creating a ref element for each iteration of the for-each loop whether there is a match or not.
What you need to do in this case is create the ref element outside of the for-each and then only create the id attribute for the matching element inside the loop
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<text>
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2">Author2, Title2, Date2</ref>
<ref xml:id="3">Author3, Title3, Date3</ref>
</text>
However, your current method is not very efficient, as for each ref element you are iterating over all bibl elements. Another approach would be to extract the author and date from the ref elements, and then look up the bibl element directly
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="author" select="normalize-space(substring-before(., ','))"/>
<xsl:variable name="date" select="normalize-space(substring-after(substring-after(., ','), ','))"/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:apply-templates select="document('list.xml')//bibl[author=$author][date=$date]"/>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
<xsl:template match="bibl">
<xsl:attribute name="xml:id">
<xsl:value-of select="#xml:id"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This should also give the same results.
EDIT:
humm...too much focus on xsl syntax, i should have seen that earlier...
you have an implicated outer loop over each ref and an inner loop over each bibl. You generate one element for every bibl for every ref regardless of match or no match.
So, instead of the xsl:otherwise you need a check after the for-each loop to see if there was no match and do the copy-of if neccessary.
Not sure how to do the check, though..maybe using position() and count() of the generated <ref>s, sorry...don't have any more time to think about this right now.
not really an explanation, but a workaround:
<xsl:otherwise>
<ref>
<xsl:value-of select="$ref"/>
</ref>
</xsl:otherwise>
My guess is that the problem lies in $ref, which is not a xpath expression at that moment (if i remember xsl-t correctly)

XSL Transformation - Insert Attribute

I'm very much a beginner with xsl transformations
I have some xml that I need to insert an attribute into an element when that attribute doesn't exist..
Using the below xml as an example.
<Order Id="IR1598756" Status="2">
<Details>
<SomeInfo>Sample Data</SomeInfo>
</Details>
<Documents>
<Invoice>
<Date>15-02-2011</Date>
<Time>11:22</Time>
<Employee Id="159">James Morrison</Employee>
</Invoice>
<DeliveryNote>
<Reference>DN1235588</Reference>
<HoldingRef>HR1598785</HoldingRef>
<Date>16-02-2011</Date>
<Time>15:00</Time>
<Employee Id="25">Javi Cortez</Employee>
</DeliveryNote>
</Documents>
</Order>
Desired Output
<Order Id="IR1598756" Status="2">
<Details>
<SomeInfo>Sample Data</SomeInfo>
</Details>
<Documents>
<Invoice Id="DN1235588">
<Date>15-02-2011</Date>
<Time>11:22</Time>
<Employee Id="159">James Morrison</Employee>
</Invoice>
</Documents>
</Order>
The <Invoice> element can have an Id attribute <Invoice Id="IR1564897">
How can I check the following.
Check that the attribute exists
If not then Insert the Value of the <Refernce>DN1235588</Reference> as the Id
If there is no <Reference> Use the Value of the <HoldingRef>HR1598785</HoldingRef>
I was looking at implementing something like the following
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//Order"/>
</xsl:template>
<xsl:template match="Order/Documents/Invoice[not(#Id)]">
<xsl:attribute name="Id">
<xsl:value-of select="//Documents/DeliveryNote/Reference"/>
</xsl:attribute>
</xsl:template>
The above is not outputting the full <Invoice> element.
How can I correct this?
<xsl:if test="Order/Documents/DeliveryNote/Reference">
<xsl:value-of select="//Documents/DeliveryNote/Reference"/>
</xsl:if>
<xsl:if test="Not(Order/Documents/DeliveryNote/Reference)">
<xsl:value-of select="//Documents/DeliveryNote/HoldingRef"/>
</xsl:if>
If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?
With the help of Alex:
The following has worked for me to replace the attribute
<xsl:template match="Order/Documents/Invoice[not(#Id)]">
<Invoice>
<xsl:attribute name="Id">
<xsl:value-of Select="//Documents/DeliveryNote/Reference"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</Invoice>
</xsl:template>
The shortest answer:
<xsl:template match="Invoice[not(#Id)]">
<Invoice Id="{(../DeliveryNote/Reference|
../DeliveryNote/HoldingRef)[1]}">
<xsl:apply-templates select="#* | node()"/>
</Invoice>
</xsl:template>
Give a try:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="//Order"/>
</xsl:template>
<!-- Match invoices without ids -->
<!-- [DeliveryNote[Reference or HoldingRef] - defend against empty attributes -->
<xsl:template match="Invoice[not(#id)][DeliveryNote[Reference or HoldingRef]]">
<xsl:copy>
<!-- create an attribute and fetch required data. In case Reference is present then insert reference
otherwise - HoldingRef -->
<xsl:attribute name="Id">
<xsl:value-of select="following-sibling::DeliveryNote[1]/Reference |
following-sibling::DeliveryNote[1]/HoldingRef"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//DeliveryNote"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
As for your questions:
The above is not outputting the full element. How can I correct this?
See my example.
If either one will always exist will this work to alternate between <Reference> and <HoldingRef>?
Either with XPath (as in my example) or with xsl:choose.
What about this?
<xsl:template match="Invoice[not(#Id)]">
<xsl:element name="Invoice">
<xsl:attribute name="Id">
<xsl:variable name="REF" select="../DeliveryNote/Reference"/>
<xsl:choose>
<xsl:when test="not($REF)">
<xsl:value-of select="../DeliveryNote/HoldingRef"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="../DeliveryNote/Reference"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
Use it instead your <xsl:template match="Order/Documents/Invoice[not(#Id)]">

XSLT deepening content structure

given the following structure:
<TITEL1>...</TITEL1>
<p>..</p>
<TITEL2>...</TITEL2>
<TITEL3>...</TITEL3>
<TITEL3>...</TITEL3>
<P>...<P>
is there a way to get to this:
<TITEL1>
<TITEL>...</TITEL>
<p>...</p>
<TITEL2>
<TITEL>...</TITEL>
<TITEL3>
<TITEL>...</TITEL>
<P>...</P>
</TITEL3>
<TITEL3>
<TITEL>...</TITEL>
<P>...</P>
</TITEL3>
</TITEL2>
</TITEL1>
or in other words,is there a way to have higher level titels inclose lower level titels and all content that follows them, thus creating a nested structure. The content of each TITEL1,2 and 3 tag should go into a new <TITEL>-element
With XSLT 2.0 (as implemented by Saxon 9 or AltovaXML tools) you can use xsl:for-each-group group-starting-with and a recursive function:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/2010/mf"
exclude-result-prefixes="xsd mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="level" as="xsd:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="*[starts-with(local-name(), concat('TITEL', $level))]">
<xsl:choose>
<xsl:when test="self::*[starts-with(local-name(), concat('TITEL', $level))]">
<xsl:element name="TITEL{$level}">
<xsl:apply-templates select="."/>
<xsl:sequence select="mf:nest(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="ROOT">
<xsl:sequence select="mf:nest(*, 1)"/>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]">
<TITEL>
<xsl:apply-templates select="#* | node()"/>
</TITEL>
</xsl:template>
</xsl:stylesheet>
With that stylesheet the input
<ROOT>
<TITEL1>Titel 1, 1</TITEL1>
<p>..</p>
<TITEL2>Titel 2, 1</TITEL2>
<TITEL3>Titel 3, 1</TITEL3>
<TITEL3>Titel 3, 2</TITEL3>
<P>...</P>
</ROOT>
is transformed to the output
<TITEL1>
<TITEL>Titel 1, 1</TITEL>
<p>..</p>
<TITEL2>
<TITEL>Titel 2, 1</TITEL>
<TITEL3>
<TITEL>Titel 3, 1</TITEL>
</TITEL3>
<TITEL3>
<TITEL>Titel 3, 2</TITEL>
<P>...</P>
</TITEL3>
</TITEL2>
</TITEL1>
There isn't a particularly eligant way of doing what you want. It's (probably) possible, but it would involve some pretty ugly (and slow) XPath queries using the following-sibling axis with filters on the preceding-sibling axis matching back to the current node.
If it's at all a possibility, I would recommend creating the hierarchy outside of XSLT (in C#, Java, etc)
If you choose to go down the scary path, you would be looking to do something like this (untested):
<xsl:template match="TITEL1">
<TITEL1>
<xsl:apply-templates
select="following-sibling::(p|TITEL2)[(preceding-sibling::TITEL1)[1]=.]" />
</TITEL1>
</xsl:template>
<xsl:template match="TITEL2">
<TITEL1>
<xsl:apply-templates
select="following-sibling::(p|TITEL3)[(preceding-sibling::TITEL2)[1]=.]" />
</TITEL1>
</xsl:template>
...
This is only an example, and I can already see problems with the match. Coming up with the final XPath query would be quite involved, if it's actually possible at all.
If you can't use XSLT 2.0, here is an XSLT 1.0 stylesheet that should produce the same result as the XSLT 2.0 stylesheet I posted earlier:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="ROOT">
<xsl:apply-templates select="*[1]" mode="nest">
<xsl:with-param name="level" select="1"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]" mode="nest">
<xsl:param name="level"/>
<xsl:choose>
<xsl:when test="$level = substring-after(local-name(), 'TITEL')">
<xsl:element name="TITEL{$level}">
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[starts-with(local-name(), concat('TITEL', $level))][1]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="." mode="nest">
<xsl:with-param name="level" select="$level + 1"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*[not(starts-with(local-name(), 'TITEL'))]" mode="nest">
<xsl:param name="level"/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::*[1][not(starts-with(local-name(), concat('TITEL', $level)))]" mode="nest">
<xsl:with-param name="level" select="$level"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'TITEL')]">
<TITEL>
<xsl:apply-templates select="#* | node()"/>
</TITEL>
</xsl:template>
</xsl:stylesheet>

XSLT - adding tags to a text() node

I am trying to implement a text filter that adds a parent node to each text node.
<xsl:template match="text()">
<aNewTag><xsl:value-of select="."/></aNewTag>
</xsl:template>
This works fine until when I call it indirectly by:
<xsl:apply-templates/>
But if I call the template directly using
<xsl:apply-templates select="text()"/>
the new tag disappears.
Can anyone explain me why?
Cheers
Jan
I was a bit confused by my own code. The complete example looks like this:
<xsl:template match="/">
<xsl:call-template name="a">
<xsl:with-param name="b">
<xsl:apply-templates select="text()"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="a">
<xsl:param name="b"/>
<xsl:value-of select="$b"/> <!-- here is my error -->
</xsl:template>
<xsl:template match="text()">
<aNewTag>
<xsl:value-of select="."/>
</aNewTag>
</xsl:template>
My error was, that I have not seen the value-of in the calling template. If I change the value-of to a apply-templates, everything works fine.
Thanks
Jan
If you use the xal:apply-templates element without a select attribute, the value of select is implicitly set to node() i.e. all the child nodes and hence your text() template is matched.
I think the issue is that in template "a" that the parameter "b" is a node set. To access this, you may have to use an "node-set" extension function in the XSL. It is not part of standard XSLT, so you need to specify an extension.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="urn:schemas-microsoft-com:xslt">
<xsl:template match="/">
<xsl:call-template name="a">
<xsl:with-param name="b">
<xsl:apply-templates select="text()"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="a">
<xsl:param name="b"/>
<xsl:for-each select="ext:node-set($b)">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()">
<aNewTag>
<xsl:value-of select="."/>
</aNewTag>
</xsl:template>
</xsl:stylesheet>
This one only works for Microsoft's XML parser (MSXML) only. For other XML processors, such as xsltproc, the namespace "http://exslt.org/common" should be used.
This then allows you to access the node, or nodes, that make up the "b" parameter, although in my example above I have used to iterate over them.
Here is an article which explains about the node-set
XML.Com Article