7

给定一组 XPath 映射,我希望能够生成一个完整的 XML 文件。

输入可以在两个映射中指定: (1) 一个列出 XPath 表达式和值;(2) 另一个定义适当的命名空间。

/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

对于命名空间:

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

另请注意,我还要处理 XPath属性表达式,这一点很重要。例如:我也应该能够处理属性,例如:

/create/article/@type => richtext

最终输出应如下所示:

<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:这是对上一个问题提出的更详细的问题,尽管由于一系列进一步的要求和澄清,建议我提出更广泛的问题以满足我的需求。

另请注意,我正在用 Java 实现这一点。因此,基于 Java 或基于 XSLT 的解决方案都是完全可以接受的。谢谢。

进一步说明:我真的在寻找一个通用的解决方案。上面显示的 XML 只是一个示例。

4

3 回答 3

2

如果建立在前一个问题的解决方案的基础上,这个问题就有一个简单的解决方案:

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

当将此转换应用于任何 XML 文档(未使用)时,将产生所需的正确结果

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

说明

  1. 一个合理的假设是,在整个生成的文档中,任何两个具有相同元素的元素都local-name()属于同一个名称空间——这涵盖了现实世界中绝大多数 XML 文档。

  2. 命名空间规范遵循路径规范。nsmespace 规范具有以下形式:<namespace of="target element's local-name" prefix="wanted prefix" url="namespace-uri"/>

  3. 在使用 生成元素之前xsl:element,使用由 . 创建的索引选择适当的命名空间规范xsl:key。根据这个命名空间规范,其prefixurl属性的值用于在xsl:element指令中指定完整元素名称和元素的命名空间 uri 的值。

于 2012-07-10T03:44:52.540 回答
0

有趣的问题。假设您输入的 XPath 表达式集满足一些合理的约束,例如,如果有一个 X/article[2],那么(在它之前)还有一个 X/article[1]。让我们暂时将问题的名称空间部分放在一边。

让我们使用 XSLT 2.0 解决方案:我们将从表单中的输入开始

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

然后我们把它变成

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

现在我们将调用一个在第一步进行分组的函数,并在下一步递归调用自身进行分组:

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

这是未经测试的,您可能需要调整一些细节才能使其正常工作。但我认为基本方法应该有效。

问题的命名空间部分可能最好通过预处理路径列表来为每个步骤元素添加命名空间属性来解决;然后可以在 xsl:element 指令中使用 this 将元素放在正确的命名空间中。

于 2012-07-09T16:03:34.660 回答
0

我遇到了类似的情况,我必须将 Set of XPath/FQN - 值映射转换为 XML。一个通用的简单解决方案可以使用以下代码,可以针对特定要求进行增强。

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();
    }

}}

输出:

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

要删除 __%num ,可以在最终字符串上使用正则表达式。像:

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

这将完成清洁工作

于 2019-06-03T12:25:05.513 回答