I was trying to read the xml data from input xml, but I am getting empty result in the xml, I am not able to understand where I did mistake and any issue with input xml. The main goal is to change the data of the Organisation information in this, but for the first step itself I am not able to read the data in the xml
Input:
<?xml version="1.0" encoding="utf-8"?>
<entities xmlns="http://schemas.ockhm.com/api/1/">
<record source="system" key="53071236" revision="1234" status="valid" type="person">
<member>
<source>L002</source>
<key>442</key>
</member>
<member>
<source>L002</source>
<key>027</key>
</member>
<member>
<source>L002</source>
<key>071</key>
</member>
<member>
<source>L002</source>
<key>0930</key>
</member>
<person status="valid">
<gender>male</gender>
<form_of_address>Mr.</form_of_address>
<qualification_preceding>Dr.</qualification_preceding>
<given_names_full>john</given_names_full>
<surname_first>test</surname_first>
<origin>
<source>L002</source>
<key>442</key>
</origin>
<origin>
<source>L002</source>
<key>027</key>
</origin>
<origin>
<source>L002</source>
<key>0071</key>
</origin>
<origin>
<source>L002</source>
<key>0930</key>
</origin>
</person>
<organization status="valid">
<name>Test college</name>
<department>test one Institute</department>
<origin>
<source>L002</source>
<key>027</key>
</origin>
<origin>
<source>L002</source>
<key>442</key>
</origin>
<origin>
<source>L002</source>
<key>0930</key>
</origin>
</organization>
<organization status="valid">
<name>Test king INST.</name>
<department>PHARMA</department>
<origin>
<source>L002</source>
<key>132</key>
</origin>
</organization>
<alias status="valid">
<alias7>732</alias7>
<origin>
<source>L002</source>
<key>9442</key>
</origin>
</alias>
<alias status="valid">
<alias7>33</alias7>
<origin>
<source>L002</source>
<key>027</key>
</origin>
</alias>
<create_user status="valid">
<create_user>572</create_user>
<origin>
<source>L002</source>
<key>027</key>
</origin>
<origin>
<source>L002</source>
<key>071</key>
</origin>
<origin>
<source>L002</source>
<key>930</key>
</origin>
</create_user>
<bp_type status="valid">
<bp_type>customers</bp_type>
<origin>
<source>CRM</source>
<key>10082</key>
</origin>
<origin>
<source>CRM</source>
<key>93</key>
</origin>
</bp_type>
<bp_type status="valid">
<bp_type>teachers</bp_type>
<origin>
<source>CRM</source>
<key>6128</key>
</origin>
<origin>
<source>CRM</source>
<key>775</key>
</origin>
<origin>
<source>CRM</source>
<key>029</key>
</origin>
</bp_type>
<head_of_account status="valid">
<head_of_account>P</head_of_account>
<origin>
<source>CRM</source>
<key>451</key>
</origin>
</head_of_account>
<head_of_account status="valid">
<head_of_account>D</head_of_account>
<origin>
<source>CRM</source>
<key>128</key>
</origin>
<origin>
<source>CRM</source>
<key>775</key>
</origin>
<origin>
<source>CRM</source>
<key>029</key>
</origin>
</head_of_account>
<postal_address status="valid">
<type>XXDEFAULT</type>
<str>xx street</str>
<hno>8</hno>
<zip>zip1234</zip>
<city>city123</city>
<country_code>gh</country_code>
<zip_add_on>1P</zip_add_on>
<origin>
<source>L002</source>
<key>071</key>
</origin>
<origin>
<source>L002</source>
<key>764</key>
</origin>
</postal_address>
<email_address status="valid">
<type>work</type>
<address>jp address</address>
<origin>
<source>CRM</source>
<key>128</key>
<id>655</id>
</origin>
</email_address>
<external_numbers status="valid">
<type>ZGRID</type>
<external_number>1234</external_number>
<origin>
<source>CRM</source>
<key>128</key>
</origin>
</external_numbers>
</record>
</entities>
XLST code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:ns1="http://sap.com/xi/XI/SplitAndMerge" xmlns="http://schemas.ockhm.com/api/1/">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:element name="ns1:Messages">
<xsl:element name="ns1:Message1">
<xsl:element name="record">
<xsl:for-each select="/record/member">
<xsl:apply-templates/>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result coming :
<?xml version="1.0" encoding="UTF-8"?>
<ns1:Messages xmlns:ns1="http://sap.com/xi/XI/SplitAndMerge">
<ns1:Message1>
<record xmlns="http://schemas.ockhm.com/api/1/"/>
</ns1:Message1>
</ns1:Messages>
Expected result:
<?xml version="1.0" encoding="UTF-8"?>
<ns1:Messages xmlns:ns1="http://sap.com/xi/XI/SplitAndMerge">
<ns1:Message1>
<record xmlns="http://schemas.ockhm.com/api/1/">
<member>
<source>L002</source>
<key>0000969442</key>
</member>
<member>
<source>L002</source>
<key>0000280027</key>
</member>
<member>
<source>L002</source>
<key>0000910071</key>
</member>
<member>
<source>L002</source>
<key>0000770930</key>
</member>
<member>
<source>L002</source>
<key>0000770764</key>
</member>
<member>
<source>SAP-CRM</source>
<key>3000828451</key>
</member>
<member>
<source>SAP-CRM</source>
<key>3000006128</key>
</member>
<member>
<source>SAP-CRM</source>
<key>3001239775</key>
</member>
<member>
<source>L002</source>
<key>0000967132</key>
</member>
<member>
<source>SAP-CRM</source>
<key>3001836593</key>
</member>
<member>
<source>SAP-CRM</source>
<key>3001365029</key>
</member>
</record>
</ns1:Message1>
</ns1:Messages>
Can anyone please let me know where is the mistake happened.
Before delving into XSLT I would suggest to take an XPath tutorial, if you have an input document where the root element is named entities then a path beginning with /record will never select anything as it looks for a root element named record.
Depending on your XSLT version you also need to take the default namespace xmlns="http://schemas.ockhm.com/api/1/" in the XML into account, if you really have an XSLT 2 processor the easiest is to declare xpath-default-namespace="http://schemas.ockhm.com/api/1/" on your stylesheet's root element.
That way a path like //record will select any record elements in that namespace.
Related
The Input has two xml in it , The InputMessagePart_0 have Multiple Location id and InputMessagePart_1 have Multiple ItemMaster then i need to create an Output where for every location id i need to have the Item Master.
I have written the Xslt where its not looping on the InputMessagePart_0(Record) level and its taking only the first Location id ,
XSlT1.0 :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s2:Root" />
</xsl:template>
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_1/s0:Root/ItemMaster">
<xsl:variable name="var:v1" select="../../../InputMessagePart_0/s1:Root/Record/LocationId" />
<xsl:variable name="var:v2" select="ItemId" />
<xsl:variable name="var:v3" select="ItemName" />
<xsl:variable name="var:v4" select="Quantity" />
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="$var:v2" />
</ItemId>
<ItemName>
<xsl:value-of select="$var:v3" />
</ItemName>
<Qty>
<xsl:value-of select="$var:v4" />
</Qty>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Input XML :
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<ns0:Root xmlns:ns0="http://Test.Lookup">
<Record>
<LocationId>12</LocationId>
</Record>
<Record>
<LocationId>13</LocationId>
</Record>
<Record>
<LocationId>14</LocationId>
</Record>
</ns0:Root>
</InputMessagePart_0>
<InputMessagePart_1>
<ns0:Root xmlns:ns0="http://Test.ItemMaster">
<ItemMaster>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
</ns0:Root>
</InputMessagePart_1>
</ns0:Root>
Current OutPut:
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
You need to have an xsl:for-each (or xsl:apply-templates) to get the Record elements with the locations. (And this xsl:for-each would contain the current xsl:for-each on the ItemMaster)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes" version="1.0" />
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_0/s1:Root/Record">
<xsl:variable name="var:v1" select="LocationId" />
<xsl:for-each select="../../../InputMessagePart_1/s0:Root/ItemMaster">
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="ItemId" />
</ItemId>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Qty>
<xsl:value-of select="Quantity" />
</Qty>
</Detail>
</xsl:for-each>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Note, the template matching / wasn't really necessary here, as XSLT's built-in template does the same thing.
I want to filter an incoming xml document to only keep those records containing an date later than today on some repeating n-th child node.
I tried to use the identity template with a template to match only those records with a predicate in the match, but I can't seem to get the right predicate if possible at all.
Example input:
<?xml version="1.0" encoding="utf-8"?>
<Example>
<Header>
<ElementA/>
<ElementB/>
</Header>
<Records>
<Record>
<Person>
<ElementC>1</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
</Employer>
</Employers>
</Record>
<Record>
<Person>
<ElementC>2</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
<History>
<HistoryRecord>
<Period>
<Date>2017-08-01</Date>
</Period>
</HistoryRecord>
<HistoryRecord>
<Period>
<Date>2017-08-01</Date>
</Period>
<Period>
<Date>2018-10-01</Date>
</Period>
</HistoryRecord>
</History>
</Employer>
</Employers>
</Record>
<Record>
<Person>
<ElementC>3</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
<History>
<HistoryRecord>
<Period>
<Date>2017-11-01</Date>
</Period>
</HistoryRecord>
</History>
</Employer>
</Employers>
</Record>
<Record>
<Person>
<ElementC>4</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
<History>
<HistoryRecord>
<Period>
<Date>2018-11-01</Date>
</Period>
</HistoryRecord>
</History>
</Employer>
</Employers>
</Record>
</Records>
</Example>
Wanted output:
<?xml version="1.0" encoding="utf-8"?>
<Example>
<Header>
<ElementA/>
<ElementB/>
</Header>
<Records>
<Person>
<ElementC>2</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
<History>
<HistoryRecord>
<Period>
<Date>2017-08-01</Date>
</Period>
</HistoryRecord>
<HistoryRecord>
<Period>
<Date>2017-08-01</Date>
</Period>
<Period>
<Date>2018-10-01</Date>
</Period>
</HistoryRecord>
</History>
</Employer>
</Employers>
</Record>
<Record>
<Person>
<ElementC>4</ElementC>
</Person>
<Employers>
<Employer>
<Identification>
<ElementD/>
<ElementE/>
</Identification>
<History>
<HistoryRecord>
<Period>
<Date>2018-11-01</Date>
</Period>
</HistoryRecord>
</History>
</Employer>
</Employers>
</Record>
</Records>
</Example>
<!-- Get today's date somehow -->
<xsl:variable name="todaysDate" select="'2018-09-06'"/>
<!-- You need the identity template -->
<xsl:template match="node()|#*">
<xsl:apply-templates select="node()|#*"/>
</xsl:template>
<xsl:template match="Record">
<!-- Since your date is in the yyyy-mm-dd format, you can do a text compare. -->
<xsl:if test=".//Date[. > $todaysDate][1]">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
I have this source XML tree:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>
<bar>
<baz>
<item>
<methods>
<item>
<id>1</id>
</item>
</methods>
<id>1</id>
</item>
<item>
<methods>
<item>
<id>19</id>
</item>
</methods>
<id>2</id>
</item>
</baz>
</bar>
</foo>
<bar_method>
<root>
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
<method id="2">
<calc md="qck" />
<tm m="4" />
<price_list>
<price mse="1">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
</root>
</bar_method>
</root>
Now I need to place fragment of this tree in variable using XPath. The fragment should look like this:
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
These are bla nodes excluding method nodes, id attributes of which missing in /root/foo/bar/baz/item/methods/item/id. I use following expression but it selects all nodes with duplicates:
<xsl:variable name="meth" select="/root/bar_method/root//*[not(name() = 'method' and count(/root/foo/bar/baz//methods/item[id = #id]) = 0)]" />
XPath can only select nodes, it cannot change them. That is to say, the children and descendants of the nodes you select will always be exactly as they were in the source document.
If you want to create a tree that's different from the input tree, you need XSLT or XQuery.
Looks like you want all the bla elements and just the first methods/method element within each of them. Is that right?
You can't do that in a single XPath expression because you can only restrict the elements being selected - you can't filter out some of their descendants as well. But it is possible using templates.
This stylesheet creates the variable $meth and outputs it using copy-of.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="meth">
<xsl:apply-templates select="root/bar_method/root/bla"/>
</xsl:variable>
<xsl:copy-of select="$meth"/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="methods">
<xsl:copy>
<xsl:apply-templates select="method[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
output
<bla id="1">
<methods>
<method id="1">
<calc md="ck"/>
<tm m="14"/>
<price_list>
<price mse="0">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd"/>
<tm m="3"/>
<price_list>
<price mse="01">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>
I have a requirment to transform a XML with the below structure
<CustomerStatements>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>10</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>20</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>30</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>40</Amt>
</CustomerStatement>
</CustomerStatements>
To
<Customers>
<Customer>
<Name>ABC</Name>
<Id>1</Id>
<Amounts>
<Amount>10</Amount>
<Amount>20</Amount>
</Amounts>
</Customer>
<Customer>
<Name>XYZ</Name>
<Id>2</Id>
<Amount>30</Amount>
<Amount>40</Amount>
</Customer>
</Customers>
I tried using a for loop and taking the name into a variable to compare the name in the next record, but this doesn't work. Can you any one help me with a sample XSLT psudo code.
Thanks
I. When this XSLT 1.0 solution:
<?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="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key
name="kCustByNameId"
match="CustomerStatement"
use="concat(Name, '+', ID)" />
<xsl:template match="/*">
<Customers>
<xsl:apply-templates
select="CustomerStatement[
generate-id() =
generate-id(key('kCustByNameId', concat(Name, '+', ID))[1])]" />
</Customers>
</xsl:template>
<xsl:template match="CustomerStatement">
<Customer>
<xsl:copy-of select="Name|ID" />
<Amounts>
<xsl:for-each select="key('kCustByNameId', concat(Name, '+', ID))/Amt">
<Amount>
<xsl:apply-templates />
</Amount>
</xsl:for-each>
</Amounts>
</Customer>
</xsl:template>
</xsl:stylesheet>
...is applied to the OP's original XML:
<CustomerStatements>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>10</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>ABC</Name>
<ID>1</ID>
<Amt>20</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>30</Amt>
</CustomerStatement>
<CustomerStatement>
<Name>XYZ</Name>
<ID>2</ID>
<Amt>40</Amt>
</CustomerStatement>
</CustomerStatements>
...the wanted result is produced:
<?xml version="1.0" encoding="UTF-8"?><Customers>
<Customer>
<Name>ABC</Name>
<ID>1</ID>
<Amounts>
<Amount>10</Amount>
<Amount>20</Amount>
</Amounts>
</Customer>
<Customer>
<Name>XYZ</Name>
<ID>2</ID>
<Amounts>
<Amount>30</Amount>
<Amount>40</Amount>
</Amounts>
</Customer>
</Customers>
The primary thing to look at here is Muenchian Grouping, which is the generally accepted method for grouping problems in XSLT 1.0.
II. Here's a more compact XSLT 2.0 solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<Customers>
<xsl:for-each-group select="CustomerStatement" group-by="ID">
<Customer>
<xsl:copy-of select="current-group()[1]/Name|current-group()[1]/ID" />
<Amounts>
<xsl:for-each select="current-group()/Amt">
<Amount>
<xsl:apply-templates />
</Amount>
</xsl:for-each>
</Amounts>
</Customer>
</xsl:for-each-group>
</Customers>
</xsl:template>
</xsl:stylesheet>
In this case, notice XSLT 2.0's use of the for-each-group element, which eliminates the need for the sometimes-verbose and potentially confusing Muenchian Grouping method.
I have the following XML:
<things>
<thing name="Foo" available="yes"/>
<thing name="Bar" available="no"/>
<thing name="Baz" available="yes">
<parent name="Foo"/>
<parent name="Bar"/>
</thing>
<thing name="Qux" available="no">
<parent name="Foo"/>
<parent name="Bar"/>
</thing>
<thing name="Waldo" available="yes">
<parent name="Foo"/>
<parent name="Bar"/>
<parent name="Baz"/>
<parent name="Qux"/>
</thing>
</things>
This represent a structure like the following:
Foo
Baz
Waldo
Qux
Waldo
Waldo
Bar
Baz
Waldo
Qux
Waldo
Waldo
The actual XML is quite large and the nesting is deep. Unavaliable things can be anywhere. There are no loops (things that have themselves as ancestors).
Now I want to generate all possible paths to Waldo, filtering out the paths containing things that are not available. Something like the following result is what I'm looking for:
<ul>
<li>
Foo
<ul>
<li>
Baz
<ul>
<li>
<b>Waldo</b>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>
Foo
<ul>
<li>
<b>Waldo</b>
</li>
</ul>
</li>
</ul>
That is:
Foo
Baz
Waldo
Foo
Waldo
Starting from a leaf node, looking up the tree, generating all possible paths while ignoring unavailable paths got me stumped. Any insights, prose, pseudocode or XSLT is much appreciated!
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kChildren" match="thing" use="parent/#name"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<things>
<xsl:apply-templates select="thing[not(parent)]"/>
</things>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vrtfPass1)/*"
mode="pass2"/>
</xsl:template>
<xsl:template match="thing">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="key('kChildren', #name)"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|#*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<xsl:template mode="pass2"
match="*[not(#name = 'Waldo' or .//*[#name='Waldo'])]" />
</xsl:stylesheet>
when applied on the provided XML document:
<things>
<thing name="Foo" available="yes"/>
<thing name="Bar" available="no"/>
<thing name="Baz" available="yes">
<parent name="Foo"/>
<parent name="Bar"/>
</thing>
<thing name="Qux" available="no">
<parent name="Foo"/>
<parent name="Bar"/>
</thing>
<thing name="Waldo" available="yes">
<parent name="Foo"/>
<parent name="Bar"/>
<parent name="Qux"/>
</thing>
</things>
produces a result that contains only "branches" containing "waldo":
<things>
<thing name="Foo" available="yes">
<thing name="Qux" available="no">
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Bar" available="no">
<thing name="Qux" available="no">
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Waldo" available="yes"/>
</thing>
</things>
It is left as an exercise to the reader to transform this to whatever final HTML format is needed.
Explanation:
This is a two-pass transformation.
The first pass constructs a tree in which the parent-child relationship is expressed explicitly.
The second pass is an identity rule, overlaid by a template with empty body ("deleting" template) for subtrees that don't contain an thing with attribute name with string-value "Waldo".
The result of the first pass is:
<things>
<thing name="Foo" available="yes">
<thing name="Baz" available="yes"/>
<thing name="Qux" available="no">
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Bar" available="no">
<thing name="Baz" available="yes"/>
<thing name="Qux" available="no">
<thing name="Waldo" available="yes"/>
</thing>
<thing name="Waldo" available="yes"/>
</thing>
</things>
The second pass strips off two <thing name="Baz" available="yes"/> elements to produce the final result.