How to send XSLT array to template - arrays

I need to take a data array from one node in XML and then use in XSL template.
Input data look like this:
<InpData>
<period number="1">
<Storage>
<Item weight="10.5" height="5" width="15" length="20"/>
<Item weight="20.75" height="4.5" width="7.3" length="18"/>
<Item weight="10.5" height="5" width="15" length="20"/>
</Storage>
<Transportation>
<Items>
<Item>
<DestRegion value="5"/>
<Sender name="Smith" company="BlueSky" />
<Date day="03" month="03" year="2017" />
<Item/>
<Item>
<DestRegion value="6"/>
<Sender name="Pith" company="BlueSky" />
<Date day="03" month="03" year="2017" />
<Item/>
<Item>
<DestRegion value="5"/>
<Sender name="Bill" company="BlueSky" />
<Date day="03" month="03" year="2017" />
<Item/>
<Items/>
</Transportation>
<period/>
<period number="2">
<period/>
</InpData>
And I need to combine nodes and to have output xml like that:
<period number="1">
<Items>
<Item weight="10.5" senderName="Smith"/>
<Item weight="20.75" senderName="Pith"/>
<Item weight="10.5" senderName="Bill"/>
<Items/>
<period/>
I made XSLT script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="period">
<xsl:variable name="weight" select="Storage/Item/#weight"></xsl:variable>
<period number="{#number}">
<Items>
<xsl:apply-templates select="Transportation/Items/Item">
<xsl:with-param name="weight_1">
<xsl:value-of select="$weight" />
</xsl:with-param>
</xsl:apply-templates>
</Items>
</period>
</xsl:template>
<xsl:template match="Transportation/Items/Item">
<xsl:param name="weight_1"/>
<xsl:variable name="pos" select="position()"></xsl:variable>
<Item weight="{$weight_1[$pos]}" senderName="{Sender/#name}">
</xsl:template>
</xsl:transform>
But it doesn't work properly. I can send to template for example and it works if I will use just $weight_1 without indexes in the template. But I cannot use $weight_1[$pos] in the template.
Please tell me how can I use the array weight in XSLT properly?

Why can't you do simply:
<xsl:template match="period">
<period number="{#number}">
<Items>
<xsl:for-each select="Storage/Item">
<xsl:variable name="i" select="position()" />
<Item weight="{#weight}" senderName="{../../Transportation/Items/Item[$i]/Sender/#name}"/>
</xsl:for-each>
</Items>
</period>
</xsl:template>
(assuming that the only thing that links the items is their position in their parent element).
Added:
To do it the way you have started, you'd need to do something like:
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="*"/>
<xsl:template match="/InpData">
<root>
<xsl:apply-templates select="period"/>
</root>
</xsl:template>
<xsl:template match="period">
<period number="{#number}">
<Items>
<xsl:apply-templates select="Transportation/Items/Item">
<xsl:with-param name="weights" select="Storage/Item/#weight"/>
</xsl:apply-templates>
</Items>
</period>
</xsl:template>
<xsl:template match="Item">
<xsl:param name="weights"/>
<xsl:variable name="i" select="position()" />
<Item weight="{$weights[$i]}" senderName="{Sender/#name}"/>
</xsl:template>
</xsl:stylesheet>

Related

Repeat position within a loop in xslt

I have two nested loops. The first loop is picking up distinct value and comparing in second loop. In second loop i am giving position() and it is coming sequential as 1,2,3 but i want it to appear as 1,1,1 during first iteration and 2,2,2 in second iteration and 3,3,3 in third iteration and so on.
XML:
<root>
<Items>
<Item>
<ItemCode>12345</ItemCode>
<ItemColor>Red</ItemColor>
<Weight>1Kg</Weight>
</Item>
<Item>
<ItemCode>19087</ItemCode>
<ItemColor>Blue</ItemColor>
<Weight>1Kg</Weight>
</Item>
</Items>
<Items>
<Item>
<ItemCode>12345</ItemCode>
<ItemColor>Yellow</ItemColor>
<Weight>1Kg</Weight>
</Item>
<Item>
<ItemCode>19087</ItemCode>
<ItemColor>Green</ItemColor>
<Weight>1Kg</Weight>
</Item>
</Items>
</root>
Required Output:
12345
1.Red
1.Yellow
19087
2.Blue
2.Green
(Or)
1.12345
Red
Yellow
2.19087
Blue
Green
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="check" match="Items" use="ItemCode" />
<xsl:template match="root/Items">
<xsl:for-each select="Item[count(. | key('check', ItemCode)[1]) = 1]">
<xsl:value-of select="ItemCode"/>
<xsl:for-each select="key('check', ItemCode)">
<xsl:value-of select="position()"/>
<xsl:value-of select="ItemColor"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I am using xslt-1.0
Your help is appreciated. Thank you!
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8"/>
<xsl:key name="item-by-code" match="Item" use="ItemCode"/>
<xsl:template match="/root">
<xsl:for-each select="Items/Item[count(. | key('item-by-code', ItemCode)[1]) = 1]">
<xsl:value-of select="position()"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="ItemCode"/>
<xsl:for-each select="key('item-by-code', ItemCode)">
<xsl:text>
</xsl:text>
<xsl:value-of select="ItemColor"/>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied yo your input example, will return:
Result
1.12345
Red
Yellow
2.19087
Blue
Green
Alternatively, you could do:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8"/>
<xsl:key name="item-by-code" match="Item" use="ItemCode"/>
<xsl:template match="/root">
<xsl:for-each select="Items/Item[count(. | key('item-by-code', ItemCode)[1]) = 1]">
<xsl:variable name="i" select="position()" />
<xsl:value-of select="ItemCode"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="key('item-by-code', ItemCode)">
<xsl:value-of select="$i"/>
<xsl:text>.</xsl:text>
<xsl:value-of select="ItemColor"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
to get:
12345
1.Red
1.Yellow
19087
2.Blue
2.Green

How do I index XML data within a content node?

I'm converting an XML document and want to dump the entire contents in a content node in the converter.
<xsl:template match="/">
<vce>
<document>
<content name="xml">
<xsl:copy-of select="." />
</content>
</document>
</vce>
</xsl:template>
This gives me a node with the name "XML" and my entire xml content within. However, this is removed when the normalization converter is run. Is there something special I need to do to index XML inside a content?
I was able to reference the converter: 'vse-converter-xml-to-vxml' to create a template that indexes the xml:
<xsl:template match="/">
<vce>
<document>
<content name="xml">
<xsl:apply-templates select="*" mode="xml-to-plain-text" />
</content>
</document>
</vce>
</xsl:template>
<xsl:template match="*" mode="xml-to-plain-text">
<xsl:text><![CDATA[<]]></xsl:text>
<xsl:value-of select="name()" />
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="text()|*|comment()">
<xsl:text>></xsl:text>
<xsl:apply-templates select="text()|*|comment()" mode="xml-to-plain-text" />
<xsl:text><![CDATA[</]]></xsl:text>
<xsl:value-of select="name()" />
<xsl:text>></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>/></xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

libxml2 - xpath and updating attributes

I have two xml files, A and B, and they have the same schema.
A.xml
<books>
<book name="Alice in Wonderland" />
</books>
B.xml
<books>
<book name="Of Mice and Men" />
<book name="Harry Potter" />
</books>
I want to add the attribute source="B" in the list of books in B.xml and then copy the list over to A.xml, so that A.xml will look like this
<books>
<book name="Alice in Wonderland" />
<book name="Of Mice and Men" source="B" />
<book name="Harry Potter" source="B" />
</books>
Can I use xpath to get the xpathobject of books from B, add the attribute, then copy the nodeset over to A? If so how would the code look like? Are there better ways than xpath to get the books from B?
I guess the easiest way would be using an XSLT-processor. For this task an XSLT-1.0 processor is sufficient. You can use the following template to "merge" both files. Just use the XSLT-processor with the parameters a.xslt and a.xml. The second filename is specified in a.xslt.
<?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" omit-xml-declaration="yes" indent="yes"/>
<!-- This is the base filename of the second file without the extension '.xml' -->
<xsl:variable name="secondXMLFile" select="'b'" />
<!-- This changes the second filename to uppercase -->
<xsl:variable name="secondName" select="translate($secondXMLFile,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<!-- This adds the filename extension '.xml' to the base filename and creates a document() node -->
<xsl:variable name="second" select="document(concat($secondXMLFile,'.xml'))" />
<!-- identity template --> <!-- This copies all elements from the first file -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<!-- modified identity template for second/other document -->
<xsl:template match="*" mode="sec"> <!-- This copies all 'book' elements from the second file -->
<xsl:element name="{name()}">
<xsl:apply-templates select="#*" />
<xsl:attribute name="source"><xsl:value-of select="$secondName" /></xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:element>
</xsl:template>
<xsl:template match="/books"> <!-- This initiates the copying of both files -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<xsl:apply-templates select="$second/books/*" mode="sec" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Convert an xml array to another type of xml array using xslt

I've two xml files. Both are arrays of
person (having a lot of nodes underneath)
I need to convert this one
<result>
<sfobject>
<id></id>
<type>CompoundEmployee</type>
<person>
<lot of nodes/>
</person>
</sfobject>
<sfobject>
<id></id>
<type>CompoundEmployee</type>
<person>
<lot of nodes/>
</person>
</sfobject>
</result>
to
<queryCompoundEmployeeResponse>
<CompoundEmployee>
<id></id>
<person>
<lot of nodes/>
</person>
</CompoundEmployee>
<CompoundEmployee>
<id></id>
<person>
<lot of nodes/>
</person>
</CompoundEmployee>
</queryCompoundEmployeeResponse>
using xslt. I've this xslt for it.
<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="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="sfobject/*[1]">
<queryCompoundEmployeeResponse>
<CompoundEmployee>
<id>
<xsl:value-of select="#id" />
</id>
<xsl:copy-of select="/*/person[1]" />
<xsl:call-template name="identity" />
</id>
</CompoundEmployee>
</queryCompoundEmployeeResponse>
</xsl:template>
<xsl:template match="/*/sfobject[1]" />
<xsl:param name="removeElementsNamed" select="'type'"/>
</xsl:stylesheet>
This doesn't check out well. I've done this before in groovy, but now this has to be converted to xslt as the system changed. I'm new to xslt and I'm sure I am to use an advanced level of xslt here. Any pointers are highly appreciated.
Is xslt the right tool at all here? Or should I stick to groovy?
You have three rules you need to implement, it seems
Rename result to queryCompoundEmployeeResponse
Rename sfobject to whatever is held in type
Remove type from under sfobject
Happuly, each rule here can actually be implemented as a separate template.
So, for rule 1, you do this...
<xsl:template match="result">
<queryCompoundEmployeeResponse>
<xsl:apply-templates />
</queryCompoundEmployeeResponse>
</xsl:template>
For rule 2, do this...
<xsl:template match="sfobject">
<xsl:element name="{type}">
<xsl:apply-templates select="node()|#*" />
</xsl:element>
</xsl:template>
And for rule 3, do this...
<xsl:template match="sfobject/type" />
You then use the identity template to handle all other nodes and attributes.
Try this 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:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<queryCompoundEmployeeResponse>
<xsl:apply-templates />
</queryCompoundEmployeeResponse>
</xsl:template>
<xsl:template match="sfobject">
<xsl:element name="{type}">
<xsl:apply-templates select="node()|#*" />
</xsl:element>
</xsl:template>
<xsl:template match="sfobject/type" />
</xsl:stylesheet>

storing two xslt variables in 1 xslt variable

I need help where I can store two xslt variables in 1 array like variable to be used later
<xsl:variable name="edgeDeviceArray">
<Item><xsl:value-of select="$edgeDev1" /></Item>
<Item><xsl:value-of select="$edgeDev2" /></Item>
</xsl:variable>
But the output of the above seems to be concatenation. I would like to refer later like edgeDeviceArray[1]...
Here is a (non-working) fragment of my stylesheet demonstrating what I am trying to do
<xsl:variable name="edgeDev1"
select="$deviceDoc/x:config/t:devices/t:device[t:address=$edgeDev1IP]/t:name" />
<xsl:variable name="edgeDev2"
select="$deviceDoc/x:config/t:devices/t:device[t:address=$edgeDev2IP]/t:name" />
<xsl:variable name="xrSet" select="$xrDeviceDoc/x:config/t:devices/t:device-module/t:devices" />
<xsl:for-each select="$xrSet">
<xsl:variable name="asideDoc"
select="document(concat($edgeDevice[position()], '.xml'))" />
</xsl:for-each>
Here, I am reading the devices names from 1 doc based on certain attributes if they match.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:t="http://tail-f.com/ns/ncs"
xmlns:x="http://tail-f.com/ns/config/1.0"
xmlns:y="http://tail-f.com/ned/alu-sr"
xmlns:a="http://tail-f.com/ned/cisco-ios-xr"
xmlns:m="http://mask.data"
xmlns:im="http://inverse-mask.data" exclude-result-prefixes="xsl t x y"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="deviceDoc" select="document('devices.xml')" />
<xsl:variable name="edgeDev1IP"
select="../y:sdp[y:sdp-id=$sdpSet[1]]/y:far-end" />
<xsl:variable name="edgeDev1"
select="$deviceDoc/x:config/t:devices/t:device[t:address=$edgeDev1IP]/t:name" />
<xsl:variable name="edgeDev2IP"
select="../y:sdp[y:sdp-id=$sdpSet[2]]/y:far-end" />
<xsl:variable name="edgeDev2"
select="$deviceDoc/x:config/t:devices/t:device[t:address=$edgeDev2IP]/t:name" />
<xsl:variable name="xrDeviceDoc" select="document('xrDevices.xml')" />
<xsl:variable name="xrSet"
select="$xrDeviceDoc/x:config/t:devices/t:device-module/t:devices" />
<xsl:variable name="edgeDeviceArray">
<item><xsl:value-of select="$edgeDev1" /></item>
<item><xsl:value-of select="$edgeDev2" /></item>
</xsl:variable>
<anurag><xsl:value-of select="exsl:node-set($edgeDeviceArray)/item[1]" /></anurag>
</xsl:template>
</xsl:stylesheet>
I would like to refer later like edgeDeviceArray[1]
That won't work for two reasons:
There's only one edgeDeviceArray. In order to refer to its first item, you would need to use something like $edgeDeviceArray/item[1];
In XSLT 1.0, your variable is a result tree fragment, and must be converted to a node-set before its contents can be addressed by XPath.
Here's a minimized example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="myVar">
<item>A</item>
<item>B</item>
</xsl:variable>
<xsl:template match="/">
<result>
<xsl:value-of select="exsl:node-set($myVar)/item[2]"/>
</result>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<result>B</result>

Resources