【问题标题】:How to flatten an XML file into a set of xpath expressions?如何将 XML 文件展平为一组 xpath 表达式?
【发布时间】:2012-07-03 02:40:24
【问题描述】:

假设我有以下示例 XML 文件:

<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 xmlns:ns1='http://predic8.com/material/1/'>bar</description>
      <price xmlns:ns1='http://predic8.com/common/1/'>
         <amount xmlns:ns1='http://predic8.com/common/1/'>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>
</ns1:create>

将其展平为一组 xpath 表达式的最佳(最有效)方法是什么。 另请注意:我想忽略任何命名空间和属性信息。 (如果需要,这也可以作为预处理步骤来完成)。

所以我想得到输出:

/create/article/name
/create/article/description
/create/article/price/amount
/create/article/price/currency
/create/article/id

我正在用 Java 实现。

编辑: PS,在文本节点没有数据的情况下,我可能还需要它来工作,因此,例如,以下应该生成与上面相同的输出:

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

【问题讨论】:

  • 我希望有比手动迭代文档等更可靠的方法。例如:我们还需要考虑多元素,它可以转换为 /field[ 0] 和 /field[1] 等

标签: java xml xpath


【解决方案1】:

使用 XSLT 可以很容易地做到这一点。查看您的示例,您似乎只想要包含文本的元素的 XPath。如果不是这样,请告诉我,我可以更新 XSLT。

我创建了一个新的输入示例来展示它如何处理同名的兄弟姐妹。在这种情况下,&lt;article&gt;

XML 输入

<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 xmlns:ns1='http://predic8.com/material/1/'>bar</description>
        <price xmlns:ns1='http://predic8.com/common/1/'>
            <amount xmlns:ns1='http://predic8.com/common/1/'>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 xmlns:ns1='http://predic8.com/material/2/'>some name</name>
        <description xmlns:ns1='http://predic8.com/material/2/'>some description</description>
        <price xmlns:ns1='http://predic8.com/common/2/'>
            <amount xmlns:ns1='http://predic8.com/common/2/'>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>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="text()"/>

    <xsl:template match="*[text()]">
        <xsl:call-template name="genPath"/>
        <xsl:apply-templates select="node()|@*"/>
    </xsl:template>

    <xsl:template name="genPath">
        <xsl:param name="prevPath"/>
        <xsl:variable name="currPath" select="concat('/',local-name(),'[',
        count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
        <xsl:for-each select="parent::*">
            <xsl:call-template name="genPath">
                <xsl:with-param name="prevPath" select="$currPath"/>
            </xsl:call-template>
        </xsl:for-each>
        <xsl:if test="not(parent::*)">
            <xsl:value-of select="$currPath"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

输出

/create[1]/article[1]/name[1]
/create[1]/article[1]/description[1]
/create[1]/article[1]/price[1]/amount[1]
/create[1]/article[1]/price[1]/currency[1]
/create[1]/article[1]/id[1]
/create[1]/article[2]/name[1]
/create[1]/article[2]/description[1]
/create[1]/article[2]/price[1]/amount[1]
/create[1]/article[2]/price[1]/currency[1]
/create[1]/article[2]/id[1]

更新

要使 XSLT 对所有元素都有效,只需从 match="*[text()]" 中删除 [text()] 谓词即可。这将输出每个元素的路径。如果您不希望包含其他元素(如 create、article 和 price)的元素的路径输出,请添加谓词 [not(*)]。这是一个更新的示例:

新的 XML 输入

<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
    <article xmlns:ns1='http://predic8.com/material/1/'>
        <name />
        <description />
        <price xmlns:ns1='http://predic8.com/common/1/'>
            <amount />
            <currency xmlns:ns1='http://predic8.com/common/1/'></currency>
        </price>
        <id xmlns:ns1='http://predic8.com/material/1/'></id>
    </article>
    <article xmlns:ns1='http://predic8.com/material/2/'>
        <name xmlns:ns1='http://predic8.com/material/2/'>some name</name>
        <description xmlns:ns1='http://predic8.com/material/2/'>some description</description>
        <price xmlns:ns1='http://predic8.com/common/2/'>
            <amount xmlns:ns1='http://predic8.com/common/2/'>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>

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="text()"/>

    <xsl:template match="*[not(*)]">
        <xsl:call-template name="genPath"/>
        <xsl:apply-templates select="node()"/>
    </xsl:template>

    <xsl:template name="genPath">
        <xsl:param name="prevPath"/>
        <xsl:variable name="currPath" select="concat('/',local-name(),'[',
            count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
        <xsl:for-each select="parent::*">
            <xsl:call-template name="genPath">
                <xsl:with-param name="prevPath" select="$currPath"/>
            </xsl:call-template>
        </xsl:for-each>
        <xsl:if test="not(parent::*)">
            <xsl:value-of select="$currPath"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

输出

/create[1]/article[1]/name[1]
/create[1]/article[1]/description[1]
/create[1]/article[1]/price[1]/amount[1]
/create[1]/article[1]/price[1]/currency[1]
/create[1]/article[1]/id[1]
/create[1]/article[2]/name[1]
/create[1]/article[2]/description[1]
/create[1]/article[2]/price[1]/amount[1]
/create[1]/article[2]/price[1]/currency[1]
/create[1]/article[2]/id[1]

如果您删除 [not(*)] 谓词,输出结果如下所示(为每个元素输出一个路径):

/create[1]
/create[1]/article[1]
/create[1]/article[1]/name[1]
/create[1]/article[1]/description[1]
/create[1]/article[1]/price[1]
/create[1]/article[1]/price[1]/amount[1]
/create[1]/article[1]/price[1]/currency[1]
/create[1]/article[1]/id[1]
/create[1]/article[2]
/create[1]/article[2]/name[1]
/create[1]/article[2]/description[1]
/create[1]/article[2]/price[1]
/create[1]/article[2]/price[1]/amount[1]
/create[1]/article[2]/price[1]/currency[1]
/create[1]/article[2]/id[1]

这是另一个 XSLT 版本,它的速度大约提高了 65%:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="text()"/>

    <xsl:template match="*[not(*)]">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/',local-name(),'[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="node()"/>
    </xsl:template>

</xsl:stylesheet>

【讨论】:

  • 非常感谢您的解决方案,但是,您能否考虑一下文本节点没有数据的情况?我已经编辑了我的问题,概述了这样的示例。谢谢。
  • @Larry - 请查看我的更新。如果您有任何其他问题,请告诉我。
  • 我还做了一个小改动,这样任何属性值都不会被转储到输出中。
  • 非常感谢... :) 这是完美的!另一方面:这个问题的反面是否有解决方案? IE。从 XML 模板开始并给定一组 XPath 和值,能够生成填充的 XML 文件。无论如何,我已经把这个作为另一个问题问了(stackoverflow.com/questions/11385323);如果您也知道解决方案,那就太好了!
【解决方案2】:

我的建议是使用 SAX 解析器。 wiki entry for SAX , Xerces: a SAX parser for java by Apache

在每个起始元素上,将元素的名称添加到列表的末尾。在每个结束元素上,删除最后一个列表条目。当您遇到内容,并且想要输出您的 xpath 时,可以通过迭代列表来检索它。

【讨论】:

    猜你喜欢
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多