Fill in XML template file from XPath Expressions?

What would be the best way to populate (or generate) a template XML file from an XPath expression mapping?

The requirements are that we will need to start with a template (as this may contain information that was not written otherwise in XPath expressions).

For example, a starter 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 get 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 

Then we must 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 possible.

PS: This question is for another question I recently asked.

+2
source share
2 answers

This conversion creates an XML document from the "expressions" that has the structure of the desired result - it remains to convert this result to 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 conversion is applied to any XML document (not used), the result :

 <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 convert the "expressions" that you specify to the format used in this conversion - it's easy and simple.

  • In the final conversion, you need to copy each node β€œas is” (using an identification rule), except that the top node must be generated in the namespace "http://predic8.com/wsdl/material/ArticleService/1/" . Please note that other namespaces present in the "template" are not used and can be safely skipped.

+1
source

This solution requires that you streamline the XPATH input information a bit and enable two-step transformation. The first transformation will write a stylesheet to be executed in the second transformation. Thus, the client needs to make two calls to the XSLT engine. Let us know if this is a problem.

Step one

Please reorganize your XPATH information in an XML document, for example. This should not be difficult to do, and even an XSLT script can be written to complete the task.

 <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 limited to:

  • The match is an implicit prefix expression: / create / '. Do not state this explicitly.
  • All matches must begin as article [n], where n is a sequence number.
  • We cannot have zero rules.
  • Any prefixes you use in the match except s11 = "http://schemas.xmlsoap.org/soap/envelope/" and ns1 = "http://predic8.com/wsdl/material/ArticleService/1/". (Note: I do not think this is true so that namespaces end with "/", but are not sure about it) are defined in node namespaces.

The above document is the first step conversion. Apply this document to this style ...

 <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> 

Second step

Apply your soap envelope file as an input to the stylesheet that was displayed from the first step. The result is an original soap document that changes as needed. This is an example of the style sheet of the second step, in which, for simplicity of illustration, only the first rule is considered (/ create / article [1] / id => 1).

 <?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> 

Additional restrictions for solution

  • The template document must contain at least one / s 11: Envelope / s11: Body / ns1: create / article. Only the node article is replicated (deeply) in accordance with the requirements of the rules. In addition, it can be any structure.
  • The template document cannot contain sub-levels s11: Envelope / s11: Body / ns1: create node.

Description

You will notice that your XPATH expressions are not much removed from the pattern matching condition. Therefore, it’s not so difficult to write a style sheet that re-expresses your XPATH and substitution values ​​as template rules. When writing styles styles, xsl: namespace-alias allows us to eliminate "xsl:" as an instruction and "xsl:" as the intended output. When XSLT 3.0 arrives, we are likely to be able to reduce this algorithm by one step, as this will allow us to dynamically evaluate XPATH, which is actually the zero point of your problem. But for now, we should be content with a two-step process.

The second style sheet is a two-phase conversion. The first stage replicates the template from the article level, as many times as necessary by the rules. The second phase analyzes this replicated template and applies dynamic rules that replace text values ​​as specified in XPATH.


UPDATE

My initial message was incorrect. Thanks to Demetrius for pointing out the error. Please find the updated solution above.

After thinking

If the two-step solution is too complicated and you are running the wintel platform, you might consider buying a commercial version of Saxon. I believe the commercial version has a dynamic XPATH evaluation function. I cannot give you such a solution because I do not have a commercial version. I believe that a solution using the evaluation function () would be much simpler. XSLT is just a hobby for me. But if you use XSLT for business purposes, the price is reasonable.

0
source

All Articles