Flatten nodes that have repeated child node using XSLT - xslt-1.0

I have a document of following structure (this is just an example to help me verbalize the problem), that I'm trying to flatten. By flattening I mean copying all the <Report_Entry> nodes with several <Event>s so that each <Report_Entry> node contained just a single <Event>
What I have:
<?xml version="1.0"?>
<Report_Data>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-06</Start_Date>
<End_Date>2011-09-10</End_Date>
</Event>
<Event>
<Start_Date>2011-09-10</Start_Date>
<End_Date>2011-09-15</End_Date>
</Event>
<Event>
<Start_Date>2011-09-15</Start_Date>
<End_Date>2011-09-20</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-20</Start_Date>
<End_Date>2011-09-25</End_Date>
</Event>
<Event>
<Start_Date>2011-09-25</Start_Date>
<End_Date>2011-09-30</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>3</ID>
<Event>
<Start_Date>2011-09-30</Start_Date>
<End_Date>2011-10-05</End_Date>
</Event>
</Report_Entry>
</Report_Data>
What I'm trying to get:
<?xml version="1.0"?>
<Report_Data>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-06</Start_Date>
<End_Date>2011-09-10</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-10</Start_Date>
<End_Date>2011-09-15</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-15</Start_Date>
<End_Date>2011-09-20</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-20</Start_Date>
<End_Date>2011-09-25</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-25</Start_Date>
<End_Date>2011-09-30</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>3</ID>
<Event>
<Start_Date>2011-09-30</Start_Date>
<End_Date>2011-10-05</End_Date>
</Event>
</Report_Entry>
</Report_Data>
Here is XSLT that I'm using:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Report_Entry">
<xsl:for-each select="Event">
<Report_Entry>
<xsl:copy-of select="../*[not(self::Event)]"/>
<xsl:copy-of select="."/>
</Report_Entry>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It works, though I feel that there might be a better, faster and more universal solution. In particular, I don't like "hardcoding" <Report_Entry> since this way I wouldn't be able to copy its attributes (if any). Are there other ways/templates to deal with this problem?

As simple as this:
<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:template match="/*">
<Report_Data>
<xsl:apply-templates select="*/Event"/>
</Report_Data>
</xsl:template>
<xsl:template match="Event">
<Report_Entry>
<xsl:copy-of select="../ID | ."/>
</Report_Entry>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Report_Data>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-06</Start_Date>
<End_Date>2011-09-10</End_Date>
</Event>
<Event>
<Start_Date>2011-09-10</Start_Date>
<End_Date>2011-09-15</End_Date>
</Event>
<Event>
<Start_Date>2011-09-15</Start_Date>
<End_Date>2011-09-20</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-20</Start_Date>
<End_Date>2011-09-25</End_Date>
</Event>
<Event>
<Start_Date>2011-09-25</Start_Date>
<End_Date>2011-09-30</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>3</ID>
<Event>
<Start_Date>2011-09-30</Start_Date>
<End_Date>2011-10-05</End_Date>
</Event>
</Report_Entry>
</Report_Data>
the wanted, correct result is produced:
<Report_Data>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-06</Start_Date>
<End_Date>2011-09-10</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-10</Start_Date>
<End_Date>2011-09-15</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>1</ID>
<Event>
<Start_Date>2011-09-15</Start_Date>
<End_Date>2011-09-20</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-20</Start_Date>
<End_Date>2011-09-25</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>2</ID>
<Event>
<Start_Date>2011-09-25</Start_Date>
<End_Date>2011-09-30</End_Date>
</Event>
</Report_Entry>
<Report_Entry>
<ID>3</ID>
<Event>
<Start_Date>2011-09-30</Start_Date>
<End_Date>2011-10-05</End_Date>
</Event>
</Report_Entry>
</Report_Data>

Your answer couldn't be much simpler, so no need to worry on that front. When writing code, but especially XSLT, the clarity of the code tends to be worth a lot more than its ultimate efficiency.
As for the hardcoded element name and copying attributes, here's a start:
<xsl:template match="Report_Entry">
<xsl:variable name="parent-name" select="name()"/>
<xsl:variable name="parent-attributes" select="#*"/>
<xsl:for-each select="Event">
<xsl:element name="{$parent-name}">
<xsl:copy-of select="$parent-attributes"/>
<xsl:copy-of select="../*[not(self::Event)]"/>
<xsl:copy-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:template>
variables are used to stash some of the context as it exists outside the for-each. element makes an element that's a look-alike for your original, no matter what it's called, and the first copy-of makes it more convincing by copying in the original's attributes as well. Now, if your data suddenly takes on attributes, you'll be ready.
The non-hardcodedness of the name doesn't mean much in this case, but it would if, say, you were to factor out that part to a separate template and call it from multiple places:
<xsl:template name="collapse-the-thing">
<xsl:param name="context"/>
<xsl:param name="sub-element-name" select="'Event'"/>
<xsl:variable name="parent-name" select="name($context)"/>
<xsl:variable name="parent-attributes" select="$context/#*"/>
<xsl:for-each select="$context/*[name()=$sub-element-name]">
<xsl:element name="{$parent-name}">
<xsl:copy-of select="$parent-attributes"/>
<xsl:copy-of select="../*[name()!=$sub-element-name]"/>
<xsl:copy-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="Report_Entry">
<xsl:call-template name="collapse-the-thing">
<xsl:with-param name="context" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template match="Some_Other_Entry">
<xsl:call-template name="collapse-the-thing">
<xsl:with-param name="context" select="."/>
<xsl:param name="sub-element-name" select="'Happening'"/>
</xsl:call-template>
</xsl:template>
Hope that was enlightening. Enjoy!

Related

How to remove Namespaces when copying nodes to a different node in XML through XSLT1.0?

I'm new to XSL and i'm trying to copy a node of XML to another node in the same XML using XSLT. I can able to transform the file as expected but the XMLNS attribute is getting added to the destination which I don't want.
I have tried all the option of copy-namespaces='no' using XSLT2.0 but that doesn't work. Also, I cant have a prefix of namespaces in the XSLT and use exclude namespaces to avoid xmlns because the incoming XML file is dynamic and namespaces keep on changing. I can't have all the namespaces declared as a prefix in XSLT.
Incoming XML:
<?xml version="1.0" encoding="UTF-8"?>
<PropertySet>
<Message>
<NotificationHeader>
<BusinessId></BusinessId>
<CorrelationId>0201201916:21:24CKG3N</CorrelationId>
<SourceName></SourceName>
<SourceId></SourceId>
<EventType></EventType>
<SecurityIdentifierId></SecurityIdentifierId>
<ClientRequestId></ClientRequestId>
<TargetId>ESB</TargetId>
<EventTime></EventTime>
<RequestUser></RequestUser>
</NotificationHeader>
<EventList>
<Event>
<PayLoad>
<ListOfActionIo xmlns="http://www.test.com/IO">
<Action>
<ActivityId>4-309C7WV</ActivityId>
<ActivitySRId></ActivitySRId>
<ActivityTemplateId></ActivityTemplateId>
<ActivityUID>4-309C7WV</ActivityUID>
<Category> Notification</Category>
<Comment></Comment>
<Type>Action</Type>
<ListOfDestination>
<Destination>
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
<Destination>
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
</ListOfDestination>
</Action>
</ListOfActionIo>
</PayLoad>
</Event>
</EventList>
<DestinationList>
<Destination>
<DestinationName></DestinationName>
<DestinationId></DestinationId>
</Destination>
</DestinationList>
</Message>
</PropertySet>
XSLT Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[name()='Message']/*[name()='DestinationList']//*[name()='Destination']"></xsl:template>
<!--Copy the destination from one node to another node-->
<xsl:template match="//*[name()='Message']/*[name()='DestinationList']">
<xsl:copy>
<xsl:copy-of select="//*[name()='Message']/*[name()='EventList']//*[name()='Destination']"/>
</xsl:copy>
</xsl:template>
<!--Remove the original node from where the destination was copied-->
<xsl:template match="//*[name()='Message']/*[name()='EventList']//*[name()='ListOfDestination']"></xsl:template>
</xsl:stylesheet>
Output:
<PropertySet>
<Message>
<NotificationHeader>
<BusinessId/>
<CorrelationId>0201201916:21:24CKG3N</CorrelationId>
<SourceName/>
<SourceId/>
<EventType/>
<SecurityIdentifierId/>
<ClientRequestId/>
<TargetId>ESB</TargetId>
<EventTime/>
<RequestUser/>
</NotificationHeader>
<EventList>
<Event>
<PayLoad>
<ListOfActionIo xmlns="http://www.test.com/IO">
<Action>
<ActivityId>4-309C7WV</ActivityId>
<ActivitySRId/>
<ActivityTemplateId/>
<ActivityUID>4-309C7WV</ActivityUID>
<Category> Notification</Category>
<Comment/>
<Type>Action</Type>
</Action>
</ListOfActionIo>
</PayLoad>
</Event>
</EventList>
<DestinationList>
<Destination xmlns="http://www.test.com/IO">
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
<Destination xmlns="http://www.test.com/IO">
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
</DestinationList>
</Message>
</PropertySet>
Expected Output:
Without the Namespaces on the copied node.
<PropertySet>
<Message>
<NotificationHeader>
<BusinessId/>
<CorrelationId>0201201916:21:24CKG3N</CorrelationId>
<SourceName/>
<SourceId/>
<EventType/>
<SecurityIdentifierId/>
<ClientRequestId/>
<TargetId>ESB</TargetId>
<EventTime/>
<RequestUser/>
</NotificationHeader>
<EventList>
<Event>
<PayLoad>
<ListOfActionIo xmlns="http://www.test.com/IO">
<Action>
<ActivityId>4-309C7WV</ActivityId>
<ActivitySRId/>
<ActivityTemplateId/>
<ActivityUID>4-309C7WV</ActivityUID>
<Category> Notification</Category>
<Comment/>
<Type>Action</Type>
</Action>
</ListOfActionIo>
</PayLoad>
</Event>
</EventList>
<DestinationList>
<Destination>
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
<Destination>
<DestinationName>NSW Aboriginal Education Consultative Group Incorporated</DestinationName>
</Destination>
</DestinationList>
</Message>
</PropertySet>
You are asking the wrong question. You don't want to "remove namespaces" - you want to change the namespace of transformed elements. This is akin to renaming the elements.
Now, since your example is confusing (and too long), consider the following:
XML
<root>
<colors>
<color>red</color>
<color>blue</color>
</colors>
<shapes xmlns="some-unknown-namespace">
<shape>circle</shape>
<shape>square</shape>
</shapes>
<sizes>
<size>small</size>
<size>large</size>
</sizes>
</root>
To change the namespace of shapes (and its descendants, that inherit it!) to no-namespace, without knowing in advance what the original namespace will be, you can do:
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="*[ancestor-or-self::*[name()='shapes']]">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<colors>
<color>red</color>
<color>blue</color>
</colors>
<shapes>
<shape>circle</shape>
<shape>square</shape>
</shapes>
<sizes>
<size>small</size>
<size>large</size>
</sizes>
</root>
Added:
lets modify the code to move the shapes into sizes and remove the shapes node.
OK, let's do that:
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="sizes">
<xsl:copy>
<xsl:apply-templates/>
<!-- move the shapes into sizes -->
<xsl:apply-templates select="../*[name()='shapes']/*"/>
</xsl:copy>
</xsl:template>
<!-- remove the shapes element -->
<xsl:template match="*[name()='shapes']"/>
<xsl:template match="*[ancestor::*[name()='shapes']]">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<colors>
<color>red</color>
<color>blue</color>
</colors>
<sizes>
<size>small</size>
<size>large</size>
<shape>circle</shape>
<shape>square</shape>
</sizes>
</root>

XSLT - First Instance of Empty Node edit

I am trying to edit the first instance of an empty node(Status) in the following xml :
<?xml version='1.0' encoding='windows-1252'?>
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setMessage>4</setMessage>
<MockOne>5</MockOne>
<MockTwo>6</MockTwo>
<MockThree>7</MockThree>
<Assert></Assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setMessage>6</setMessage>
<MockOne>7</MockOne>
<MockTwo>8</MockTwo>
<MockThree></MockThree>
<Assert></Assert>
<status></status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setMessage>7</setMessage>
<MockOne>8</MockOne>
<MockTwo>9</MockTwo>
<MockThree></MockThree>
<Assert>3</Assert>
<status></status>
</row>
</root>
What I want to achieve is for the xslt to find the first instance of the tag which is empty and edit it to say 123. I tried using the following XSLT, but it seems to be replacing every Empty Status tag and I need it only to do the first instance. Kindly suggest on what has to be changed
The XSLT as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>
The output right now is as follows( Every empty Status tag is replaced instead of the first instance of an empty one)
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setmessage>4</setmessage>
<mockone>5</mockone>
<mocktwo>6</mocktwo>
<mockthree>7</mockthree>
<assert></assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setmessage>6</setmessage>
<mockone>7</mockone>
<mocktwo>8</mocktwo>
<mockthree></mockthree>
<assert></assert>
<status>123</status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setmessage>7</setmessage>
<mockone>8</mockone>
<mocktwo>9</mocktwo>
<mockthree></mockthree>
<assert>3</assert>
<status>123</status>
</row>
</root>
Your Xpath expression is saying to update all row/status which is empty.
You need to change like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[not(preceding-sibling::row[not(normalize-space(status))])]/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>

XSLT: Convert Name/Value pair and transform an XML

I need to convert a name value pair into XML. I'm able to generate an XML, but the element name should be grouped and it should not be duplicated. Please see below. The FieldValue element contains 2 OrderItem values in the Detail node. If the FieldValue with OrderItem repeats, then the result should be grouped into one OrderItem node. Please help.
Source XML:
<SC>
<Header>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderHeader</FieldValue>
</Record>
<Record>
<FieldName>Order</FieldName>
<FieldValue>1234</FieldValue>
</Record>
</Header>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>1</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>10</FieldValue>
</Record>
</Detail>
<Detail>
<Record>
<FieldName>Schema</FieldName>
<FieldValue>OrderItem</FieldValue>
</Record>
<Record>
<FieldName>Item</FieldName>
<FieldValue>2</FieldValue>
</Record>
<Record>
<FieldName>Qty</FieldName>
<FieldValue>20</FieldValue>
</Record>
</Detail>
</SC>
Target XML:
<Order>
<OrderItem>
<Item>
<Item>1</Item>
<Qty>10</Qty>
</Item>
<Item>
<Item>2</Item>
<Qty>20</Qty>
</Item>
</OrderItem>
</Order>
XSLT:
<xsl:template match="#*|node()">
<Order>
<xsl:for-each select="Detail">
<Item>
<xsl:apply-templates select="Record[position()>1]"/>
</Item>
</xsl:for-each>
</Order>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
The grouping can be done as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="SC">
<Order>
<xsl:for-each-group select="Detail" group-by="Record[1]/FieldValue">
<xsl:element name="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:for-each-group>
</Order>
</xsl:template>
<xsl:template match="Detail">
<Item>
<xsl:apply-templates select="Record[position() gt 1]"/>
</Item>
</xsl:template>
<xsl:template match="Record">
<xsl:element name="{FieldName}">
<xsl:value-of select="FieldValue"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It appears that you are trying to define two XSLT templates, when one should be sufficient. You want to match on the root and then that you want to iterate over each SC/Detail.
Then, you want to take the FieldValue of the sibling of the FieldName node that is 'Item' (for item value) and 'Qty' (for quantity value), but only those listed under 'Record'.
Note: You have specified a doubly-nested <Item> in your transformed output and this solution reflects that requirement.
This XSLT should do what you are requesting:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:for-each select="SC/Detail">
<Order>
<OrderItem>
<Item>
<Item>
<xsl:value-of select="Record[FieldName[text()='Item']]/FieldValue" />
</Item>
<Qty>
<xsl:value-of select="Record[FieldName[text()='Qty']]/FieldValue" />
</Qty>
</Item>
</OrderItem>
</Order>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT get Node by Index

i am beginner in XSLT and i am using it to transform XML to XML
This is the source XML i receive
Source XML:
<Response>
<Pax>
<Id>1</Id>
</Pax>
<Pax>
<Id>2</Id>
</Pax>
<Travelers>
<Traveler>
<Name>ABC</Name>
</Traveler>
<Traveler>
<Name>XYZ</Name>
</Traveler>
</Travelers>
</Response>
I have written below XSLT
XSLT:
<?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:template match="Response">
<xsl:element name="Root">
<xsl:apply-templates select="Travelers/Traveler"/>
</xsl:element>
</xsl:template>
<xsl:template match="Traveler">
<xsl:element name="Person">
<xsl:element name="PId">
<xsl:value-of select="//Pax/Id[position()]" />
</xsl:element>
<xsl:element name="Name">
<xsl:value-of select="Name" />
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>1</PId>
<Name>XYZ</Name>
</Person>
</Root>
I would like to generate below XML output
Expected Output:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>2</PId>
<Name>XYZ</Name>
</Person>
</Root>
As shown in above XML the only issue is with PId, it should have value 2.
Please help. Thanks.
Here's a relatively simple solution.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<Root>
<xsl:apply-templates select="Pax" />
</Root>
</xsl:template>
<xsl:template match="Pax">
<xsl:variable name="vPosition" select="position()" />
<Person>
<PId>
<xsl:value-of select="Id" />
</PId>
<Name>
<xsl:value-of select="/*/Travelers/*[$vPosition]/Name" />
</Name>
</Person>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<Response>
<Pax>
<Id>1</Id>
</Pax>
<Pax>
<Id>2</Id>
</Pax>
<Travelers>
<Traveler>
<Name>ABC</Name>
</Traveler>
<Traveler>
<Name>XYZ</Name>
</Traveler>
</Travelers>
</Response>
...the wanted result is produced:
<Root>
<Person>
<PId>1</PId>
<Name>ABC</Name>
</Person>
<Person>
<PId>2</PId>
<Name>XYZ</Name>
</Person>
</Root>
<?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:template match="Response">
<Root>
<xsl:for-each select="Travelers/Traveler">
<Person>
<xsl:variable name="index" select="position()" />
<Pid><xsl:value-of select="//Pax[$index]/Id"/></Pid>
<Name><xsl:value-of select="Name"/></Name>
</Person>
</xsl:for-each>
</Root>
</xsl:template>
</xsl:stylesheet>
<?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"/>
<xsl:template match="/Response">
<Root>
<xsl:for-each select="Pax">
<xsl:variable name="pos" select="position()"/>
<Person>
<PId>
<xsl:value-of select="Id"/>
</PId>
<xsl:apply-templates select="//Travelers">
<xsl:with-param name="pos" select="$pos"/>
</xsl:apply-templates>
</Person>
</xsl:for-each>
</Root>
</xsl:template>
<xsl:template match="Travelers">
<xsl:param name="pos"/>
<xsl:for-each select="//Name">
<xsl:if test="position()=$pos">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Accessing current node in predicate with xsl:for-each

I'm stuck finding out how I can access the current node whilst iterating over a collection of nodes with xsL:for-each. This is my XML:
<events>
<event>
<date>
<year>2012</year>
<month>July</month>
</date>
<description>...</description>
</event>
<!-- more events -->
</events>
What I try to achieve is an HTML-representation like this:
<h2>2012</h2>
<dl>
<dt>July</dt>
<dd>One of these for every event with year=2012 and month=July</dd>
<dt>August</dt>
<!-- ... -->
</dl>
<h2>2013</h2>
<!-- ... -->
I'm using an XPath-expression to get all distinct years and then iterate over them calling a template called year with parameters $year and $events. Getting the value for $year is easy, but I'm struggling finding the right events. The following won't work, probably because . in the second predicate refers to the event being tested for the predicate. But how to access the year in there?
<xsl:template match="events">
<xsl:for-each select="event[not(date/year=preceding-sibling::event/date/year)]/date/year">
<xsl:call-template name="year">
<xsl:with-param name="year" select="." />
<xsl:with-param name="events" select="event[date/year=.]" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
Many thanks in advance!
PS: Must work with XPath and XSLT 1.0.
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:key name="kYear" match="year" use="."/>
<xsl:key name="kEventByMonthYear" match="event"
use="string(date)"/>
<xsl:key name="kMonthByMonthYear" match="month"
use="string(..)"/>
<xsl:key name="kMonthByYear" match="month"
use="../year"/>
<xsl:template match="/*">
<xsl:for-each select=
"*/date/year
[generate-id() = generate-id(key('kYear', .)[1])]
">
<h2><xsl:value-of select="."/></h2>
<dl>
<xsl:apply-templates select=
"key('kMonthByYear', current())
[generate-id()
=
generate-id(key('kMonthByMonthYear',
string(..)
)[1]
)
]"/>
</dl>
</xsl:for-each>
</xsl:template>
<xsl:template match="month">
<dt><xsl:value-of select="."/></dt>
<xsl:for-each select=
"key('kEventByMonthYear', string(current()/..))">
<dd><xsl:value-of select="description"/></dd>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<events>
<event>
<date>
<year>2012</year>
<month>January</month>
</date>
<description>Jan1</description>
</event>
<event>
<date>
<year>2012</year>
<month>January</month>
</date>
<description>Jan2</description>
</event>
<event>
<date>
<year>2012</year>
<month>March</month>
</date>
<description>Mar1</description>
</event>
<event>
<date>
<year>2012</year>
<month>March</month>
</date>
<description>Mar2</description>
</event>
<event>
<date>
<year>2012</year>
<month>March</month>
</date>
<description>Mar3</description>
</event>
<event>
<date>
<year>2012</year>
<month>July</month>
</date>
<description>Jul1</description>
</event>
<event>
<date>
<year>2012</year>
<month>July</month>
</date>
<description>Jul2</description>
</event>
<event>
<date>
<year>2012</year>
<month>September</month>
</date>
<description>Sep1</description>
</event>
<event>
<date>
<year>2012</year>
<month>October</month>
</date>
<description>Oct1</description>
</event>
<event>
<date>
<year>2012</year>
<month>October</month>
</date>
<description>Oct2</description>
</event>
<event>
<date>
<year>2012</year>
<month>November</month>
</date>
<description>Nov1</description>
</event>
<event>
<date>
<year>2012</year>
<month>November</month>
</date>
<description>Nov2</description>
</event>
<event>
<date>
<year>2012</year>
<month>December</month>
</date>
<description>Dec1</description>
</event>
<event>
<date>
<year>2012</year>
<month>December</month>
</date>
<description>Dec2</description>
</event>
<event>
<date>
<year>2012</year>
<month>December</month>
</date>
<description>Dec3</description>
</event>
<event>
<date>
<year>2013</year>
<month>January</month>
</date>
<description>Jan1</description>
</event>
<event>
<date>
<year>2013</year>
<month>January</month>
</date>
<description>Jan2</description>
</event>
</events>
produces the wanted, correct result:
<h2>2012</h2>
<dl>
<dt>January</dt>
<dd>Jan1</dd>
<dd>Jan2</dd>
<dt>March</dt>
<dd>Mar1</dd>
<dd>Mar2</dd>
<dd>Mar3</dd>
<dt>July</dt>
<dd>Jul1</dd>
<dd>Jul2</dd>
<dt>September</dt>
<dd>Sep1</dd>
<dt>October</dt>
<dd>Oct1</dd>
<dd>Oct2</dd>
<dt>November</dt>
<dd>Nov1</dd>
<dd>Nov2</dd>
<dt>December</dt>
<dd>Dec1</dd>
<dd>Dec2</dd>
<dd>Dec3</dd>
</dl>
<h2>2013</h2>
<dl>
<dt>January</dt>
<dd>Jan1</dd>
<dd>Jan2</dd>
</dl>
Explanation:
Proper use of the Muenchian method for grouping -- with composite grouping keys.
#Dimitre Novatchev's answer is the more elaborate one, but I found another possibility I'd like to share. It doesn't depend on keys and therefore is a bit more "newbie-friendly". On the other hand it too doesn't solve the original "accessing current node of an iteration" problem:
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="events">
<xsl:for-each select="event[not(date/year=preceding-sibling::event/date/year)]/date/year">
<xsl:call-template name="year">
<xsl:with-param name="year" select="." />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="year">
<xsl:param name="year" />
<h2><xsl:value-of select="$year" /></h2>
<dl class="dl-horizontal">
<xsl:for-each select="//event[date/year=$year][not(date/month=preceding-sibling::event[date/year=$year]/date/month)]/date/month">
<xsl:call-template name="month">
<xsl:with-param name="month" select="." />
<xsl:with-param name="year" select="$year" />
</xsl:call-template>
</xsl:for-each>
</dl>
</xsl:template>
<xsl:template name="month">
<xsl:param name="month" />
<xsl:param name="year" />
<dt><xsl:value-of select="$month" /></dt>
<xsl:for-each select="//event[date/year=$year][date/month=$month]">
<xsl:call-template name="event">
<xsl:with-param name="event" select="." />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="event">
<xsl:param name="event" />
<dd><xsl:copy-of select="description/node()" /></dd>
</xsl:template>
</xsl:transform>