How to Generate an XML File from a set of XPath Expressions?

后端 未结 3 499
臣服心动
臣服心动 2020-12-16 05:12

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

The input could specified in two mappings: (1) One which lists the XPath expression

相关标签:
3条回答
  • 2020-12-16 05:49

    i came across a similar situation where i had to convert Set of XPath/FQN - value mappings to XML. A generic simple solution can be using the following code, which can be enhanced 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();
        }
    
    }}
    

    Output:

    <?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 , can use regular expressions on final string. like:

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

    This would do the cleaning job

    0 讨论(0)
  • 2020-12-16 05:57

    This problem has an easy solution if one builds upon the solution of the 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 on any XML document (not used), the wanted, correct result is produced:

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

    1. A reasonable assumption is made that throughout the generated document any two elements with the same local-name() belong to the same namespace -- this covers the predominant majority of real-world XML documents.

    2. The namespace specifications follow the path specifications. A nsmespace specification has the form: <namespace of="target element's local-name" prefix="wanted prefix" url="namespace-uri"/>

    3. Before generating an element with xsl:element, the appropriate namespace specification is selected using an index created by an xsl:key. From this namespace specification the values of its prefix and url attributes are used in specifying in the xsl:element instruction the values of the full element name and the element's namespace-uri.

    0 讨论(0)
  • 2020-12-16 06:08

    Interesting question. Let's assume that your input set of XPath expressions satisfies some reasonsable constraints, for example if there is an X/article[2] then there also (preceding it) an X/article[1]. And let's put the namespace part of the problem to one side for the moment.

    Let's go for an XSLT 2.0 solution: we'll start with the input in the form

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

    and then we'll turn this into

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

    Now we'll call a function which does a grouping on the first step, and calls itself recursively to do grouping on 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>
    

    That's untested, and there may well be details you have to adjust to get it working. But I think the basic approach should work.

    The namespace part of the problem is perhaps best tackled by preprocessing the list of paths to add a namespace attribute to each step element; this can then be used in the xsl:element instruction to put the element in the right namespace.

    0 讨论(0)
提交回复
热议问题