How to create an XML file from an XPath expression set?

I want to be able to generate a complete XML file, given the set of XPath mappings.

Input can be specified in two comparisons: (1) One that lists XPath expressions and values; and (2) another that defines the appropriate namespaces.

/create/article[1]/id => 1 /create/article[1]/description => bar /create/article[1]/name[1] => foo /create/article[1]/price[1]/amount => 00.00 /create/article[1]/price[1]/currency => USD /create/article[2]/id => 2 /create/article[2]/description => some name /create/article[2]/name[1] => some description /create/article[2]/price[1]/amount => 00.01 /create/article[2]/price[1]/currency => USD 

For namespaces:

 /create => xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/ /create/article => xmlns:ns1='http://predic8.com/material/1/' /create/article/price => xmlns:ns1='http://predic8.com/common/1/' /create/article/id => xmlns:ns1='http://predic8.com/material/1/' 

Please also note that it is also important that I work with XPath Attributes expressions. For example: I should also be able to handle attributes such as:

 /create/article/@type => richtext 

The final output should look something like this:

 <ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'> <ns1:article xmlns:ns1='http://predic8.com/material/1/' type='richtext'> <name>foo</name> <description>bar</description> <ns1:price xmlns:ns1='http://predic8.com/common/1/'> <amount>00.00</amount> <currency>USD</currency> </ns1:price> <ns1:id xmlns:ns1='http://predic8.com/material/1/'>1</ns1:id> </ns1:article> <ns1:article xmlns:ns1='http://predic8.com/material/2/' type='richtext'> <name>some name</name> <description>some description</description> <ns1:price xmlns:ns1='http://predic8.com/common/2/'> <amount>00.01</amount> <currency>USD</currency> </ns1:price> <ns1:id xmlns:ns1='http://predic8.com/material/2/'>2</ns1:id> </ns1:article> </ns1:create> 

PS: This is a more detailed question to the previous question , although due to a number of additional requirements and explanations I was asked to ask a wider question to satisfy my needs.

Please note that I implement this in Java. Thus, either a Java-based solution or XSLT would be perfectly acceptable. Thnx.

Further note: I'm really looking for a generic solution. The above XML is just an example.

+7
source share
3 answers

This problem has a simple solution if you are building a solution to a previous problem :

 <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:key name="kNSFor" match="namespace" use="@of"/> <xsl:variable name="vStylesheet" select="document('')"/> <xsl:variable name="vPop" as="element()*"> <item path="/create/article/@type">richtext</item> <item path="/create/article/@lang">en-us</item> <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> <namespace of="create" prefix="ns1:" url="http://predic8.com/wsdl/material/ArticleService/1/"/> <namespace of="article" prefix="ns1:" url="xmlns:ns1='http://predic8.com/material/1/"/> <namespace of="@lang" prefix="xml:" url="http://www.w3.org/XML/1998/namespace"/> <namespace of="price" prefix="ns1:" url="xmlns:ns1='http://predic8.com/material/1/"/> <namespace of="id" prefix="ns1:" url="xmlns:ns1='http://predic8.com/material/1/"/> </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:variable name="vLocal-name" select= "substring-before(concat(current-grouping-key(), '['), '[')"/> <xsl:variable name="vNamespace" select="key('kNSFor', $vLocal-name, $vStylesheet)"/> <xsl:choose> <xsl:when test="starts-with($vLocal-name, '@')"> <xsl:attribute name= "{$vNamespace/@prefix}{substring($vLocal-name,2)}" namespace="{$vNamespace/@url}"> <xsl:value-of select= "substring( substring-after(current-group(), current-grouping-key()), 2 )"/> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:element name="{$vNamespace/@prefix}{$vLocal-name}" namespace="{$vNamespace/@url}"> <xsl:sequence select= "my:subTree(for $s in current-group() return concat('/',substring-after(substring($s, 2),'/')) ) "/> </xsl:element> </xsl:otherwise> </xsl:choose> </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 to any XML document (not used), the desired, correct result is obtained :

 <ns1:create xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/"> <ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/" type="richtext" xml:lang="en-us"/> <ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/"> <ns1:id>1</ns1:id> <description>bar</description> <name>foo</name> <ns1:price> <amount>00.00</amount> <currency>USD</currency> </ns1:price> <ns1:price> <amount>11.11</amount> <currency>AUD</currency> </ns1:price> </ns1:article> <ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/"> <ns1:id>2</ns1:id> <description>some name</description> <name>some description</name> <ns1:price> <amount>00.01</amount> <currency>USD</currency> </ns1:price> </ns1:article> </ns1:create> 

Explanation

  • A reasonable assumption has been made that, in the entire generated document, any two elements with the same local-name() belong to the same namespace - this covers the vast majority of real XML documents.

  • The namespace specifications conform to the path specifications. The nsmespace specification is: <namespace of="target element local-name" prefix="wanted prefix" url="namespace-uri"/>

  • Before creating an element with xsl:element corresponding namespace specification is selected using the index created by xsl:key . From this namespace specification, the values ​​of its prefix and url attributes are used when the xsl:element statement specifies the full name of the element and the namespace of the uri element.

+2
source

Interest Ask. Suppose your XPath input expression set satisfies some limiting reasons, for example, if there is X / article [2], then also (preceding it) X / article [1]. And let at the moment a part of the namespace will stand on one side.

Let’s release the XSLT 2.0 solution: we will start by typing in the form

 <paths> <path value="1">/create/article[1]/id</path> <path value="bar">/create/article[1]/description</path> </paths> 

and then we turn it into

 <paths> <path value="1"><step>create</step><step>article[1]</step><step>id</step></path> ... </paths> 

Now we will call a function that performs grouping in the first step, and calls itself recursively for grouping in the next step:

 <xsl:function name="f:group"> <xsl:param name="paths" as="element(path)*"/> <xsl:param name="step" as="xs:integer"/> <xsl:for-each-group select="$paths" group-by="step[$step]"> <xsl:element name="{replace(current-grouping-key(), '\[.*', '')}"> <xsl:choose> <xsl:when test="count(current-group) gt 1"> <xsl:sequence select="f:group(current-group(), $step+1)"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="current-group()[1]/@value"/> </xsl:otherwise> </xsl:choose> </xsl:element> </xsl:for-each-group> </xsl:function> 

This is untested, and there may be details that you need to configure in order to make it work. But I think the basic approach should work.

Part of the namespace might be best resolved by preprocessing the path list to add a namespace attribute for each step element; this can then be used in the xsl: element statement to put the element in the correct namespace.

0
source

I faced a similar situation when I had to convert a set of XPath / FQN values ​​- values ​​to XML. The usual simple solution would be to use the following code, which can be extended to specific requirements.

 public class XMLUtils { static public String transformToXML(Map<String, String> pathValueMap, String delimiter) throws ParserConfigurationException, TransformerException { DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder(); Document document = documentBuilder.newDocument(); Element rootElement = null; Iterator<Entry<String, String>> it = pathValueMap.entrySet().iterator(); while (it.hasNext()) { Entry<String, String> pair = it.next(); if (pair.getKey() != null && pair.getKey() != "" && rootElement == null) { String[] pathValuesplit = pair.getKey().split(delimiter); rootElement = document.createElement(pathValuesplit[0]); break; } } document.appendChild(rootElement); Element rootNode = rootElement; Iterator<Entry<String, String>> iterator = pathValueMap.entrySet().iterator(); while (iterator.hasNext()) { Entry<String, String> pair = iterator.next(); if (pair.getKey() != null && pair.getKey() != "" && rootElement != null) { String[] pathValuesplit = pair.getKey().split(delimiter); if (pathValuesplit[0].equals(rootElement.getNodeName())) { int i = pathValuesplit.length; Element parentNode = rootNode; int j = 1; while (j < i) { Element child = null; NodeList childNodes = parentNode.getChildNodes(); for (int k = 0; k < childNodes.getLength(); k++) { if (childNodes.item(k).getNodeName().equals(pathValuesplit[j]) && childNodes.item(k) instanceof Element) { child = (Element) childNodes.item(k); break; } } if (child == null) { child = document.createElement(pathValuesplit[j]); if (j == (i - 1)) { child.appendChild( document.createTextNode(pair.getValue() == null ? "" : pair.getValue())); } } parentNode.appendChild(child); parentNode = child; j++; } } else { // ignore any other root - add logger System.out.println("Data not processed for node: " + pair.getKey()); } } } TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); DOMSource domSource = new DOMSource(document); // to return a XMLstring in response to an API StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); StreamResult resultToFile = new StreamResult(new File("C:/EclipseProgramOutputs/GeneratedXMLFromPathValue.xml")); transformer.transform(domSource, resultToFile); transformer.transform(domSource, result); return writer.toString(); } public static void main(String args[]) { Map<String, String> pathValueMap = new HashMap<String, String>(); String delimiter = "/"; pathValueMap.put("create/article__1/id", "1"); pathValueMap.put("create/article__1/description", "something"); pathValueMap.put("create/article__1/name", "Book Name"); pathValueMap.put("create/article__1/price/amount", "120" ); pathValueMap.put("create/article__1/price/currency", "INR"); pathValueMap.put("create/article__2/id", "2"); pathValueMap.put("create/article__2/description", "something else"); pathValueMap.put("create/article__2/name", "Book name 1"); pathValueMap.put("create/article__2/price/amount", "2100"); pathValueMap.put("create/article__2/price/currency", "USD"); try { XMLUtils.transformToXML(pathValueMap, delimiter); } catch (ParserConfigurationException | TransformerException e) { // TODO Auto-generated catch block e.printStackTrace(); } }} 

Exit:

 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <create> <article__1> <id>1</id> <name>Book Name</name> <description>something</description> <price> <currency>INR</currency> <amount>120</amount> </price> </article__1> <article__2> <description>something else</description> <name>Book name 1</name> <id>2</id> <price> <currency>USD</currency> <amount>2100</amount> </price> </article__2> 

To remove __% num, you can use regular expressions in the summary line. like:

 resultString = resultString.replaceAll("(__[0-9][0-9])|(__[0-9])", ""); 

That would do the cleaning

0
source

All Articles