如何将 XML 文件扁平化为一组 xpath 表达式?

Lar*_*rry 5 java xml xpath

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

\n\n
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>\n   <article xmlns:ns1='http://predic8.com/material/1/'>\n      <name xmlns:ns1='http://predic8.com/material/1/'>foo</name>\n      <description xmlns:ns1='http://predic8.com/material/1/'>bar</description>\n      <price xmlns:ns1='http://predic8.com/common/1/'>\n         <amount xmlns:ns1='http://predic8.com/common/1/'>00.00</amount>\n         <currency xmlns:ns1='http://predic8.com/common/1/'>USD</currency>\n      </price>\n      <id xmlns:ns1='http://predic8.com/material/1/'>1</id>\n   </article>\n</ns1:create>\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

所以我想得到作为输出:

\n\n
/create/article/name\n/create/article/description\n/create/article/price/amount\n/create/article/price/currency\n/create/article/id\n
Run Code Online (Sandbox Code Playgroud)\n\n

I\xe2\x80\x99m 用 Ja​​va 实现。

\n\n

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

\n\n
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>\n  <article xmlns:ns1='http://predic8.com/material/1/'>\n    <name />\n    <description />\n    <price xmlns:ns1='http://predic8.com/common/1/'>\n      <amount />\n      <currency xmlns:ns1='http://predic8.com/common/1/'></currency>\n    </price>\n    <id xmlns:ns1='http://predic8.com/material/1/'></id>\n  </article>\n</ns1:create>\n
Run Code Online (Sandbox Code Playgroud)\n

Dan*_*ley 3

您可以使用 XSLT 轻松完成此操作。查看您的示例,您似乎只需要包含文本的元素的 XPath。如果情况并非如此,请告诉我,我可以更新 XSLT。

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

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

输出

/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]
Run Code Online (Sandbox Code Playgroud)

更新

要使 XSLT 适用于所有元素,只需[text()]从 中删除谓词即可match="*[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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

输出

/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]
Run Code Online (Sandbox Code Playgroud)

如果删除[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]
Run Code Online (Sandbox Code Playgroud)

这是 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>
Run Code Online (Sandbox Code Playgroud)