Insert element at specific point in output document during XSLT transformation - xslt

I am wondering if you can access the result-document during processing.
The reason I ask is that I am transforming an input document and would like to insert elements depending on some conditions but this would have to occur when I have traversed the tree and I am nearly at end of creating it.
The transformed xml looks something similar to this:
<xform>
<xforms>
<model>
<instance>
<data />
<data />
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
I intend, during transformation (before the above xml is serialized), to access the <instance> tag and insert additional <data> elements.
Note
The input document is different from the above xml - the above xml is what the transformation should produce.
Similarly, I would want to access the <xform> element and insert additional <bind> nodes.
So the final document would look like this (assuming I added 2 data nodes and 2 bind nodes):
<xform>
<xforms>
<model>
<instance>
<data />
<data />
<data>new data node</data>
<data>second new data node</data>
</instance>
</model>
<bind />
<bind />
<bind />
<bind>new bind node</bind>
<bind>second new bind node</bind>
</xforms>
</xform>
Any help is appreciated.

No, you can't access a result-document, you can however create temporary trees in variables and then process them again, if needed with templates with a different mode. So instead of e.g.
<xsl:template match="/">
<xsl:result-document href="example.xml">
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
</xsl:result-document>
</xsl:template>
you would create the first result in a variable and then process it further as in e.g.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="temp1">
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
</xsl:variable>
<xsl:result-document href="example.xml">
<xsl:apply-templates select="$temp1/*"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="instance">
<xsl:copy>
<xsl:apply-templates/>
<data>...</data>
</xsl:copy>
</xsl:template>
That sample does not use modes but I often use them with variables and different processing steps to cleanly seperate the templates for each step from other steps.

Yes, the way to do this is with multi-pass processing:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" mode="#default pass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates select="$vPass1/node()" mode="pass2"/>
</xsl:template>
<xsl:template match="instance" mode="pass2">
<instance>
<xsl:apply-templates mode="pass2"/>
<data>2</data>
<data>3</data>
</instance>
</xsl:template>
<xsl:template match="model" mode="pass2">
<model>
<xsl:apply-templates mode="pass2"/>
<bind>1</bind>
<bind>2</bind>
<bind>3</bind>
</model>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<xform>
<xforms>
<model>
<instance>
<data>
</data>
</instance>
</model>
<bind />
<bind />
<bind />
</xforms>
</xform>
it transforms it to itself using the identity rule and the result of this first pass is captured in the variable $vPass1. Then the second pass processes the current results in $vPass1 and adds two new data children under the instance element and three bind children under the model element -- so the final result is:
<xform>
<xforms>
<model>
<instance>
<data/>
<data>2</data>
<data>3</data>
</instance>
<bind>1</bind>
<bind>2</bind>
<bind>3</bind>
</model>
<bind/>
<bind/>
<bind/>
</xforms>
</xform>

Related

xsl to move node under precibling parent based on matching of parent attribute

I'm trying to handle a xml converted from a pdf to another xml file in some format. First I want to move / group some text / node together based on the geometry of the text but failed to do so. The following is my input & what I wanted:
input xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5.</Text>
</Box>
<Box llx="81.84" lly="560.64" urx="194.39" ury="571.68">
<Text>Equipment list</Text>
</Box>
<Box llx="257.40" lly="560.64" urx="265.36" ury="571.68">
<Text>C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
Output xml:
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
<Page>
same structure as above...
</Page>
</Pages>
What i have:
<xsl:template match="#*|node()" name = "identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box">
<xsl:choose>
<xsl:when test="#ury = following-sibling::Box/#ury">
<xsl:call-template name="identity"/>
<xsl:apply-templates select ="#*"/>
<xsl:copy-of select="following-sibling::Box/Text"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
1.It doesn't copy the wanted nodes 2. i don't know how to exclude the following nodes. I hope someone can help me on this. Many thanks in advance.
I tried the following to exclude the duplicates but it doesn't copy what i want anyways:
<xsl:template match="Box[#ury != preceding-sibling::Box/#ury]/Text">
<xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
This is a case of muenchian grouping in which you need to group the nodes based on certain common criteria and process them to provide an output.
Based on the version of XSLT being used, the solution differs for XSLT 1.0 and XSLT 2.0
XSLT 1.0
Version 1.0 uses a <xsl:key> to group the elements based on common criteria. In this case, the grouping is being done based on the value of attribute #ury so we define a key
<xsl:key name="groupingKey" match="Box" use="#ury" />
Using this key, the templates are grouped together for processing.
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
Finally within the grouped elements, a loop is run over the <Text> elements to concatenate its values.
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
Below is the complete XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="groupingKey" match="Box" use="#ury" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Box[generate-id() = generate-id(key('groupingKey', #ury)[1])]">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="key('groupingKey', #ury)/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:template>
<xsl:template match="Box" />
</xsl:stylesheet>
XSLT 2.0
Version 2.0 is advanced and provides a simpler approach as compared to XSLT 1.0. The <xsl:for-each-group> and group-by feature can be used to group the elements together.
<xsl:for-each-group select="Box" group-by="#ury">
Below is the complete XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Page">
<xsl:copy>
<xsl:apply-templates select="PAGENUMBER" />
<xsl:for-each-group select="Box" group-by="#ury">
<xsl:copy>
<xsl:apply-templates select="#*" />
<Text>
<xsl:variable name="fullText">
<xsl:for-each select="current-group()/Text">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="normalize-space($fullText)" />
</Text>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Both the XSLT provide the required output
<Pages>
<Page>
<PAGENUMBER>1</PAGENUMBER>
<Box llx="59.40" lly="560.64" urx="68.58" ury="571.68">
<Text>5. Equipment list C</Text>
</Box>
<Box llx="315.84" lly="535.32" urx="325.63" ury="546.36">
<Text>a)</Text>
</Box>
</Page>
</Pages>

Creating taxonomic structure from individual records

I am having a failure of imagination in how to effectively solve this problem. My actual data set has thousands of thousands of records. Each record indicates its location in a taxonomic structure. I need to create that taxonomic structure and place the records within that structure (e.g., records that indicate they go in "/a/b/c" end up in the "/a/b/c" but there is only one each of the taxonomic levels "a", "b", and "c"). Due to client confidentiality, I've posted a naive representation of this here.
Using XSLT 3 is desirable. I know there is a solution to this using xsl:iterate but I cannot figure it out.
Input:
<outer>
<record>
<id>rec1</id>
<taxNodes>
<node>
<id>1</id>
<note>First level</note>
<node>
<id>node2a</id>
<note>Second level Entry A</note>
</node>
</node>
</taxNodes>
</record>
<record>
<id>rec3</id>
<taxNodes>
<node>
<id>1</id>
<note>First level</note>
<node>
<id>node2b</id>
<note>Second level Entry B</note>
</node>
</node>
</taxNodes>
</record>
<record>
<id>rec4</id>
<taxNodes>
<node>
<id>1</id>
<note>First level</note>
<node>
<id>node2b</id>
<note>Second level Entry B</note>
</node>
</node>
</taxNodes>
</record>
</outer>
Desired Output:
<outer>
<node>
<id>1</id>
<note>First level</note>
<node>
<id>node2a</id>
<note>Second level Entry A</note>
<records>
<record>
<id>rec1</id>
</record>
</records>
</node>
<node>
<id>node2b</id>
<note>Second level Entry B</note>
<records>
<record>
<id>rec3</id>
</record>
<record>
<id>rec4</id>
</record>
</records>
</node>
</node>
</outer>
I think this can be seen as a grouping problem and then solved using a recursive function using xsl:for-each-group:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs math mf"
version="3.0">
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="input-nodes" as="element(node)*"/>
<xsl:for-each-group select="$input-nodes" group-by="id">
<xsl:copy>
<xsl:copy-of select="id, note"/>
<xsl:choose>
<xsl:when test="current-group()/node">
<xsl:sequence select="mf:group(current-group()/node)"/>
</xsl:when>
<xsl:otherwise>
<records>
<xsl:apply-templates select="current-group()/ancestor::record"/>
</records>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="outer">
<xsl:copy>
<xsl:sequence select="mf:group(record/taxNodes/node)"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record">
<xsl:copy>
<xsl:copy-of select="id"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That gives the desired result I think for the input sample you have posted, I am not sure it will do for other inputs, mainly as I am not sure how variable the input can be, I think if I understand the spec 'there is only one each of the taxonomic levels "a", "b", and "c"' correctly then it should work fine.
As for having a huge input file and using XSLT 3.0 (with streaming?), I am not sure that a streaming solution is possible, due to the nature of the problem where we need to recursively group the whole set of input nodes.
This stylesheet provides a solution but does so using a function for looping.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:local="http://www.local.com"
exclude-result-prefixes="xs local"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="maxTaxonomyDepth" select="max(//node[not(node)]/count(ancestor-or-self::node))" as="xs:integer"/>
<xsl:sequence select="local:getTaxNodesForDepth(outer, 1, $maxTaxonomyDepth, '')" />
</xsl:template>
<xsl:function name="local:getTaxNodesForDepth">
<xsl:param name="out" as="element()" />
<xsl:param name="curDepth" as="xs:integer" />
<xsl:param name="maxDepth" as="xs:integer" />
<xsl:param name="parentId" as="xs:string*" />
<xsl:for-each select="distinct-values($out/record/taxNodes//node[count(ancestor-or-self::node) = $curDepth]
[if ($curDepth > 1) then parent::node/id/normalize-space(.) = $parentId else true()]
/id/normalize-space(.))">
<xsl:variable name="context" select="." as="xs:string" />
<node>
<xsl:sequence select="($out/record/taxNodes//node[count(ancestor-or-self::node) = $curDepth][id/normalize-space(.) = $context])[1]/(id | descriptor)" />
<xsl:apply-templates select="$out/record[taxNodes/descendant::node[last()][id/normalize-space(.) = $context]]" />
<xsl:choose>
<xsl:when test="$curDepth < $maxDepth">
<xsl:sequence select="local:getTaxNodesForDepth($out, $curDepth + 1, $maxDepth,
($out/record/taxNodes//node[count(ancestor-or-self::node) = $curDepth][id/normalize-space(.) = $context])[1]/id/normalize-space(.))" />
</xsl:when>
</xsl:choose>
</node>
</xsl:for-each>
</xsl:function>
<xsl:template match="taxNodes"/>
<xsl:template match="#*|node()" mode="#default">
<xsl:copy>
<xsl:apply-templates select="#*" mode="#current"/>
<xsl:apply-templates select="node()" mode="#current"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 transformation

I'm trying to transform a XML file with XSLT 1.0 but I'm having troubles with this.
Input:
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4</id>
<name>test</name>
</task>
</task_order>
Wanted result:
For each task_order element: When there is more than 1 record-element (SPLIT4 and SPLIT4_1) I need to duplicate the task element and change the orginal task-id with the id from record elements.
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4_0</id>
<name>test</name>
</task>
<task>
<id>SPLIT4_1</id>
<name>test</name>
</task>
</task_order>
Any suggestions?
Thank you
First start off with the Identity Template which will handle copying across all existing nodes
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Next, to make looking up the columns slightly easier, consider using an xsl:key
<xsl:key name="column" match="column" use="substring-before(., '_')" />
Then, you have a template matching task where you can look up all matching column elements using the key, and create a new task element for each
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<!-- Create new task -->
</xsl:for-each>
</xsl:template>
Try this XSTL
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="column" match="column" use="substring-before(., '_')" />
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<task>
<id><xsl:value-of select="." /></id>
<xsl:apply-templates select="$task/*[not(self::id)]" />
</task>
</xsl:for-each>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Add parent element and rename the child element

I am trying to loop child elements under import and export. Create a parent element fields and put all the elements which the name is field into it, just under import, not include the elements field under structure, Create element structures over all elements structure, and the last work is rename all the element with the value of attribute name. I just know copy all of them first and can not go to next step to create the right template.
input:
<?xml version='1.0'?>
<jco name="TEST" timestamp="1275691115508" version="1.0">
<import>
<field name="RESERVED_IN">12345</field>
<structure name="GM_HEADER">
<field name="PSTNG_DATE">2004-07-02</field>
<field name="DOC_DATE">2004-04-02</field>
</structure>
<structure name="TESTRUN">
<field name="TESTRUN"></field>
</structure>
</import>
<export>
<field name="RESERVED_OUT"></field>
<structure name="GM_HEADER_RET">
<field name="MAT_DOC"></field>
<field name="DOC_YEAR">0000</field>
</structure>
</export>
</jco>
desired output:
<?xml version="1.0" ?>
<jco version="1.0" name="TEST">
<import>
<fields>
<RESERVED_IN>12345</RESERVED_IN>
</fields>
<structures>
<GM_HEADER>
<PSTNG_DATE>2004-07-02</PSTNG_DATE>
<DOC_DATE>2004-04-02</DOC_DATE>
</GM_HEADER>
<TESTRUN>
<TESTRUN></TESTRUN>
</TESTRUN>
</structures>
</import>
<export>
<fields>
<RESERVED_OUT></RESERVED_OUT>
</fields>
<structures>
<GM_HEADER_RET>
<MAT_DOC></MAT_DOC>
<DOC_YEAR>0000</DOC_YEAR>
</GM_HEADER_RET>
</structures>
</export>
</jco>
Below is my xslt, but it seems messed up.
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="import">
<fields>
<xsl:for-each select="/field">
<xsl:call-template name="field" />
</xsl:for-each>
</fields>
<structures>
<xsl:for-each select="/structure">
<xsl:element name="{#name}">
<xsl:value-of select="." />
</xsl:element>
<xsl:call-template name="field" />
</xsl:for-each>
</structures>
</xsl:template>
<xsl:template name="field">
<xsl:for-each select="/field">
<xsl:element name="{#name}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:template>
Use templates and apply-templates, not for-each, and it works out rather elegantly:
<xsl:stylesheet version="2.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:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="import | export">
<xsl:copy>
<fields>
<xsl:apply-templates select="field"/>
</fields>
<structures>
<xsl:apply-templates select="structure"/>
</structures>
</xsl:copy>
</xsl:template>
<xsl:template match="field | structure">
<xsl:element name="{#name}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Get a collection of the attributes of a nodeset

I have a collection of nodes like this
<node id="1">
<languaje>c</languaje>
<os>linux</os>
</node>
<node id="2">
<languaje>c++</languaje>
<os>linux</os>
</node>
<node id="3">
<languaje>c#</languaje>
<os>window</os>
</node>
<node id="4">
<languaje>basic</languaje>
<os>mac</os>
</node>
And i want to create a new collection of all the properties id's like this
<root>
<token>1</token>
<token>2</token>
<token>3</token>
<token>4</token>
</root>
How can do that
If you can use XQuery you can do it like this:
<root>
{ ($document/node/<node>{string(#id)}</node>) }
</root>
which is imho the clearest solution.
Otherwise you could create a string (not a document) containing your desired result with XPath 2 by concatenating the tags and your ids :
concat("<root>", string-join(for $i in /base/node/#id return concat("<node>",$i,"</node>"), " ") , "</root>")
All you need is
<xsl:output indent="yes"/>
<xsl:template match="*[node]">
<root>
<xsl:apply-templates select="node"/>
</root>
</xsl:template>
<xsl:template match="node">
<token><xsl:value-of select="#id"/></token>
</xsl:template>
If you want to store the result in a variable you can create a result tree fragment with XSLT 1.0 with e.g.
<xsl:variable name="rtf1">
<xsl:apply-templates select="node()" mode="m1"/>
</xsl:variable>
<xsl:template match="*[node]" mode="m1">
<root>
<xsl:apply-templates select="node" mode="m1"/>
</root>
</xsl:template>
<xsl:template match="node" mode="m1">
<token><xsl:value-of select="#id"/></token>
</xsl:template>
Then you can do <xsl:copy-of select="$rtf1"/> to use the result tree fragment, or with 'exsl:node-set` you can process the created nodes with XPath and XSLT e.g.
<xsl:apply-templates select="exsl:node-set($rtf1)/root/token"/>
With XSLT 2.0 there are no longer result tree fragments so you can use the variable like any input without the need for an extension function.
If you wrap all the nodes under a tag, like <nodes> this works:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<root>
<xsl:apply-templates select="*" />
</root>
</xsl:template>
<!-- templates -->
<xsl:template match="node">
<token><xsl:value-of select="#id" /></token>
</xsl:template>
</xsl:stylesheet>
Tested on XsltCake
http://www.xsltcake.com/slices/E937yH