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>
Related
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
What would be xsl 3.0 way in order to add <string key="name">abc</string> to the map after <string key="value">1001</string>?
What about deleting array element e.g. <number>1050.0</number>?
Thanks!
<?xml version="1.0"?>
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="content">
<map key="Details">>
<number key="id">70805774</number>
<string key="value">1001</string>
<array key="position">
<number>1004.0</number>
<number>288.0</number>
<number>1050.0</number>
<number>324.0</number>
</array>
</map>
</array>
</map>
XML to XML transformation requiring a few changes is usually done with the identity transformation as the base template (declared in XSLT 3 as <xsl:mode on-no-match="shallow-copy"/>) and then by adding templates for the changes you want to make e.g. add an element
<?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"
xpath-default-namespace="http://www.w3.org/2005/xpath-functions"
xmlns="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="string[#key = 'value'][. = '1001']">
<xsl:next-match/>
<string key="name">abc</string>
</xsl:template>
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93nwMo6
or delete one with <xsl:template match="array[#key = 'position']/number[. = 1050]"/> : https://xsltfiddle.liberty-development.net/93nwMo6/1
You can the freely combine various changes by adding more templates:
<?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"
xpath-default-namespace="http://www.w3.org/2005/xpath-functions"
xmlns="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="string[#key = 'value'][. = '1001']">
<xsl:next-match/>
<string key="name">abc</string>
</xsl:template>
<xsl:template match="array[#key = 'position']/number[. = 1050]"/>
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93nwMo6/2
Another approach would be to treat the data as XPath 3.1 maps and arrays and use the functions on those types; an intermediary result (the input sample you posted first needs to be corrected to have the key attribute on the map removed) would be:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:template match="/">
<xsl:sequence
select="let $map := . => xml-to-json() => parse-json(),
$content := $map?content,
$contentMap := $content?1,
$newContentMap := $contentMap => map:put('name', 'abc'),
$newContentMap := $newContentMap => map:put('position', array:remove($contentMap?position, 3))
return $map => map:put('content', array:put($content, 1, $newContentMap))"/>
</xsl:template>
<xsl:output method="json" indent="yes"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93nwMo6/3
if you want to get back to the XML representation you can use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.w3.org/2005/xpath-functions"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:template match="/">
<xsl:sequence
select="let $map := . => xml-to-json() => parse-json(),
$content := $map?content,
$contentMap := $content?1,
$newContentMap := $contentMap => map:put('name', 'abc'),
$newContentMap := $newContentMap => map:put('position', array:remove($contentMap?position, 3)),
$map := $map => map:put('content', array:put($content, 1, $newContentMap))
return $map => serialize(map { 'method' : 'json' }) => json-to-xml()"/>
</xsl:template>
<xsl:output method="xml" indent="yes"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/93nwMo6/4
The order of "object"/map items is not defined/preserved with XDM maps.
Try
<xsl:template match="string[key='value']">
<xsl:copy-of select="."/>
<string key="value">1001</string>
</xsl:template>
<xsl:template match="array/number[3]"/>
Assuming the correct namespace declarations.
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>
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>
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>