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.
Related
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.
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 have to add one attribute Publisher="Penguin" to the nodes from a NodeList : The input xml looks like:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10"/>
<Row RowNo="2" NoOfBooks="15"/>
<Rows>
</Rack>
The output xml lookslike:
<Rack RackNo="1">
<Rows>
<Row RowNo="1" NoOfBooks="10" Publisher="Penguin"/>
<Row RowNo="2" NoOfBooks="15" Publisher="Penguin"/>
<Rows>
</Rack>
The xsl i wrote is :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<Order>
<xsl:copy-of select = "Rack/#*"/>
<xsl:for-each select="Rows/Row">
<OrderLine>
<xsl:copy-of select = "Row/#*"/>
<xsl:attribute name="Publisher"></xsl:attribute>
<xsl:copy-of select = "Row/*"/>
</OrderLine>
</xsl:for-each>
<xsl:copy-of select = "Rack/*"/>
</Order>
</xsl:template>
</xsl:stylesheet>
This doesnt return the desired output.
Any help will be much appreciated.
Thanks in advance guys.
This is a job for the XSLT identity transform. On its own it simple creates a copy of all the nodes in your input XML
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
All you need to do is add an extra template to match Row element, and add a Publisher attribute to it. It might be good to first parameterise the publisher you wish to add
<xsl:param name="publisher" select="'Penguin'" />
You then create the matching template as follows:
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</xsl:template>
Note the use of "Attribute Value Templates" to create the Publisher attribute. The curly braces indicate it is an expression to be evaluated. Also note in your XSLT it looks like you are renaming the elements too, so I have done this in my XSLT as well. (If this is not the case, simply replace OrderLine back with Row.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="publisher" select="'Penguin'" />
<xsl:template match="Rack">
<Order>
<xsl:apply-templates />
</Order>
</xsl:template>
<xsl:template match="Rows">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Row">
<OrderLine Publisher="{$publisher}">
<xsl:apply-templates select="#*|node()"/>
</OrderLine>
</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
<Order>
<OrderLine Publisher="Penguin" RowNo="1" NoOfBooks="10"></OrderLine>
<OrderLine Publisher="Penguin" RowNo="2" NoOfBooks="15"></OrderLine>
</Order>
I have DB tables stored in XML format that have a FK based on two columns (Table2 has FK to Table1 based on ID and TYPE).
Table1.xml
<Table>
<Row>
<ID>1</ID>
<TYPE>A</TYPE>
<CONFIG>Y</CONFIG>
...
</Row>
<Row>
<ID>2</ID>
<TYPE>A</TYPE>
<CONFIG>Z</CONFIG>
...
</Row>
<Row>
<ID>1</ID>
<TYPE>B</TYPE>
<CONFIG>X</CONFIG>
...
</Row>
<Row>
<ID>3</ID>
<TYPE>A</TYPE>
<CONFIG>Z</CONFIG>
...
</Row>
</Table>
Table2.xml
<Table>
<Row>
<ID>1</ID>
<TYPE>A</TYPE>
...
</Row>
<Row>
<ID>2</ID>
<TYPE>A</TYPE>
...
</Row>
<Row>
<ID>1</ID>
<TYPE>B</TYPE>
...
</Row>
<Row>
<ID>3</ID>
<TYPE>A</TYPE>
...
</Row>
</Table>
I will have two XSLT files to delete rows in each XML file. Table2 will be processed first. I want to delete the row in Table2 where when joined with Table1 CONFIG=Z (ie, delete rows where (ID=2 and Type=A) and (ID=3 and Type=A), but I need to figure this out only knowing I want to delete records where CONFIG=Z). Table1 will then be processed to delete rows where CONFIG=Z, which I was able to figure out.
I think the XSLT that will be applied to Table2 needs to read in Table1 XML (xsl:variable name="table1Rows" select="document('Table1.xml')/Table/Row"/>). After that I'm lost on how to delete rows in Table2 where CONFIG=Z. I've tried several things based on examples I saw, but couldn't get anything to work.
With XSLT 2.0 define a key and cross-reference the elements, then simply do an identity transformation to copy nodes plus a template that suppresses the copying for those Row elements where the key function call finds a Row in the other document with the CONFIG being Z:
<xsl:variable name="table1" select="doc('Table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[key('r-by-id-and-type', concat(ID, '|', TYPE), $table1)/CONFIG = 'Z']"/>
[edit] For completeness, I tested the following complete sample with both Saxon 9.4 as well as AltovaXML successfully:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:variable name="table1" select="doc('table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[key('r-by-id-and-type', concat(ID, '|', TYPE), $table1)/CONFIG = 'Z']"/>
</xsl:stylesheet>
On request in comment I also add an XSLT 1.0 stylesheet:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:variable name="table1" select="document('test2012100102.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row">
<xsl:variable name="this" select="."/>
<xsl:for-each select="$table1">
<xsl:if test="not(key('r-by-id-and-type', concat($this/ID, '|', $this/TYPE))/CONFIG = 'Z')">
<xsl:copy-of select="$this"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Martin's solution is correct, given the original question and should be accepted.
In reference to the OP's request for an additional XSLT 1.0 solution, here is a polyglot. This style-sheet, a minor variation of Martin's solution, works on XSLT 2.0 processors and probably most XSLT 1.0 processors.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:variable name="table1" select="doc('table1.xml')"/>
<xsl:key name="r-by-id-and-type" match="Table/Row" use="concat(ID, '|', TYPE)"/>
<xsl:template match="#*|node()" name="ident">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match="Row"
use-when="number(system-property('xsl:version')) < 2" priority="2">
<xsl:variable name="row" select="." />
<xsl:variable name="id-type" select="concat(ID, '|', TYPE)" />
<xsl:for-each select="$table1">
<xsl:if test="not( key('r-by-id-and-type', $id-type))">
<xsl:for-each select="$row">
<xsl:call-template name="ident" />
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template
match="Row"
use-when="number(system-property('xsl:version')) >= 2" priority="1">
<xsl:if test="not( key('r-by-id-and-type', concat(ID, '|', TYPE), $table1))">
<xsl:call-template name="ident" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Caveat
This style-sheet was not tested.
The answers provided by Martin work and are probably the best solutions possible. For XSLT 1.0 I had come up with the following that seems to run faster, but is not as elegant. For this solution I knew that the only possible TYPE for CONFIG=Z is 'A'. (Note there could be a typo below since I'm running the XSLT on another machine and retyped it here with the mock column names/values.)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="table1_Z_rows" select="document('table1.xml')/Table/Row[CONFIG='Z']"/>
<xsl:template match="Row">
<xsl:choose>
<xsl:when test="TYPE != 'A'">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="not(ID = $table1_Z_rows/ID)">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
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