Add attributes from to node to parent - xslt

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>

Related

Grouping 3 elements into a node

I have the following xml where a the xml element name have a number in it..
<Root>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>221</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>222</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>321</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>322</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
<Row>
<Coverage>Full</Coverage>
<Admission>Self</Admission>
<Sequence1>1</Sequence1>
<Qualifier1>421</Qualifier1>
<Date1>2017-06-01</Date1>
<Sequence2>2</Sequence2>
<Qualifier2>422</Qualifier2>
<Date2>2022-05-06</Date2>
</Row>
</Root>
I would like to group Sequence, Qualifier and Date into a group node called Benefit like below. Also, Based on the first element "Coverage" value, should be merged. XML output should as below.
<Root>
<Row>
<Coverage>Partial</Coverage>
<Admission>Self</Admission>
<Benefits>
<Benefit>
<Sequence>1</Sequence>
<Qualifier>221</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>2</Sequence>
<Qualifier>222<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
<Benefit>
<Sequence>3</Sequence>
<Qualifier>321</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>4</Sequence>
<Qualifier>322<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
</Benefits>
</Row>
<Row>
<Coverage>Full</Coverage>
<Admission>Self</Admission>
<Benefits>
<Benefit>
<Sequence>1</Sequence>
<Qualifier>421</Qualifier>
<Date>2017-06-01</Date>
</Benefit>
<Benefit>
<Sequence>2</Sequence>
<Qualifier>422<Qualifier>
<Date>2022-05-06</Date>
<Benefit>
</Benefits>
</Row>
</Root>
Any help is greatly appreciated.
Here's a simple method, based on the assumption that the 3 elements to be grouped will always come in a contiguous block that starts with a SequenceX:
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="/Row">
<xsl:copy>
<xsl:copy-of select="Coverage | Admission"/>
<Benefits>
<xsl:for-each select="*[starts-with(name(), 'Sequence')]">
<Benefit>
<xsl:for-each select=". | following-sibling::*[position() < 3]">
<xsl:element name="{translate(name(), '0123456789', '')}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</Benefit>
</xsl:for-each>
</Benefits>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If the above assumption is not true, you can perform actual grouping based on the number included in the element names:
XSLT 2.0
<xsl:stylesheet version="2.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="/Row">
<xsl:copy>
<xsl:copy-of select="Coverage | Admission"/>
<Benefits>
<xsl:for-each-group select="*[matches(name(), '^Sequence|^Qualifier|^Date')]" group-by="replace(name(), '\D', '')">
<Benefit>
<xsl:for-each select="current-group()">
<xsl:element name="{replace(name(), '\d', '')}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</Benefit>
</xsl:for-each-group>
</Benefits>
</xsl:copy>
</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.

Grouping and moving remaining nodes using XSLT 1.0

I have the following xml,
<?xml version="1.0" encoding="UTF-8"?>
<response>
<case>
<CMEDIA>Phone</CMEDIA>
</case>
<results>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject1</OBJECTID>
</row>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject2</OBJECTID>
</row>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject3</OBJECTID>
</row>
<row>
<IKEY>TestKey4</IKEY>
<OBJECTID>TestObject4</OBJECTID>
</row>
</results>
</response>
My requirement is to group all the matching <IKEY> rows and move them under one <row> and moving all <OBJECTID> nodes under that new <row>.
<?xml version="1.0" encoding="UTF-8"?>
<response>
<case>
<CMEDIA>Phone</CMEDIA>
</case>
<results>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject1</OBJECTID>
<OBJECTID>TestObject2</OBJECTID>
<OBJECTID>TestObject3</OBJECTID>
</row>
<row>
<IKEY>TestKey4</IKEY>
<OBJECTID>TestObject4</OBJECTID>
</row>
</results>
</response>
I am trying with the following xsl for grouping based on <IKEY>, but I am not able to move all <OBJECTID> nodes to new <row>(Here I have to use only XSLT 1.0).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="ikey" match="row" use="string(IKEY)" />
<xsl:template match="results">
<xsl:copy>
<xsl:apply-templates select="row[generate-id() = generate-id(key('ikey', string(IKEY))[1])]" mode="ikey" />
</xsl:copy>
</xsl:template>
<xsl:template match="row" mode="ikey">
<xsl:choose>
<xsl:when test="IKEY">
<row>
<xsl:apply-templates select="IKEY|OBJECTID" />
</row>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Can somebody tell me what I am missing here?
Change
<xsl:apply-templates select="IKEY|OBJECTID" />
to
<xsl:apply-templates select="IKEY|key('ikey', IKEY)/OBJECTID" />

How to pick up rows from XML for processing

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>

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>