2

我试图从 XML 文档中找到某个元素的最小值(它实际上是一个转换为 XML 的 HTML 表)。但是,这不能按预期工作。

该查询类似于如何使用 XPath 查找一组元素中属性的最小值?. 它看起来像这样:

/table[@id="search-result-0"]/tbody/tr[
    not(substring-before(td[1], " ") > substring-before(../tr/td[1], " "))
]

在示例 XML 上执行

<table class="tablesorter" id="search-result-0">
    <thead>
        <tr>
            <th class="header headerSortDown">Preis</th>
            <th class="header headerSortDown">Zustand</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td width="45px">15 CHF</td>
            <td width="175px">Ausgepack und doch nie gebraucht</td>
        </tr>
        <tr>
            <td width="45px">20 CHF</td>
            <td width="175px">Ausgepack und doch nie gebraucht</td>
        </tr>
        <tr>
            <td width="45px">25 CHF</td>
            <td width="175px">Ausgepack und doch nie gebraucht</td>
        </tr>
        <tr>
            <td width="45px">35 CHF</td>
            <td width="175px">Ausgepack und doch nie gebraucht</td>
        </tr>
        <tr>
            <td width="45px">14 CHF</td>
            <td width="175px">Gebraucht, aber noch in Ordnung</td>
        </tr>
        <tr>
            <td width="45px">15 CHF</td>
            <td width="175px">Gebraucht, aber noch in Ordnung</td>
        </tr>
        <tr>
            <td width="45px">15 CHF</td>
            <td width="175px">Gebraucht, aber noch in Ordnung</td>
        </tr>
    </tbody>
</table>

查询返回以下结果:

<tr>
<td width="45px">15 CHF</td>
<td width="175px">Ausgepack und doch nie gebraucht</td>
</tr>
-----------------------
<tr>
<td width="45px">14 CHF</td>
<td width="175px">Gebraucht, aber noch in Ordnung</td>
</tr>
-----------------------
<tr>
<td width="45px">15 CHF</td>
<td width="175px">Gebraucht, aber noch in Ordnung</td>
</tr>
-----------------------
<tr>
<td width="45px">15 CHF</td>
<td width="175px">Gebraucht, aber noch in Ordnung</td>
</tr>

为什么返回的节点多于一个?应该只返回一个节点,因为只有一个最小值。有人看到查询有什么问题吗?它应该只返回包含14 CHF.

使用http://xpath.online-toolz.com/tools/xpath-editor.php获得的结果

4

3 回答 3

3

TML 已经指出了为什么您当前的路径表达式不起作用,但没有提出可行的替代方案。

原因很简单,正如@Tomalak 所说:

我同意马蒂亚斯的观点。如果不更改输入 XML,这在 XPath 1.0 中实际上是不可能的。

我添加这个答案是为了详细说明在搜索最小量的 CHF之前必须预处理 XML 的方式。请记住:这非常复杂,因为您要求在 XPath 1.0 中提供解决方案。使用 XPath 2.0,您的问题可以通过单个路径表达式来解决。


XML 设计

我认为您的问题说明了为什么在使用 XML 时 XML 设计实际上是必不可少的。为什么?因为您的问题归结为以下几点:您的 XML 的设计方式使得操作内容变得困难。更准确地说,在这样的td元素中:

<td width="45px">15 CHF</td>

td元素的文本节点中都有金额(作为数字)和货币。如果您的 XML 输入以更聪明或更规范的方式设计,它看起来像:

<td width="45px" currency="CHF">15</td>

看到不同?现在,不同种类的内容明显地彼此分离。


XPath 修订版

假设在新设计的 XML 中,tr/td[1]元素的唯一内容是数字,则可以使您使用的 Pavel Minaev 的 XPath 表达式起作用:

/table[@id="search-result-0"]/tbody/tr[not(td[1] > ../tr/td[1])][1]

XML 结果使用您使用的工具测试)

<tr>
<td width="45px">14</td>
<td width="175px">Ausgepack und doch nie gebraucht</td>
</tr>

为什么Pavel 的表达式不起作用,仅仅因为我添加了substring-before

您自己已经找到了部分答案。它与 XPath 1.0 函数中如何处理项目序列有关。

substring-before()是一个 XPath 1.0 函数,它需要两个参数,它们都是字符串。而且,最重要的是,如果您将一系列字符串定义为 的第一个参数substring-before(),则只会处理第一个字符串,其他字符串将被忽略。

帕维尔的回答,适应了这个问题:

tr[not(td[1] > ../tr/td[1])][1]

../tr/td[1]依赖于表达式的第二部分找到的所有元素的所有第一td个子元素这一事实。不涉及函数,将序列作为 的操作数并没有错。trtbody>

如果我们需要substring-before()因为文本内容实际上既是数字(我们想要)又是货币(我们想忽略),我们必须将它包裹在表达式的两个部分:

tr[not(substring-before(td[1],' ') > substring-before(../tr/td[1],' '))][1]

左边没问题>,因为td[1]电流只有一个tr。但在右边,有一个节点序列,即../tr/td[1]。可悲的是,substring-before()只能处理其中的第一个

请参阅@TML 的答案以了解其后果。

于 2014-09-23T16:29:42.630 回答
1

您在此处使用的 XPath 查询只会在没有重复值的情况下找到“最小值”,并且在将值写入节点之前对其进行排序;这是因为它只是将当前值substring-before(td[1], " ")与找到的第一个值进行比较substring-before(../tr/td[1], " ")。分解比较:

[1] not(15 > 15)
[2] not(20 > 15)
[3] not(25 > 15)
[4] not(35 > 15)
[5] not(14 > 15)
[6] not(15 > 15)
[7] not(15 > 15)

比较 1、5、6 和 7 评估为真(左侧不大于右侧)。

于 2014-09-22T21:15:19.020 回答
0

与此同时,我决定改用 XSLT。这是我想出的样式表:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">

    <xsl:output method="text" omit-xml-declaration="yes" indent="no" encoding="UTF-8"/>
    <xsl:strip-space elements="*"/> 

    <xsl:template match="//table[@id=\'search-result-0\']/tbody">
        <ul>
            <xsl:for-each select="tr/td[@width=\'45px\']">
                <xsl:sort select="substring-before(., \' \')" data-type="number" order="ascending"/>

                <xsl:if test="position() = 1">
                     <xsl:value-of select="substring-before(., \' \')"/>
                </xsl:if>
            </xsl:for-each>
        </ul>
    </xsl:template>

    <xsl:template match="text()"/> <!-- ignore the plain text -->

</xsl:stylesheet>
于 2014-09-27T12:30:56.357 回答