How to pick up rows from XML for processing - xslt

I would like to process some XML like that:
<rows>
<row>
<country>AT</country>
<some>element</some>
</row>
<row>
<country>CZ</country>
<some>element</some>
</row>
<row>
<country>BG</country>
<some>element</some>
</row>
<row>
<country>CZ</country>
<some>element</some>
</row>
</rows>
I have to regroup rows to the target XML in this way: first must be rows with country 'CZ', then can be rows with other countries.
I can pick up rows with country 'CZ' in this way:
<xsl:key name="countries" match="row" use="country">
<xsl:for-each select="key('countries', 'CZ')">
<!-- do some transformation -->
</xsl:for-each>
But I don't know, how to pick up rows with other countries? Can I use somethink like:
<xsl:for-each select="key('countries', !'CZ')">
?
EDIT:
Expected output is:
<rows>
<row>
<country>CZ</country>
<transformed>element</transformed>
</row>
<row>
<country>CZ</country>
<transformed>element</transformed>
</row>
<row>
<country>AT</country>
<transformed>element</transformed>
</row>
<row>
<country>BG</country>
<transformed>element</transformed>
</row>
</rows>
Order of other rows (except 'CZ') is not mandatory.
I'm using XSLT 1.0, but I can use XSLT 2.0, too.

Even without xsl:key.
Select CZ rows:
<xsl:apply-templates select="//row[country='CZ']"/>
Select other rows:
<xsl:apply-templates select="//row[country!='CZ']"/>
You can use the same expressions in xsl:for-each as well.

IMHO, the most straightforward method to change the order of nodes is to sort them - for example:
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="/rows">
<xsl:copy>
<xsl:apply-templates select="row">
<xsl:sort select="number(country='CZ')" data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="some">
<transformed>
<xsl:apply-templates/>
</transformed>
</xsl:template>
</xsl:stylesheet>

Related

Add attributes from to node to parent

After reading a lot about this question already, I still do not find final solution for my problem as I am an absolut beginner with xsl.
I want to add all attributes of child nodes to parent level.
This is what I have:
<rankings date="2021-03-15">
<ranking rank="1" rank_change="0" points="12008">
<player initials="" nationality="SRB" last_name="Djokovic" first_name="Novak" id="7" display_name="Novak Djokovic"/>
</ranking>
<ranking rank="2" rank_change="1" points="9940">
<player initials="" nationality="RUS" last_name="Medvedev" first_name="Daniil" id="35844" display_name="Daniil Medvedev"/>
</ranking>
<ranking rank="3" rank_change="-1" points="9670">
<player initials="" nationality="ESP" last_name="Nadal" first_name="Rafael" id="4" display_name="Rafael Nadal"/>
</ranking>
</rankings>
This is what I tried (miss identity tranform I think)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="rankings">
<data>
<xsl:apply-templates select="*"/>
</data>
</xsl:template>
<xsl:template match="ranking | player">
<row>
<xsl:apply-templates select="#* | node()"/>
</row>
</xsl:template>
<xsl:template match="ranking/#* | player/#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
With following result:
<data>
<row>
<rank>1</rank>
<rank_change>0</rank_change>
<points>12008</points>
<row>
<initials/>
<nationality>SRB</nationality>
<last_name>Djokovic</last_name>
<first_name>Novak</first_name>
<id>7</id>
<display_name>Novak Djokovic</display_name>
</row>
</row>
</data>
This is my goal:
<data>
<row>
<rank>1</rank>
<rank_change>0</rank_change>
<points>12008</points>
<initials/>
<nationality>SRB</nationality>
<last_name>Djokovic</last_name>
<first_name>Novak</first_name>
<id>7</id>
<display_name>Novak Djokovic</display_name>
</row>
</data>
I hope one of you can help me with this.
Cheers,
Phil
try splitting ranking and player in its own template
<xsl:template match="ranking">
<row>
<xsl:apply-templates select="#* | node()"/>
</row>
</xsl:template>
<xsl:template match="player">
<xsl:apply-templates select="#* | node()"/>
</xsl:template>
Result:
<data>
<row>
<rank>1</rank>
<rank_change>0</rank_change>
<points>12008</points>
<initials/>
<nationality>SRB</nationality>
<last_name>Djokovic</last_name>
<first_name>Novak</first_name>
<id>7</id>
<display_name>Novak Djokovic</display_name>
</row>
<row>
<rank>2</rank>
<rank_change>1</rank_change>
<points>9940</points>
<initials/>
<nationality>RUS</nationality>
<last_name>Medvedev</last_name>
<first_name>Daniil</first_name>
<id>35844</id>
<display_name>Daniil Medvedev</display_name>
</row>
<row>
<rank>3</rank>
<rank_change>-1</rank_change>
<points>9670</points>
<initials/>
<nationality>ESP</nationality>
<last_name>Nadal</last_name>
<first_name>Rafael</first_name>
<id>4</id>
<display_name>Rafael Nadal</display_name>
</row>
</data>
If I am guessing correctly what your real goal is, you could do simply:
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:template match="rankings">
<data>
<xsl:for-each select="ranking">
<row>
<xsl:for-each select=".//#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</row>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>

XSLT Apply Templates to derive specific parent & children records, children can be parent too

I need help with my XSLT. Here's my XML structure.
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
<where_used>
<component>subfield_2</component>
<type>Field</type>
</where_used>
<where_used>
<component>report_1</component>
<type>Report</type>
</where_used>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
<where_used>
<component>report_2</component>
<type>report</type>
</where_used>
</row>
<row>
<component>mainfield_3</component>
<type>Field</type>
</row>
</root>
I would like it to be transformed into the following:
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
</row>
<row>
<component>report_1</component>
<type>Report</type>
</row>
<row>
<component>report_2</component>
<type>report</type>
</row>
</root>
Basically, I am trying to get all the distinct dependencies of component mainfield_1. Here's my sample code but it is not enough to find any matching parent that has the same component name as the children.
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
</xsl:template>
If I run the above, I will not be able to get this.
<row>
<component>report_2</component>
<type>report</type>
</row>
Please help.
Consider using a key to look up the row items by component
<xsl:key name="rows" match="row" use="component" />
Then you can have a template for where_used nodes that refer to a separate row, allowing you to select that row instead
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="rows" match="row" use="component" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="* except where_used" />
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<xsl:apply-templates />
</row>
</xsl:template>
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
</xsl:stylesheet>
Note I have used the identity template too, to avoid having to explicitly copy existing nodes that don't need to be changed.

How create hierarchy using xslt

I'm new to XSL and am seeking a way to solve some problem. I have xml something like:
<Table>
<Row Id="1">
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
</Row>
<Row Id="2">
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row Id="3">
<Field1>"3.1.1.M5"</Field1>
</Row>
<Row Id="4">
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
</Row>
<Row Id="5">
<Field1>"3.11.M2"</Field1>
</Row>
</Table>
Where Row Id=1 and Row Id=4 are headers of invoices and remaining rows are lines of invoices. Every invoice header has its ID in field1 but there is no invoice ID in invoice lines. I know that when there is no field3 in row, it means that row is invoice line. In other case it is invoice header. Every rows before header row belong to previous header row. How create xml with proper invoice hierarchy using xslt?
Output xml could be like:
<Invoice>
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row>
<Field1>"3.1.1.M5"</Field1>
</Row>
</Invoice>
<Invoice>
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"3.11.M2"</Field1>
</Row>
</Invoice>
I would do this using keys as following:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="Rows" match="Row[not(Field3)]" use="generate-id(preceding-sibling::Row[Field3][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[Field3]">
<Invoice>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="key('Rows', generate-id())" mode="followingRows"/>
</Invoice>
</xsl:template>
<xsl:template match="Row" mode="followingRows">
<xsl:copy><xsl:apply-templates select="node()"/></xsl:copy>
</xsl:template>
<xsl:template match="Row"/>
</xsl:stylesheet>
One solution is the following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Table">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Row[Field3]">
<xsl:variable name="invoice-count" select="count(preceding-sibling::Row[Field3]) + 1"/>
<Invoice>
<xsl:apply-templates/>
<xsl:apply-templates select="following-sibling::Row[not(Field3)
and not(count(preceding-sibling::Row[Field3]) > $invoice-count)]" mode="copy"/>
</Invoice>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row" mode="copy">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row"/>
</xsl:transform>
when applied to your input XML produces the output
<Invoice>
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row>
<Field1>"3.1.1.M5"</Field1>
</Row>
</Invoice>
<Invoice>
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"3.11.M2"</Field1>
</Row>
</Invoice>
One template matches all Row elements that contain a Field3:
<xsl:template match="Row[Field3]">
This template writes an <Invoice> node and copies the content of this Row by applying templates. Then all following silbing Row elements that have no Field3 and not more preceding sibling Row elements with Field3 than the current Row are copied by applying the template mode="copy".
This template copies the content of the Row but not the attributes, so the id of the Row will be removed from the output.
To avoid writing the Row elements twice, the empty template
<xsl:template match="Row"/> matches all Row nodes that are already handled by applying templates in the template that matches the Row elements with Field3.

EOF (End Of File) expexted error in output XML after XSL transformation

I have "EOF Expexted" error in output XML after XSL transformation.
Question: What i need to do with this XSLT to get correct output?
Input XML:
<RowSet>
<Row>
<msg_id>1</msg_id>
<doc_id>1</doc_id>
<doc_version>1</doc_version>
</Row>
<Row>
<msg_id>2</msg_id>
<doc_id>1</doc_id>
<doc_version>2</doc_version>
</Row>
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
<RowSet>
XSLT:
<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:key name="kRowByDocId" match="Row" use="doc_id"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</xsl:template>
<xsl:template match="Row">
<xsl:for-each select="key('kRowByDocId',doc_id)">
<xsl:sort select="doc_version" data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:copy-of select="."/></xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output that i'm getting now (with EOF error):
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
Correct output must be something like this:
<RowSet>
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
<RowSet>
Thank you!
This is quite simple. If you want to output a Rowset element, you just to need to output one in your template that matches the root element
<xsl:template match="/*">
<RowSet>
<xsl:apply-templates select="Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</RowSet>
</xsl:template>
Alternatively, if you don't want to hardcode the RowSet element, you can just use xsl:copy to copy whatever the root element is:
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</xsl:copy>
</xsl:template>

Combining two nodesets in XSL

I have the following XML:
<root>
<row>
<elem>Timestamp</elem>
<elem>ERB.CHW.BTU_CV</elem>
<elem>ERB.CHW.BTU1_CV</elem>
<elem>ERB.HW.BTU_CV</elem>
<elem>ERB.HW.BTU1_CV</elem>
<elem>ERB.KW.DEMAND_CV</elem>
<elem>ERB.KWH.MT_CV</elem>
<elem></elem>
</row>
<row>
<elem>2011/09/30 11:21:13.9062</elem>
<elem>2.307609E+09</elem>
<elem>1880067</elem>
<elem>1.068635E+08</elem>
<elem>1340.386</elem>
<elem>448.8</elem>
<elem>1427723</elem>
<elem></elem>
</row>
</root>
I want to alter it such that the first <row> defines new elements (via <elem>) and each non-empty <elem> that follows provides values - such as:
<root>
<row>
<Timestamp>2011/09/30 11:21:13.9062</Timestamp>
<ERB.CHW.BTU_CV>2.307609E+09</ERB.CHW.BTU_CV>
<ERB.CHW.BTU1_CV>1880067</ERB.CHW.BTU1_CV>
<ERB.HW.BTU_CV>1.068635E+08</ERB.HW.BTU_CV>
<ERB.HW.BTU1_CV>1340.386</ERB.HW.BTU1_CV>
<ERB.KW.DEMAND_CV>448.8</ERB.KW.DEMAND_CV>
<ERB.KWH.MT_CV>1427723</ERB.KWH.MT_CV>
</row>
</root>
Two things to note:
Notice how blank <elem> elements are removed from the source structure.
This should work for any number of <row> nodesets.
I feel like this should be simple, but I'm struggling to even know where to begin. Help?
EDIT: RE: #2 above, I am not looking to duplicate the initial <row> nodeset (which is used to define the new elements). Rather, the solution should work for any <row> nodeset that contains data points (i.e., if the second <row> nodeset was duplicated 5 times in a row).
This transformation:
<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:variable name="vElems" select=
"/*/row[1]/elem[normalize-space()]"/>
<xsl:template match="/*">
<root>
<xsl:apply-templates select="row[position() >1]"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="$vElems">
<xsl:with-param name="pValues"
select="elem"/>
</xsl:apply-templates>
</row>
</xsl:template>
<xsl:template match="elem">
<xsl:param name="pValues"/>
<xsl:variable name="vPos" select="position()"/>
<xsl:element name="{.}">
<xsl:value-of select="$pValues[$vPos]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (the provided plus one more data row):
<root>
<row>
<elem>Timestamp</elem>
<elem>ERB.CHW.BTU_CV</elem>
<elem>ERB.CHW.BTU1_CV</elem>
<elem>ERB.HW.BTU_CV</elem>
<elem>ERB.HW.BTU1_CV</elem>
<elem>ERB.KW.DEMAND_CV</elem>
<elem>ERB.KWH.MT_CV</elem>
<elem></elem>
</row>
<row>
<elem>2011/09/30 11:21:13.9062</elem>
<elem>2.307609E+09</elem>
<elem>1880067</elem>
<elem>1.068635E+08</elem>
<elem>1340.386</elem>
<elem>448.8</elem>
<elem>1427723</elem>
<elem></elem>
</row>
<row>
<elem>2011/09/31 11:22:33.9063</elem>
<elem>3.418609E+10</elem>
<elem>1991073</elem>
<elem>1.068635E+08</elem>
<elem>1340.386</elem>
<elem>452.5</elem>
<elem>169578</elem>
<elem></elem>
</row>
</root>
produces the wanted, correct result:
<root>
<row>
<Timestamp>2011/09/30 11:21:13.9062</Timestamp>
<ERB.CHW.BTU_CV>2.307609E+09</ERB.CHW.BTU_CV>
<ERB.CHW.BTU1_CV>1880067</ERB.CHW.BTU1_CV>
<ERB.HW.BTU_CV>1.068635E+08</ERB.HW.BTU_CV>
<ERB.HW.BTU1_CV>1340.386</ERB.HW.BTU1_CV>
<ERB.KW.DEMAND_CV>448.8</ERB.KW.DEMAND_CV>
<ERB.KWH.MT_CV>1427723</ERB.KWH.MT_CV>
</row>
<row>
<Timestamp>2011/09/31 11:22:33.9063</Timestamp>
<ERB.CHW.BTU_CV>3.418609E+10</ERB.CHW.BTU_CV>
<ERB.CHW.BTU1_CV>1991073</ERB.CHW.BTU1_CV>
<ERB.HW.BTU_CV>1.068635E+08</ERB.HW.BTU_CV>
<ERB.HW.BTU1_CV>1340.386</ERB.HW.BTU1_CV>
<ERB.KW.DEMAND_CV>452.5</ERB.KW.DEMAND_CV>
<ERB.KWH.MT_CV>169578</ERB.KWH.MT_CV>
</row>
</root>
The following stylesheet produces the requested result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- ignore even-numbered rows -->
<xsl:template match="row[position() mod 2 = 0]"/>
<!-- non-empty elem nodes of odd-numbered rows -->
<xsl:template match="elem[normalize-space()]">
<xsl:variable name="pos" select="position()"/>
<xsl:element name="{text()}">
<xsl:value-of select="../following-sibling::row[1]/elem[$pos]"/>
</xsl:element>
</xsl:template>
<xsl:template match="elem"/>
</xsl:stylesheet>
Explanation:
The Identity Transform is used to copy most nodes through unchanged
All even-numbered rows are initially skipped
When processing the (non-empty) elem elements of each odd-numbered row, we grab the value of the elem at the same position in the row's following sibling