I am new to XSLT and I need help in merging two different XML documents into one.
XML1.xml
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
</customer>
</customers>
XML2.xml
<Products>
<Product>
<name>Onida Tv</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>washing machine</name>
<consumer>Ram</consumer>
</Product>
<Product>
<name>Water purifier</name>
<consumer>Raghav</consumer>
</Product>
<Product>
<name>iPhone</name>
<consumer>Raghav</consumer>
</Products>
</Products>
Desired XML output:
<customers>
<customer>
<Person name="Ram" Id="101"/>
<address>flat 4</address>
<products>
<name>washing machine</name>
<name>Onida TV</name>
</products>
</customer>
<customer>
<Person name="Raghav" Id="102"/>
<address>flat 9</address>
<products>
<name>iPhone</name>
<name>Water purifier</name>
</products>
</customer>
</customers>
The second XML is to be considered external in this context. I need to append to each customer the corresponding products. How can I do that?
Try it this way:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<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="*"/>
<xsl:variable name="lookup-document" select="document('XML2.xml')" />
<xsl:key name="product-by-consumer" match="Product" use="consumer" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="customer">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:variable name="name" select="Person/#name" />
<products>
<!-- switch context to the other document in order to use key -->
<xsl:for-each select="$lookup-document">
<xsl:copy-of select="key('product-by-consumer', $name)/name"/>
</xsl:for-each>
</products>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that customer names are unique.
Related
I would like to filter out elements except some in a list.
I have this XML:
<catalog>
<product>
<code>Y17231</code>
<pname>Test 1</pname>
</product>
<product>
<code>Y19232</code>
<pname>Test 2</pname>
</product>
<product>
<code>Y18333</code>
<pname>Test 3</pname>
</product>
</catalog>
Currently I am filtering out all elements, which are not starting with Y19* code.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml"/>
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="product[not(matches(code, 'Y[^X]?19.*[^V]$'))]" />
</xsl:stylesheet>
This is working, xsltransform example, example output:
<catalog>
<product>
<code>Y19232</code>
<pname>Test 2</pname>
</product>
</catalog>
The goal:
I would like to extend this with a feature, where I can define some codes, which are then not filtered out (they will be included in the result).
<xsl:param name="include-this" as="xs:string*" select="'Y18333','Y16222','Y12333'"/>
So output should be this:
<catalog>
<product>
<code>Y19232</code>
<pname>Test 2</pname>
</product>
<product>
<code>Y18333</code>
<pname>Test 3</pname>
</product>
</catalog>
Can you help me how to achieve my goal?
I think you want to use match="product[not(code = $include-this) and not(matches(code, 'Y[^X]?19.*[^V]$'))]".
I have a working XSLT that replaces an element name with an attribute value only when the attribute is named "AttributeID". It saves the original element name in a new attribute called StepClass but it only works at one level.
This XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
</Product>
</Products>
Becomes this XML:
<Products>
<Product>
<Values>
<One StepClass="Value">1</One>
<Multi1 StepClass="MultiValue">
<Value>111</Value>
</Multi1>
</Values>
</Product>
</Products>
Using this XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Change Values/Value value of AttributeID -->
<!-- <xsl:template match="Values/Value|MultiValue|MetaData/Value"> This was working with 1 level -->
<xsl:template match="*[#AttributeID]">
<xsl:element name="{#AttributeID}">
<xsl:attribute name="StepClass">
<xsl:value-of select="name()" />
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<!--empty template suppresses this attribute-->
<xsl:template match="#AttributeID" />
</xsl:stylesheet>
But fails as soon as the source XML has nested products. I would like the substitution to work at any level. What am I doing wrong here?
Nested XML:
<Products>
<Product>
<Values>
<Value AttributeID="One">1</Value>
<MultiValue AttributeID="Multi1">
<Value>111</Value>
</MultiValue>
</Values>
<Product>
<Values>
<Value AttributeID="Two">2</Value>
<MultiValue AttributeID="Multi2">
<Value>222</Value>
</MultiValue>
</Values>
</Product>
</Product>
</Products>
XSLT as depicted in the post works as expected.
I am trying to implement an at first looking simple transformation but whatever I have tried has been failed.
The XML is generated from a fixed length record and have the below format.
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>30</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
...
</record>
The no_of_records indicates how many _X suffixed elements contains each record and because of its fix length origin has a defined maximum.
I want to transform it to a “verticalized” form resempling the below.
<record>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
...
</customer>
</record>
Any help would greatly appreciated.
In XSLT 2.0, you want something like
<xsl:for-each-group select="*" group-starting-with="*[starts-with(local-name(), 'cust_lastname']">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
....
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
The solution from #Michael Kay works fine. Thank you !
XML
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>3</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
</record>
XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="record">
<records>
<xsl:for-each-group select="*[starts-with(local-name(), 'cust_')]"
group-starting-with="*[starts-with(local-name(), 'cust_lastname')]">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
</records>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<records>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
</customer>
</records>
I am creating a new productfeed and need the following field: <diff>.
if difference between price and old_price is more than 1: y (from Yes) in field: <diff>
if difference between price and old_price is 1 or less than 1: n (from No) in field: <diff>
File: Data.xml
<?xml version="1.0"?>
<products>
<product id="0001">
<price>120.00</price>
<old_price>125.00</old_price>
</product>
<product id="0002">
<price>5.00</price>
<old_price>5.50</old_price>
</product>
</products>
Wished output:
<?xml version="1.0"?>
<products>
<product id="0001">
<diff>y</diff>
</product>
<product id="0002">
<diff>n</diff>
</product>
</products>
I haven't tested that but it should be like that:
<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="/">
<products>
<xsl:for-each select="//product">
<diff>
<xsl:choose>
<xsl:when test="price - old_price > 1">
y
</xsl:when>
<xsl:otherwise>
n
</xsl:otherwise>
</xsl:choose>
</diff>
<xsl:copy-of select="*" />
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Will test and put update
Give this a whirl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="price[translate(. - ../old_price, '-', '') > 1]">
<diff>y</diff>
</xsl:template>
<xsl:template match="price">
<diff>n</diff>
</xsl:template>
<xsl:template match="old_price" />
</xsl:stylesheet>
When run on your sample input, this produces:
<products>
<product id="0001">
<diff>y</diff>
</product>
<product id="0002">
<diff>n</diff>
</product>
</products>
Input xml
<catalog>
<product id="1">
<name>abc</name>
<category>aaa</category>
<category>bbb</category>
<category>ccc</category>
</product>
<product id="2">
<name>cde</name>
<category>aaa</category>
<category>bbb</category>
</product>
</catalog>
Expected Output xml
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</products>
XSLT for transformation
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/catalog">
<products>
<xsl:for-each select="product">
<product>
<id><xsl:value-of select="#id"/></id>
<name><xsl:value-of select="name"/></name>
<category><xsl:value-of select="category" /></category>
</product>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Actual Output xml :(
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa</category>
</product>
</products>
Code needed in looping through all sibling node by the name 'category' under every 'product' and merging/concatenating into single node separated by a comma. Number of 'category' varies for every product and hence the count is unknown.
Using this handy join call-template defined here, this becomes as simple as:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/catalog">
<products>
<xsl:for-each select="product">
<product>
<id>
<xsl:value-of select="#id"/>
</id>
<name>
<xsl:value-of select="name"/>
</name>
<category>
<xsl:call-template name="join">
<xsl:with-param name="list" select="category" />
<xsl:with-param name="separator" select="','" />
</xsl:call-template>
</category>
</product>
</xsl:for-each>
</products>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
<products>
<product>
<id>1</id>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<id>2</id>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</products>
In XSLT 2.0 you only need to make one small change to your code:
<category><xsl:value-of select="category" separator=","/></category>
Note that if you require an XSLT 1.0 solution it's a good idea to say so. Some people in some environments are stuck on 1.0, but a lot of people aren't.
Here's one other XSLT 1.0 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="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="product">
<xsl:copy>
<xsl:apply-templates select="*[not(self::category)]" />
<category>
<xsl:apply-templates select="category/text()" />
</category>
</xsl:copy>
</xsl:template>
<xsl:template match="category/text()">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
...is applied to the OP's original XML:
<catalog>
<product id="1">
<name>abc</name>
<category>aaa</category>
<category>bbb</category>
<category>ccc</category>
</product>
<product id="2">
<name>cde</name>
<category>aaa</category>
<category>bbb</category>
</product>
</catalog>
...the desired result is produced:
<?xml version="1.0"?>
<catalog>
<product>
<name>abc</name>
<category>aaa,bbb,ccc</category>
</product>
<product>
<name>cde</name>
<category>aaa,bbb</category>
</product>
</catalog>
Explanation:
The first template -- the Identity Template -- matches all nodes and attributes and copies them to the result document as-is.
The second template overrides the Identity Template by creating a new <category> element and processing the text children of each <category> element in the current location of the document.
The final template outputs the text values and commas as necessary.