I am using a test-driven-development process to create some xslt to create kml data dynamically.
With xslt I use xspec to manage the tests.
And I am experiencing a default namespace problem.
The xsl stylesheet has a large number of namespace declarations
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom"
exclude-result-prefixes="xs xd kml gx atom"
version="1.0">
The xpec test file has a simpler declaration
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec"
Part of the xslt code copies swathes of kml style elements into the output stream, e.g.
<StyleMap id="msn_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#sn_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#sh_ylw-pushpin</styleUrl>
</Pair>
</StyleMap>
This is defined in an element.
In the xspec test file I am specifying what kml to expect and I just copy and paste this code into the appropriate element.
But the test fails as what is generated contains an empty xmlns="" namespace declaration, e.g.
XSPEC file
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec" stylesheet="KMLLibraryTest.xsl">
<x:scenario label="just a test">
<x:call template="make-ghost-namespace"/>
<x:expect label="Test output">
<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Las Salinetas Port Limits</name>
<StyleMap id="msn_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#sn_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#sh_ylw-pushpin</styleUrl>
</Pair>
</StyleMap>
<Placemark id="{generate-id()}">
<Snippet> </Snippet>
<name>Test</name>
<styleUrl>#msn_ylw-pushpin</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-25.3633333,46.9916667,0 -25.3566667,46.9383333,0 </coordinates>
</LineString>
</Placemark>
</Document>
</kml>
</x:expect>
</x:scenario>
</x:description>
XSL file
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom"
exclude-result-prefixes="xs xd kml gx atom"
version="1.0">
<xsl:variable name="test-style">
<StyleMap id="msn_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#sn_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#sh_ylw-pushpin</styleUrl>
</Pair>
</StyleMap>
</xsl:variable>
<xd:doc scope="component">
<xd:desc>
<xd:p>Testing the creation of ghost empty namespace declarations</xd:p>
</xd:desc>
</xd:doc>
<xsl:template name="make-ghost-namespace">
<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Las Salinetas Port Limits</name>
<xsl:copy-of select="$test-style"/>
<Placemark id="{generate-id()}">
<Snippet> </Snippet>
<name>Test</name>
<styleUrl>#msn_ylw-pushpin</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-25.3633333,46.9916667,0 -25.3566667,46.9383333,0 </coordinates>
</LineString>
</Placemark>
</Document>
</kml>
</xsl:template>
</xsl:stylesheet>
The single test fails as the actual output is
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Las Salinetas Port Limits</name>
<StyleMap xmlns="" id="msn_ylw-pushpin">
<Pair>
<key>normal</key>
<styleUrl>#sn_ylw-pushpin</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#sh_ylw-pushpin</styleUrl>
</Pair>
</StyleMap>
<Placemark id="d5">
<Snippet> </Snippet>
<name>Test</name>
<styleUrl>#msn_ylw-pushpin</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-25.3633333,46.9916667,0 -25.3566667,46.9383333,0 </coordinates>
</LineString>
</Placemark>
</Document>
</kml>
So what do I need to do in order avoid those default namespaces being created during testing?
EXPANSION:
So the first answer does work, but if I add another template to the sylesheet
<xsl:template name="make-network-link">
<xsl:param name="name"/>
<xsl:param name="uri"/>
<xsl:param name="vis" select="0"/>
<xsl:param name="open" select="0"/>
<xsl:param name="refresh"/>
<NetworkLink>
<name><xsl:value-of select="$name"/></name>
<visibility><xsl:value-of select="$vis"/></visibility>
<open><xsl:value-of select="$open"/></open>
<xsl:if test="$refresh">
<refreshVisibility><xsl:value-of select="$refresh"/></refreshVisibility>
</xsl:if>
<Link>
<href><xsl:value-of select="$uri"/></href>
</Link>
</NetworkLink>
</xsl:template>
and change the declaration to include the default namespace as well as suppress #default as follows
<xsl:stylesheet xmlns="http://www.opengis.net/kml/2.2"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2"
xmlns:atom="http://www.w3.org/2005/Atom"
exclude-result-prefixes="#default xs xd kml gx atom"
version="1.0">
and add another test to the xspec file
<x:scenario label="Create test Network link">
<x:call template="make-network-link">
<x:param name="name" select="'Test network link etc'"/>
<x:param name="uri" select="'http://kmlbalahblah.com/GE/TZFolder.kml'"/>
<x:param name="vis" select="'1'"/>
</x:call>
<x:expect label="Default Hardcoded links">
<NetworkLink>
<name>Test network link etc</name>
<visibility>1</visibility>
<open>0</open>
<Link>
<href>http://kmlbalahblah.com/GE/TZFolder.kml</href>
</Link>
</NetworkLink>
</x:expect>
</x:scenario>
The output produced still comes through with the default namespace, i.e.
<NetworkLink xmlns="http://www.opengis.net/kml/2.2">
<name>Test network link etc</name>
<visibility>1</visibility>
<open>0</open>
<Link>
<href>http://kmlbalahblah.com/GE/TZFolder.kml</href>
</Link>
</NetworkLink>
How do I suppress THAT namespace now?
Related
I'm trying to convert JSON to a specific XML format, all in one XSLT. (It doesn't have to be in one step, but, you know,...)
I can convert the JSON to generic XML from here: How to use XPath/XSLT fn:json-to-xml
Converting the resultant generic XML to the XML I want is then simple.
But I can't work out how to combine the XSLTs so I can do it in one step, do JSON-to-XML and then the XML transformation. I've tried with variables, include, import, but can't get it to work.
I suspect it's straightforward! It needs to be in (just) XSLT.
So, from the question linked to above, I start with JSON (in XML tags)
<root>
<data>{
"desc" : "Distances between several cities, in kilometers.",
"updated" : "2014-02-04T18:50:45",
"uptodate": true,
"author" : null,
"cities" : {
"Brussels": [
{"to": "London", "distance": 322},
{"to": "Paris", "distance": 265},
{"to": "Amsterdam", "distance": 173}
],...
and transform to
<map xmlns="http://www.w3.org/2005/xpath-functions">
<string key="desc">Distances between several cities, in kilometers.</string>
<string key="updated">2014-02-04T18:50:45</string>
<boolean key="uptodate">true</boolean>
<null key="author"/>
<map key="cities">
<array key="Brussels">
<map>
<string key="to">London</string>
<number key="distance">322</number>
</map>
<map>
<string key="to">Paris</string>
<number key="distance">265</number>
</map>...
using
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" version="3.0">
<xsl:output indent="yes"/>
<xsl:template match="data">
<xsl:copy-of select="json-to-xml(.)"/>
</xsl:template>
</xsl:stylesheet>
Now I can apply this stylesheet to the 'intermediate' XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.w3.org/2005/xpath-functions">
<xsl:output indent="yes"/>
<xsl:template match="/">
<Distances>
<xsl:for-each select="f:map/f:map/f:array">
<Start>
<StartPoint><xsl:value-of select="#key"/></StartPoint>
<xsl:for-each select="f:map">
<Distance>
<xsl:attribute name="end"><xsl:value-of select="f:string"/></xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="f:number"/></xsl:attribute>
</Distance>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
and get my desired structure:
<?xml version="1.0" encoding="UTF-8"?>
<Distances xmlns:f="http://www.w3.org/2005/xpath-functions">
<Start>
<StartPoint>Brussels</StartPoint>
<Distance end="London" value="322"/>
<Distance end="Paris" value="265"/>
<Distance end="Amsterdam" value="173"/>
</Start>...
So, is it possible to combine the JSON-to-XML and the XML transformation XSLs in one?
I am guessing you want to do:
XSLT 3.0
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="f">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/root">
<Distances>
<xsl:for-each select="json-to-xml(data)/f:map/f:map/f:array">
<Start>
<StartPoint>
<xsl:value-of select="#key"/>
</StartPoint>
<xsl:for-each select="f:map">
<Distance end="{f:string}" value="{f:number}"/>
</xsl:for-each>
</Start>
</xsl:for-each>
</Distances>
</xsl:template>
</xsl:stylesheet>
Untested, because no code suitable for testing was provided.
To do it the way you were proposing, you can do
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>
and then add template rules to transform the generic XML produced by json-to-xml() to your application-specific XML.
But I think the approach suggested by #michael.hor257k is probably better.
I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current code
``
<?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="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)
I have source xml looking like this :
<Data>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Data>
Which is to transform to this:
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic" />
</category>
I want to use variable :
<xsl:template match="ActionPlaces">
<xsl:variable name="id" select="/ActionPlace/ActionPlaceID"/>
<xsl:template match="CommonAction" >
<category name="<xsl:value-of select="#name"/> >
<name><xsl:value-of select="#name"/></name>
<parent <xsl:if test="/Action/ActionPlaceID = $id">
name=/Action/ActionPlaceID/> <- how to get name of theatre here?
</xsl:template>
Can variable store not only id but name also? And how to get it? What is the most common approach to handle this ?
Here's one option using XSL keys (as #michael-hor257k suggested):
Input
<Root>
<ActionPlaces>
<ActionPlace>
<ActionPlaceID>74</ActionPlaceID>
<PlaceName>Theatre Of Classic</PlaceName>
</ActionPlace>
</ActionPlaces>
<Actions>
<CommonAction Id="2075" Name="King">
<Action>
<ActionID>4706</ActionID>
<ActionPlaceID>74</ActionPlaceID>
</Action>
</CommonAction>
</Actions>
</Root>
Stylesheet
<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"/>
<!-- Collect all <ActionPlace> elements into an XSL key -->
<xsl:key name="ActionPlaceById" match="ActionPlace" use="ActionPlaceID"/>
<xsl:template match="/">
<xsl:apply-templates select="Root/Actions/CommonAction"/>
</xsl:template>
<xsl:template match="CommonAction">
<category name="{#Name}">
<name>
<xsl:value-of select="#Name"/>
</name>
<!--
Using the ActionPlaceById key we created earlier, fetch the <ActionPlace>
element that has an <ActionPlaceID> child that has the same value as the
<ActionPlaceID> descendant of the current <CommonAction> element.
-->
<parent name="{key('ActionPlaceById', Action/ActionPlaceID)/PlaceName}"/>
</category>
</xsl:template>
</xsl:stylesheet>
Output
<?xml version="1.0" encoding="utf-8"?>
<category name="King">
<name>King</name>
<parent name="Theatre Of Classic"/>
</category>
I have a xml document which looks like
<!-- language: lang-xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Request xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Header>
<AccountServer e-dtype="int">3</AccountServer>
</Header>
<Response>
<ResponseList e-dtype="list">
<Response>
<RequestId e-dtype="string">ServiceOrderGetRef</RequestId>
<RequestObjName e-dtype="string">ServiceOrder</RequestObjName>
<ServiceOrder>
<CreateDt e-dtype="dateTime">2014-03-01 00:00:00</CreateDt>
<CreateWho e-dtype="string">vc</CreateWho>
<WorkflowStartDt e-dtype="dateTime">2014-04-01 00:00:00</WorkflowStartDt>
</ServiceOrder>
</Response>
<Response>
<ComponentList e-dtype="list"/>
<Count e-dtype="int">0</Count>
<RequestId e-dtype="string">ComponentFindRef</RequestId>
<RequestObjName e-dtype="string">Component</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<CustomerContractList e-dtype="list"/>
<RequestId e-dtype="string">CustomerContractRef</RequestId>
<RequestObjName e-dtype="string">CustomerContract</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<ProductList e-dtype="list"/>
<RequestId e-dtype="string">ProductRef</RequestId>
<RequestObjName e-dtype="string">Product</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
<Response>
<Count e-dtype="int">0</Count>
<NrcList e-dtype="list"/>
<RequestId e-dtype="string">NrcFindRef</RequestId>
<RequestObjName e-dtype="string">Nrc</RequestObjName>
<TotalCount e-dtype="int">0</TotalCount>
</Response>
</ResponseList>
</Response>
</Request>
I am using copy-of function to copy node ServiceOrder within another xml document
I want to modify text of node WorkFlowStartDt and CreateDt and then do a copy-of. How can I do this?
My copied serviceorder node should look like this after modifing text. Below is the result xml
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Fetch e-dtype="boolean">true</Fetch>
<Order>
<AccountInternalId e-dtype="int">12345</AccountInternalId>
<Key>
<OrderId e-dtype="numeric">12345678</OrderId>
</Key>
</Order>
<ServiceOrderList e-dtype="list">
<ServiceOrder xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance">
<CreateDt e-dtype="dateTime">2014-03-02 00:00:00</CreateDt>
<CreateWho e-dtype="string">vc</CreateWho>
<WorkflowStartDt e-dtype="dateTime">2014-05-01 00:00:00</WorkflowStartDt>
</ServiceOrder>
</ServiceOrderList>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>
Below is my xslt processor file
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess> <Order> <AccountInternalId e-dtype="int">
<xsl:value-of
select="/Request/Response/ResponseList/Response/ServiceOrder/AccountInternalId"/>
</AccountInternalId> <Key> <OrderId e-dtype="numeric"> <xsl:value-of select="/Request/Response/ResponseList/Response/ServiceOrder/OrderId"/>
</OrderId>
</Key>
</Order>
<ServiceOrderList e-dtype="list">
<xsl:copy-of select="/Request/Response/ResponseList/Response/ServiceOrder"/>
</ServiceOrderList>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</xsl:if>
</RequestList>
</CustomerUdtRequest>
</Request>
</xsl:template>
</xsl:stylesheet>
I want to modify text of node WorkFlowStartDt and CreateDt and then do
a copy-of.
That would be an unnecessary complication. You can modify nodes while you add them to the output tree.
Since you essentially want to copy everything "as is" except two nodes, it would be best to start with an identity transform template, then add an "exception" template for the two specific nodes that need modifying (one template for both, since the modification is identical).:
<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"/>
<!-- identity transformation -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="CreateDt | WorkFlowStartDt">
<xsl:copy>
<xsl:value-of select="concat('**', ., '**')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Added:
In response to the edited question, try this stylesheet:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="fst"
exclude-result-prefixes="ns">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Order>
<AccountInternalId e-dtype="int">
<!-- THIS DOESN'T POINT TO ANY EXISTING NODE!! -->
<xsl:value-of select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder/ns:AccountInternalId"/>
</AccountInternalId>
<Key>
<OrderId e-dtype="numeric">
<!-- THIS DOESN'T POINT TO ANY EXISTING NODE!! -->
<xsl:value-of select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder/ns:OrderId"/>
</OrderId>
</Key>
</Order>
<xsl:apply-templates select="ns:Request/ns:Response/ns:ResponseList/ns:Response/ns:ServiceOrder"/>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>
</xsl:template>
<xsl:template match="ns:ServiceOrder">
<xsl:copy>
<xsl:copy-of select="ns:CreateWho"/>
<xsl:apply-templates select="ns:CreateDt | ns:WorkflowStartDt"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ns:CreateDt | ns:WorkflowStartDt">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="concat('**', ., '**')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your input (minus the illegal opening comment), the following result is obtained:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<OperatorName e-dtype="string">ws</OperatorName>
<ApplicationName e-dtype="string">ws</ApplicationName>
</Header>
<CustomerUdtRequest>
<RequestList e-dtype="list">
<LogicalServiceOrder>
<RequestId e-dtype="string">MyExistingOrder</RequestId>
<LogicalServiceOrderPreProcess>
<Order>
<AccountInternalId e-dtype="int"/>
<Key>
<OrderId e-dtype="numeric"/>
</Key>
</Order>
<ServiceOrder xmlns="fst" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CreateWho e-dtype="string">vc</CreateWho>
<CreateDt e-dtype="dateTime">**2014-03-01 00:00:00**</CreateDt>
<WorkflowStartDt e-dtype="dateTime">**2014-04-01 00:00:00**</WorkflowStartDt>
</ServiceOrder>
</LogicalServiceOrderPreProcess>
</LogicalServiceOrder>
</RequestList>
</CustomerUdtRequest>
</Request>
I need to do a conditional sum using XSLT. The sum of 'Oty' for each 'SKU' should be calculated only for providers listed within the 'Provider' node. In the provided example, the Qty for providerCode 4 should be skipped as its not in the 'Providers' list. I'm restricted to using XSLT 1.0.
I would appreciate any help. Thanks!
Here is the sample XML.
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Providers>
<ProviderCode>1</ProviderCode>
<ProviderCode>2</ProviderCode>
<ProviderCode>3</ProviderCode>
</Providers>
<SKU>
<SKU>XYZ</SKU>
<Description>XYZ Description</Description>
<Provider>
<ProviderCode>1</ProviderCode>
<Qty>100</Qty>
</Provider>
<Provider>
<ProviderCode>2</ProviderCode>
<Qty>67</Qty>
</Provider>
<Provider>
<ProviderCode>3</ProviderCode>
<Qty>74</Qty>
</Provider>
<Provider>
<ProviderCode>4</ProviderCode>
<Qty>62</Qty>
</Provider>
</SKU>
<SKU>
<SKU>ABC</SKU>
<Description>ABC Description</Description>
<Provider>
<ProviderCode>1</ProviderCode>
<Qty>20</Qty>
</Provider>
<Provider>
<ProviderCode>2</ProviderCode>
<Qty>77</Qty>
</Provider>
<Provider>
<ProviderCode>3</ProviderCode>
<Qty>42</Qty>
</Provider>
<Provider>
<ProviderCode>4</ProviderCode>
<Qty>631</Qty>
</Provider>
</SKU>
</Root>
Here is the required output.
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<SKU>
<SKU>XYZ</SKU>
<Qty>241</Qty>
</SKU>
<SKU>
<SKU>ABC</SKU>
<Qty>139</Qty>
</SKU>
</Root>
You can simply use sum on the nodes you want, either by comparing sum(Provider[ProviderCode = //Providers/ProviderCode]/Qty) or by using a key:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="prov" match="Providers/ProviderCode" use="."/>
<xsl:template match="Root">
<xsl:copy>
<xsl:apply-templates select="SKU"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Root/SKU">
<xsl:copy>
<xsl:copy-of select="SKU"/>
<Qty><xsl:value-of select="sum(Provider[key('prov', ProviderCode)]/Qty)"/></Qty>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>