1

我有一个如下的 XML:

<object>
    <codes>
        <cd1>A</cd1>
        <cd2>B</cd2>
        <cd3>C</cd3>
    </codes>
    <codes>
        <cd1>A</cd1>
        <cd2>D</cd2>
        <cd3></cd3>
    </codes>
    <codes>
        <cd1>E</cd1>
        <cd2>D</cd2>
        <cd3></cd3>
    </codes>
</object>

到目前为止,我的 XPath 演变如下:

  1. //cd1|//cd2|//cd3: 获取所有 cd1、cd2 和 cd3 元素

  2. (//cd1|//cd2|//cd3)[text()[1]]:从上面的列表中过滤所有具有非空文本值的元素并返回下面的元素。

    <cd1>A</cd1> <cd2>B</cd2> <cd3>C</cd3> <cd1>A</cd1> <cd2>D</cd2> <cd1>E</cd1> <cd2>D</cd2>

  3. 现在我需要删除具有重复文本值的元素。我试过 xpath 作为(//cd1|//cd2|//cd3)[text()[1]][(preceding::cd1)|(preceding::cd2)|(preceding::cd3)]. 我希望实现的是检查该值是否在上面的任何 cd1 或 cd2 或 cd3 中。但这会在<cd2>D</cd2>重复的地方返回。

    <cd2>B</cd2> <cd3>C</cd3> <cd1>A</cd1> <cd2>D</cd2> <cd1>E</cd1> <cd2>D</cd2>

如何编写 xpath 来解决上述 (3) 问题?

请注意我必须使用 Xpath 1.0 ,因此 distinct-values 函数不是一个选项。此外,我需要获取匹配的节点列表,而不是来自 xpath 的文本值,因为我必须使用 AXIOM 对这些节点进行更多处理。

更新:我正在使用这个 xpath 来获取匹配的元素,然后用 AXIOM 处理这些元素。因此,我需要编写一个 xpath 表达式来一次性获取匹配的元素(我无法在 AXIOM 中编写自定义流程或使用 XSLT)。也不能使用 cd* ,因为真实姓名不匹配。我在这里使用了一个示例。

4

3 回答 3

1

尝试这个:

//cd1[not(text() = preceding::cd1/text())][normalize-space()]|
//cd2[not(text() = preceding::cd2/text())][normalize-space()]|
//cd3[not(text() = preceding::cd3/text())][normalize-space()]
于 2015-09-01T23:01:03.560 回答
1

这实际上是非常简单的 Muenchian 分组,只有三个键:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml" />
<xsl:key name="cd1" match="//cd1" use="text()" />
<xsl:key name="cd2" match="//cd2" use="text()" />
<xsl:key name="cd3" match="//cd3" use="text()" />

<xsl:template match="/">    
    <xsl:for-each select="/object/codes/cd1[./text() != '' and count(. | key('cd1', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>

    <xsl:for-each select="/object/codes/cd2[./text() != '' and count(. | key('cd2', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>
    <xsl:for-each select="/object/codes/cd3[./text() != '' and count(. | key('cd3', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>

</xsl:template>
</xsl:stylesheet>

输出:

<?xml version="1.0" encoding="UTF-8"?>
<cd1>A</cd1>
<cd1>E</cd1>
<cd2>B</cd2>
<cd2>D</cd2>
<cd3>C</cd3>

或者,如果您想对它们进行分组而不考虑节点名称(即 ifcd1cd2两者都具有A作为文本值),那么它就不那么简单了。

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml" />
<xsl:key name="cd" match="//cd1 | //cd2 | //cd3" use="text()" />

<xsl:template match="/">    
    <xsl:for-each select="/object/codes/cd1[./text() != '' and count(. | key('cd', .)[1]) = 1] | /object/codes/cd2[./text() != '' and count(. | key('cd', .)[1]) = 1] | /object/codes/cd3[./text() != '' and count(. | key('cd', .)[1]) = 1]">
        <xsl:copy-of select="." />
    </xsl:for-each>


</xsl:template>
</xsl:stylesheet>

这将给出与上面相同的输出(但按照当前模板输出的方式排序),但会消除 a 、 或共享相同文本之间的重复cd1cd2并且cd3只取第一个具有它的文本)。

另请注意,我忽略了空节点 - 这可能不是我们想要的(并且可以通过./text() != ''从选择器中删除来轻松修复 - 但是,如果需要,必须使用不同的方法来消除重复的空节点(可能只是一个一系列模板或xsl:ifs,用于测试空节点并在这种情况下输出单个(如果存在)。

于 2015-09-01T22:20:22.650 回答
1

我发现的一种方法是使用以下模板:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
    <xsl:for-each select="//*[starts-with(node-name(.), 'cd')]">
        <xsl:variable name="content"><xsl:value-of select="text()"/></xsl:variable>
        <xsl:if test="count(preceding::*[starts-with(node-name(.), 'cd') and text() = $content]) = 0 and text()">
               <xsl:copy-of select="."/> 
            </xsl:if>
     </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

这需要所有 cd* 元素,并获取每个元素的内容,它用来计算有多少前面具有相同内容的 - 如果那是 0 -> 然后它使用它。

据我所知,这是可以在 xslt-1 中完成的唯一方法(通过使用变量)。这是因为您不能在 xpath 中进行反向引用 - 除非您在变量中有值(并且您需要将“当前”(外部)文本与“当前”(xpath 内的节点)文本进行比较)。

希望这可以帮助。

于 2015-09-01T21:37:46.583 回答