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
Related
In the below xml, I would like to ignore the third node <Data1>12347</Data1> because I have to account for only the first unique occurrence of refCode. Since the first node value and third node value of refCode is 112233, I want to ignore the third node.
<?xml version="1.0" encoding="UTF-8"?>
<Example>
<Row>
<Data1>12345</Data1>
<Data2>
<DataRef>
<RefName>refCode</RefName>
<RefValue>112233</RefValue>
</DataRef>
<DataRef>
<RefName>SKU</RefName>
<RefValue>444-1112</RefValue>
</DataRef>
</Data2>
</Row>
<Row>
<Data1>12346</Data1>
<Data2>
<DataRef>
<RefName>refCode</RefName>
<RefValue>325325</RefValue>
</DataRef>
<DataRef>
<RefName>SKU</RefName>
<RefValue>444-1113</RefValue>
</DataRef>
</Data2>
</Row>
<Row>
<Data1>12347</Data1>
<Data2>
<DataRef>
<RefName>refCode</RefName>
<RefValue>112233</RefValue>
</DataRef>
<DataRef>
<RefName>SKU</RefName>
<RefValue>444-1114</RefValue>
</DataRef>
</Data2>
</Row>
</Example>
For XSLT-1.0 use an XSLT with a distinct value principle:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="distinct" match="Row" use="Data2/DataRef/RefValue" />
<!-- identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- matches unique Row elements -->
<xsl:template match="Row[generate-id(key('distinct',Data2/DataRef/RefValue)) = generate-id()]">
<xsl:copy-of select="."/>
</xsl:template>
<!-- ignores doublettes -->
<xsl:template match="Row" />
</xsl:stylesheet>
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>
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.
If I have xml that looks like this
<Msg>
<Payload role="s">
<row>
<venue>XDM</venue>
<account>60190</account>
</row>
</Payload>
<Payload role="c" id="atom1">
<ResultSet>
<Row>
<U_LegAcc>XDM60190</U_LegAcc>
<U_AccCod>SYS00000000508</U_AccCod>
</Row>
</ResultSet>
</Payload>
</Msg>
I need to get the U_AccCod node value based on the U_LegAcc which is matched to the concatenated value of venue (XDM) & account (61090) i.e. XDM61090
How can I get xml that looks like this.
<Msg>
<Payload>
<row>
<venue>XDM</venue>
<account>60190</account>
<U_AccCod>SYS00000000508</U_AccCod>
</row>
</Payload>
</Msg>
I have tried simplifying it and removing the concatenation just to get a start but I can't even get that to work, i.e. <account> and <U_AccCod> are the same. I have tried using a key but I am not getting any output
<xsl:key name="sapaccount" match="ResultSet" use="U_LegAcc" />
<xsl:template match="Row" mode="name">
<xsl:value-of select="U_AccCod" />
</xsl:template>
<xsl:template match="row/account">
<xsl:apply-templates select="key('sapaccount', .)" mode="name" />
</xsl:template>
There is actually no reason why you can't continue to use the key here. They are generally more efficient to use to look up elements. The problem with your current key is that is not quite correct. You are currently looking for ResultSet elements by means of the U_LegAcc value, but U_LegAcc is not a direct child of ResultSet, but of Row, so you probably want to define your key like so:
<xsl:key name="sapaccount" match="ResultSet/Row" use="U_LegAcc" />
Or maybe just this, if Row elements can only occur in one place
<xsl:key name="sapaccount" match="Row" use="U_LegAcc" />
Then, to look up the value, if positioned on a account element, you would do this:
<xsl:apply-templates select="key('sapaccount', concat(preceding-sibling::venue, .))" />
Or better still, have a template to match the row element, and then you can do this
<xsl:apply-templates select="key('sapaccount', concat(venue, account))" />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="sapaccount" match="Row" use="U_LegAcc"/>
<xsl:template match="Payload[#role='s']">
<Payload>
<xsl:apply-templates/>
</Payload>
</xsl:template>
<xsl:template match="Payload[#role='c']"/>
<xsl:template match="row">
<row>
<xsl:apply-templates/>
<xsl:apply-templates select="key('sapaccount', concat(venue, account))/U_AccCod"/>
</row>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Msg>
<Payload>
<row>
<venue>XDM</venue>
<account>60190</account>
<U_AccCod>SYS00000000508</U_AccCod>
</row>
</Payload>
</Msg>
In the part of your XSL which is emitting the row element from the same element in the input:
<xsl:template match="row">
<row>
<xsl:copy-of select="venue"/>
<xsl:copy-of select="account"/>
<U_AccCod>
<xsl:variable name="this" select="."/>
<xsl:value-of select="/Msg/Payload/ResultSet/Row/U_AccCod[../U_LegAcc=concat($this/venue,$this/account)]"/>
</U_AccCod>
</row>
</xsl:template>
Untested, may need tweaking.
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>