Sort nodes in XML using DOM parser - java

How can I sort the XML nodes according to the tag and append in
the new XML using DOM parser or can it be done using DOM parser. We've
used DOM parser extensively for appending nodes into a new file but I am not able to sort the nodes.
Any help would be highly appreciated.
Input.xml
<rss version="2.0">
<Configs>
<Value>defaultValue</Value>
<Config name="test1">
<title>Title 1</title>
<author>Author1</author>
<value>5600</value>
<order>02</order>
</Config>
<Config name="test2">
<title>Title 2</title>
<author>Author2</author>
<Value>6100</Value>
<order>01</order>
</Config>
</Configs>
<Ratings>
<body>
<Items name="ac_object1">
<something1>something1</something1>
<value>someValue1</value>
<order>02</order>
</Items>
<Items name="op_object2">
<something1>something2</something1>
<value>someValue2</value>
<order>03</order>
</Items>
<Items name="vt_object3">
<something1>something3</something1>
<value>someValue3</value>
<order>01</order>
</Items>
</body>
</Ratings>
</rss>
Expected Output.xml
<rss version="2.0">
<Configs>
<Value>defaultValue</Value>
<Config name="test2">
<title>Title 2</title>
<author>Author2</author>
<Value>6100</Value>
<order>01</order>
</Config>
<Config name="test1">
<title>Title 1</title>
<author>Author1</author>
<value>5600</value>
<order>02</order>
</Config>
</Configs>
<Ratings>
<body>
<Items name="vt_object3">
<something1>something3</something1>
<value>someValue3</value>
<order>01</order>
</Items>
<Items name="ac_object1">
<something1>something1</something1>
<value>someValue1</value>
<order>02</order>
</Items>
<Items name="op_object2">
<something1>something2</something1>
<value>someValue2</value>
<order>03</order>
</Items>
</body>
</Ratings>
</rss>

You really don't want to do this using low-level DOM interfaces. Here's how to do it in XSLT 3.0 (which you can call from Java after installing Saxon-HE):
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform> version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[*/order]">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="number(order)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:transform>
With a few extra lines of code you could also do it using XSLT 1.0, which comes bundled with the JDK.
How it works:
The xsl:mode declaration says that the default action for elements is to copy the element and then process its children
xsl:strip-space says ignore whitespace in the input
xsl:output says add indentation in the output
The xsl:template rule says that when processing an element that has order elements among its grandchildren, copy the start and end tag, and process the children in sorted order of the numeric value of their order child element.

Related

How to retrieve duplicate nodes from XML using xpath or java script

Below is my input xml.
<?xml version="1.0" encoding="UTF-8"?>
<Hierarchy>
<Records>
<Org_Unit_Name>ABC</Org_Unit_Name>
<Parent_Org_Unit>123</Parent_Org_Unit>
</Records>
<Records>
<Org_Unit_Name>ABC</Org_Unit_Name>
<Parent_Org_Unit>DEF</Parent_Org_Unit>
</Records>
<Records>
<Org_Unit_Name>456</Org_Unit_Name>
<Parent_Org_Unit>879</Parent_Org_Unit>
</Records>
</Hierarchy>
I would like to extract only duplicate values. so the output should be as below
<?xml version="1.0" encoding="UTF-8"?>
<Hierarchy>
<Records>
<Org_Unit_Name>ABC</Org_Unit_Name>
<Parent_Org_Unit>123</Parent_Org_Unit>
</Records>
<Records>
<Org_Unit_Name>ABC</Org_Unit_Name>
<Parent_Org_Unit>DEF</Parent_Org_Unit>
</Records>
I tried preceding axes in xpath/xslt, but of no use and unique(false) in java script but I am unable to retrieve the expected output. Please guide me how to proceed forward.
Regards,
Amuktha
Assuming that "duplicate" means a Records element with the same Org_Unit_Name value, you could use a variation of Muenchian grouping:
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:key name="Records-by-Org_Unit_Name" match="Records" use="Org_Unit_Name" />
<xsl:template match="/Hierarchy">
<xsl:copy>
<xsl:copy-of select="Records[count(key('Records-by-Org_Unit_Name', Org_Unit_Name)) > 1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to copy an XML node and paste in same level using XLS transformations

I wanted to copy a node of an xml and paste it in the same level.
Consider I have an xml as below.
<MyXml>
<system>
<Groups>
<Group id="01" check="true">
<name>Value</name>
<age>test</age>
</Group>
<Group id="02" check="true">
<name>Value</name>
<age>test</age>
</Group>
<Group id="03" check="true">
<name>Value</name>
<age>test</age>
</Group>
</Groups>
</system>
</MyXml>
I wanted to copy Group 03 and paste in the same level(inside groups) as "04" using XSL Transformations.
Expected Output
<MyXml>
<system>
<Groups>
<Group id="01" check="true">
<name>Value</name>
<age>test</age>
</Group>
<Group id="02" check="true">
<name>Value</name>
<age>test</age>
</Group>
<Group id="03" check="true">
<name>Value</name>
<age>test</age>
</Group>
<Group id="04" check="true">
<name>Value</name>
<age>test</age>
</Group>
</Groups>
</system>
</MyXml>
Can somebody please help on completing the XSL stylesheet for the same. Not sure whether the below xsl in correct. Thanks in advance.
<?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" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="groupId" />
<xsl:param name="newGroupId" />
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MyXML/system/Groups/Group[#id=$groupId]" >
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<!--Wanted to do something for pasting the copied node and changing the id value with new Group Id.-->
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, it is actually considered an error to have a variable expression in a template match (although you may find some processors allow it).
But what you probably should do, is call the identity template in the template matching Group, and then have an xsl:if to decide whether to copy it.
Try this template instead
<xsl:template match="Group" >
<xsl:call-template name="identity" />;
<xsl:if test="#id = $groupId">
<group id="{$newGroupId}">
<xsl:apply-templates select="#*[name() != 'id']|node()"/>
</group>
</xsl:if>
</xsl:template>
Note you don't need the full path to Group in your template match, not unless there are Group elements in other levels which you don't want to match. (Additionally, your current match was referring to MyXML, when your XML has it as MyXml. XSLT is case-sensitive, so this wouldn't have matched).

Convert xml to csv with xsl java

i am following this link
i want to convert an xml to csv
i have an xml and written and xsl for it
openning xml with href to the xsl work on internet explorer
but runing the code from this link return an error
Can not resolve namespace prefix: xmlns
what should fix it ?
i have a working xml & xsl
xml
<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="student2.xsl"?>
<rankings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ign="http://api.DomainName.com/2.0" count="438" total="438" offset="0" xsi:schemaLocation="http://api.DomainName.com/2.0 http://api.DomainName.com/2.0/api.xsd" >
<ranking keyword="80s fancy dress">
<ranks>
<rank week="201526" country="uk" searchengine="google_uk_en">NR</rank>
<rank week="201527" country="uk" searchengine="google_uk_en">NR</rank>
<rank week="201528" country="uk" searchengine="google_uk_en">NR</rank>
<rank week="201529" country="uk" searchengine="google_uk_en">NR</rank>
<rank week="201530" country="uk" searchengine="google_uk_en">NR</rank>
<rank week="201531" country="uk" searchengine="google_uk_en">NR</rank>
</ranks>
</ranking>
</rankings>
xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://api.DomainName.com/2.0" exclude-result-prefixes=xmlns>
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes" indent="yes" />
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr bgcolor="#9acd32">
</tr>
<xsl:for-each select="rankings/ranking/ranks/rank">
<tr>
<keyword><xsl:value-of select="../../#keyword"/></keyword>
<xsl:text>,</xsl:text>
<week><xsl:value-of select="#week"/></week>
<xsl:text>,</xsl:text>
<country><xsl:value-of select="#country"/></country>
<xsl:text>,</xsl:text>
<searchengine><xsl:value-of select="#searchengine"/></searchengine>
<xsl:text>,</xsl:text>
<rank><xsl:value-of select="../rank"/></rank>
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
java code
after running the java code from the above link
i get an error
(Location of error unknown)org.xml.sax.SAXException: Can not resolve namespace prefix: xmlns
Exception in thread "main" java.lang.NullPointerException
at org.apache.xalan.transformer.TransformerImpl.createSerializationHandler(TransformerImpl.java:1171)
at org.apache.xalan.transformer.TransformerImpl.createSerializationHandler(TransformerImpl.java:1060)
at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1268)
at org.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1251)
what should fix it ?
To eliminate the error, you need to remove this:
exclude-result-prefixes=xmlns
from your xsl:stylesheet tag. You also need to remove the default namespace declaration:
xmlns="http://api.DomainName.com/2.0"
otherwise all your output will be placed in that namespace, which you most certainly don't want to happen if - as it seems - you want it to be HTML.
There are other changes you need to make - for example, if you want to output an HTML table, set the output method to "html" instead of "text", and make sure your table structure is valid.
Note:
Your XML declares a namespace: xmlns:ign="http://api.DomainName.com/2.0" but this namespace declaration isn't used anywhere. Therefore your stylesheet can ignore it. Provided you are showing us a representative sample of the real XML.

Xpath with Default Namespace

I am trying to get all values of a certain XML element. However, the namespace is not defined. I've tried using the local-name() function but am not having any luck.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base="https://www.website.com" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<id>A</id>
<title type="text"></title>
<updated>2015-07-21T02:40:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Application" href="A(1347)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/b" type="application/atom+xml;type=feed" title="B" href="A(1347)/B" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/C" type="application/atom+xml;type=feed" title="C" href="A(1347)/C" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/D" type="application/atom+xml;type=entry" title="D" href="A(1347)/D" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/E" type="application/atom+xml;type=feed" title="E" href="A(1347)/E">
<m:inline>
<feed>
<title type="text">E</title>
<id>1347</id>
<updated>2015-07-21T02:40:30Z</updated>
<link rel="self" title="E" href="A(1347)/E" />
<entry>
<id>www.website.com/</id>
<title type="text"></title>
<updated>2015-07-21T02:40:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="E" href="E(4294)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/A" type="application/atom+xml;type=entry" title="Application" href="E(4294)/A" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/D" type="application/atom+xml;type=entry" title="D" href="E(4294)/D" />
<category term="APIModel.FileBaseDocuments" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ID m:type="Edm.Int32">4294</d:ID>
<d:Type>123</d:Type>
</m:properties>
</content>
</entry>
<entry>
<id>www.website.com</id>
<title type="text"></title>
<updated>2015-07-21T02:40:30Z</updated>
<author>
<name />
</author>
<link rel="edit" title="E" href="E(4295)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/A" type="application/atom+xml;type=entry" title="A" href="E(4295)/A" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/D" type="application/atom+xml;type=entry" title="D" href="E(4295)/D" />
<category term="APIModel.FileBaseDocuments" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ID m:type="Edm.Int32">4295</d:ID>
<d:Type>456</d:Type>
</m:properties>
</content>
</entry>
</feed>
</m:inline>
</link>
</entry>
I want to retrieve all values inside of "m:properties/d:ID m:type="Edm.Int32" (in this case 4294) but I am getting no luck. So for one file, there would be one "feed" tag filled with multiple "entry" tags. Inside these tags there would be one "m:properties/d:ID m:type="Edm.Int32" which I need to retrieve. Any suggestion on what the correct xPath would be for this situation?
Instead of resorting to namespace agnostic xml by using local-name(), why not register a namespace for the default namespace, e.g. x prefix for xmlns:x="http://www.w3.org/2005/Atom", and then your Xpath would be something like:
//x:feed/x:entry/x:content/m:properties/d:ID[#m:type='Edm.Int32']
The local-name() approach is more verbose:
//*[local-name()='feed']/*[local-name()='entry']/*[local-name()='content']
/*[local-name()='properties']/*[local-name()='ID' and #m:type='Edm.Int32']
Example of both approaches here
Use the #exclude-result-prefixes attribute to filter out the extraneous namespaces in your output. You don't need to declare the http://www.w3.org/2005/Atom in your transform, unless you need it for other purposes, but even so, it is probably a good thing.
Just match on expression m:properties/d:ID[#m:type='Edm.Int32'] to get your required data.
For example, this transform, when applied to your given input document ....
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
xmlns:atom="http://www.w3.org/2005/Atom"
version="2.0"
exclude-result-prefixes="xsl d m atom">
<xsl:output encoding="utf-8" omit-xml-declaration="yes" indent="yes" />
<xsl:template match="/">
<Edm.Int32s>
<xsl:apply-templates />
</Edm.Int32s>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
<xsl:template match="m:properties/d:ID[#m:type='Edm.Int32']">
<value><xsl:value-of select="text()" /></value>
</xsl:template>
</xsl:stylesheet>
... yields output ....
<edm.int32s>
<value>4294</value>
<value>4295</value>
</edm.int32s>
Note
I have assumed from your question tags that you want an XSLT transform. If you just wanted a simple XPath expression to apply directly to the document, you could use ...
//m:properties/d:ID[#m:type='Edm.Int32']/text()
(passing, of course, the namespace declarations for m and d).

Populate XML template-file from XPath Expressions?

What would be the best way to populate (or generate) an XML template-file from a mapping of XPath expressions?
The requirements are that we will need to start with a template (since this might contain information not otherwise captured in the XPath expressions).
For example, a starting template might be:
<s11:Envelope xmlns:s11='http://schemas.xmlsoap.org/soap/envelope/'>
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
<article xmlns:ns1='http://predic8.com/material/1/'>
<name>?XXX?</name>
<description>?XXX?</description>
<price xmlns:ns1='http://predic8.com/common/1/'>
<amount>?999.99?</amount>
<currency xmlns:ns1='http://predic8.com/common/1/'>???</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/1/'>???</id>
</article>
</ns1:create>
</s11:Body>
</s11:Envelope>
Then we are supplied, something like:
expression: /create/article[1]/id => 1
expression: /create/article[1]/description => bar
expression: /create/article[1]/name[1] => foo
expression: /create/article[1]/price[1]/amount => 00.00
expression: /create/article[1]/price[1]/currency => USD
expression: /create/article[2]/id => 2
expression: /create/article[2]/description => some name
expression: /create/article[2]/name[1] => some description
expression: /create/article[2]/price[1]/amount => 00.01
expression: /create/article[2]/price[1]/currency => USD
We should then generate:
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
<article xmlns:ns1='http://predic8.com/material/1/'>
<name xmlns:ns1='http://predic8.com/material/1/'>foo</name>
<description>bar</description>
<price xmlns:ns1='http://predic8.com/common/1/'>
<amount>00.00</amount>
<currency xmlns:ns1='http://predic8.com/common/1/'>USD</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/1/'>1</id>
</article>
<article xmlns:ns1='http://predic8.com/material/2/'>
<name>some name</name>
<description>some description</description>
<price xmlns:ns1='http://predic8.com/common/2/'>
<amount>00.01</amount>
<currency xmlns:ns1='http://predic8.com/common/2/'>USD</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/2/'>2</id>
</article>
</ns1:create>
I am implemented in Java, although I would prefer an XSLT-based solution if one is possible.
PS: This question is the reverse of another question I recently asked.
This transformation creates from the "expressions" an XML document that has the structure of the wanted result -- it remains to transform this result into the final result:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vPop" as="element()*">
<item path="/create/article[1]/id">1</item>
<item path="/create/article[1]/description">bar</item>
<item path="/create/article[1]/name[1]">foo</item>
<item path="/create/article[1]/price[1]/amount">00.00</item>
<item path="/create/article[1]/price[1]/currency">USD</item>
<item path="/create/article[1]/price[2]/amount">11.11</item>
<item path="/create/article[1]/price[2]/currency">AUD</item>
<item path="/create/article[2]/id">2</item>
<item path="/create/article[2]/description">some name</item>
<item path="/create/article[2]/name[1]">some description</item>
<item path="/create/article[2]/price[1]/amount">00.01</item>
<item path="/create/article[2]/price[1]/currency">USD</item>
</xsl:variable>
<xsl:template match="/">
<xsl:sequence select="my:subTree($vPop/#path/concat(.,'/',string(..)))"/>
</xsl:template>
<xsl:function name="my:subTree" as="node()*">
<xsl:param name="pPaths" as="xs:string*"/>
<xsl:for-each-group select="$pPaths"
group-adjacent=
"substring-before(substring-after(concat(., '/'), '/'), '/')">
<xsl:if test="current-grouping-key()">
<xsl:choose>
<xsl:when test=
"substring-after(current-group()[1], current-grouping-key())">
<xsl:element name=
"{substring-before(concat(current-grouping-key(), '['), '[')}">
<xsl:sequence select=
"my:subTree(for $s in current-group()
return
concat('/',substring-after(substring($s, 2),'/'))
)
"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current-grouping-key()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the result is:
<create>
<article>
<id>1</id>
<description>bar</description>
<name>foo</name>
<price>
<amount>00.00</amount>
<currency>USD</currency>
</price>
<price>
<amount>11.11</amount>
<currency>AUD</currency>
</price>
</article>
<article>
<id>2</id>
<description>some name</description>
<name>some description</name>
<price>
<amount>00.01</amount>
<currency>USD</currency>
</price>
</article>
</create>
Note:
You need to transform the "expressions" you are given into the format used in this transformation -- this is easy and straightforward.
In the final transformation you need to copy every node "as-is" (using the identity rule), with the exception that the top node should be generated in the "http://predic8.com/wsdl/material/ArticleService/1/" namespace. Note that the other namespaces present in the "template" are not used and can be safely ommitted.
This solution requires you to re-organise your XPATH input information slightly, and to allow a 2-step transformation. The first transformation will write the stylesheet, which will be executed in the second transformation - Thus the client is required to do two invocations of the XSLT engine. Let us know if this is a problem.
Step One
Please re-organise your XPATH information into an XML document like so. It should not be difficult to do, and even an XSLT script could be written to do the job.
<paths>
<rule>
<match>article[1]/id[1]</match>
<namespaces>
<namespace prefix="ns1">http://predic8.com/wsdl/material/ArticleService/1/</namespace>
<!-- The namespace node declares a namespace that is used in the match expression.
There can be many of these. It is not required to define the s11: namespace,
nor the ns1 namespace. -->
</namespaces>
<replacement>1</replacement>
</rule>
<rule>
<match>article[1]/description[1]</match>
<namespaces/>
<replacement>bar</replacement>
</rule>
... etc ...
</paths>
Solution constraints
In the above rules document we are constrained so that:
The match is implicitly prefixed 'expression: /create/'. Don't put that explicitly.
All matches must begin like article[n] where n is some ordinal number.
We can't have zero rules.
Any prefixes that you use in the match, other than s11="http://schemas.xmlsoap.org/soap/envelope/" and ns1="http://predic8.com/wsdl/material/ArticleService/1/". (Note: I don't think it is valid for namespaces to end in '/' - but not sure about that), are defined in the namespaces node.
The above is the input document to the step one transformation. Apply this document to this style-sheet ...
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:step2="http://www.w3.org/1999/XSL/Transform-step2"
xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes='xsl'>
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:namespace-alias stylesheet-prefix="step2" result-prefix="xsl"/>
<xsl:template match="/">
<step2:stylesheet version="2.0">
<step2:output method="xml" indent="yes" encoding="UTF-8" />
<step2:variable name="replicated-template" as="element()*">
<step2:apply-templates select="/" mode="replication" />
</step2:variable>
<step2:template match="#*|node()" mode="replication">
<step2:copy>
<step2:apply-templates select="#*|node()" mode="replication" />
</step2:copy>
</step2:template>
<step2:template match="/s11:Envelope/s11:Body/ns1:create/article" mode="replication">
<step2:variable name="replicant" select="." />
<step2:for-each select="for $i in 1 to
{max(for $m in /paths/rule/match return
xs:integer(substring-before(substring-after($m,'article['),']')))}
return $i">
<step2:for-each select="$replicant">
<step2:copy>
<step2:apply-templates select="#*|node()" mode="replication" />
</step2:copy>
</step2:for-each>
</step2:for-each>
</step2:template>
<step2:template match="#*|node()">
<step2:copy>
<step2:apply-templates select="#*|node()"/>
</step2:copy>
</step2:template>
<step2:template match="/">
<step2:apply-templates select="$replicated-template" />
</step2:template>
<xsl:apply-templates select="paths/rule" />
</step2:stylesheet>
</xsl:template>
<xsl:template match="rule">
<step2:template match="s11:Envelope/s11:Body/ns1:create/{match}">
<xsl:for-each select="namespaces/namespace">
<xsl:namespace name="{#prefix}" select="." />
</xsl:for-each>
<step2:copy>
<step2:apply-templates select="#*"/>
<step2:value-of select="'{replacement}'"/>
<step2:apply-templates select="*"/>
</step2:copy>
</step2:template>
</xsl:template>
</xsl:stylesheet>
Step Two
Apply your soap envelope file, as an input document, to the style-sheet which was output from step one. The result is the original soap document, altered as required. This is a sample of a step two style-sheet, with just the first rule (/create/article[1]/id => 1) being considered for the sake of simplicity of illustration.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/"
match="/s11:Envelope/s11:Body/ns1:create[1]/article[1]/id[1]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="'1'"/>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
More solution constraints
The template document must contain at least one /s11:Envelope/s11:Body/ns1:create/article . Only the article node is replicated (deeply) as required by rules. Other than than it can be any structure.
The template document cannot contain nested levels of the s11:Envelope/s11:Body/ns1:create node.
Explanation
You will notice that your XPATH expressions are not far removed from a match condition of template. Therefore it is not too difficult to write a stylesheet which re-expresses your XPATH and replacement values as template rules. When writing a style-sheet writing style-sheet the xsl:namespace-alias enables us to disambiguate "xsl:" as an instruction and "xsl:" as intended output. When XSLT 3.0 comes along, we are quiet likely to be able to reduce this algorithm into one step, as it will allow dynamic XPATH evaluation, which is really the nub of your problem. But for the moment we must be content with a 2-step process.
The second style-sheet is a two-phase transformation. The first stage replicates the template from the article level, as many times as needed by the rules. The second phase parses this replicated template, and applies the dynamic rules substituting text values as indicated by the XPATHs.
UPDATE
My original post was wrong. Thanks to Dimitre for pointing out the error. Please find updated solution above.
After-thought
If a two-step solultion is too complicated, and you are running on a wintel platform, you may consider purchasing the commercial version of Saxon. I believe that the commercial version has a dynamic XPATH evaluation function. I can't give you such a solution because I don't have the commercial version. I imagine a solution using an evaluate() function would be a lot simpler. XSLT is just a hobby for me. But if you are using XSLT for business purposes, the price is quiet reasonable.

Categories