【问题标题】:HTML parsing with XPath: flattened hierarchical data使用 XPath 进行 HTML 解析:扁平的分层数据
【发布时间】:2014-02-23 18:23:55
【问题描述】:

我的目标 HTML 是一个扁平的元素表,其中包含由类属性定义的 2 级数据:

<tr>
    <td class="type">Type 1</td>
</tr>
<tr>
    <td class="name">name1</td>
    <td class="year">1970</td>
    <td class="rank">1</td>
</tr>
<tr>
    <td class="name">name2</td>
    <td class="year">1982</td>
    <td class="rank">3</td>
</tr>

目标是解析出名称、年份、排名元素的列表,我使用这些 xpath 表达式来完成:

//td[@class = 'name']/text()
//td[@class = 'year']/text()
//td[@class = 'rank']/text()

每个元素都在紧接的前面

<tr>
        <td class="type">Type 1</td>
</tr>

我想为上面解析的每个元素分配“类型 1”。它可以是相同长度的单独列表。当然,我的目标 HTML 在同一个 2 级层次结构中包含许多这样的元素:类型 - 元素(名称、年份、等级)。

【问题讨论】:

  • 您可以使用 XSLT 分组(如果您只支持 XSLT 1.0,则可以使用 muenchian 分组)。但使用串行解析器 (SAX) 可能更容易,您可以在其中跟踪读取的 type 类的最后一个元素并将数据附加到后面的节点。
  • 是的,SAX 解析器是一种比较明显的方法,我之前花了相当多的时间用 SAX 解析这样的 HTML。通常,由于要解析多个 HTML,因此代码变得庞大而难以维护。我希望有一些 xpath 魔法......
  • 因此,对于 XSLT 分组,您建议首先使用 XSLT 将其转换为更统一的 XML,然后应用 XPath...这看起来是一种有效的方法,谢谢!现在仍然希望只有 XPath 的解决方案。

标签: html xml parsing xpath


【解决方案1】:

以下相当笨拙的xpath 将最接近的前一个@type td 连接到上面匹配的name td

    concat(//td[@class = 'name']/preceding::td[@class='type'][1]/text(), '-',
           //td[@class = 'name']/text())

在下面的 xsl 中显示时,这可能更有意义

    <xsl:for-each select="//td[@class='name']">
        <Name>
            <xsl:value-of select="concat(preceding::td[@class='type'][1]/text(), 
                          '-', ./text())" />
        </Name>
    </xsl:for-each>

应用于以下xml

<xml>
    <tr>
        <td class="type">Type 1</td>
    </tr>
    <tr>
        <td class="name">name1</td>
        <td class="year">1970</td>
        <td class="rank">1</td>
    </tr>
    <tr>
        <td class="name">name2</td>
        <td class="year">1982</td>
        <td class="rank">3</td>
    </tr>
    <tr>
        <td class="type">Type 2</td>
    </tr>
    <tr>
        <td class="name">name3</td>
        <td class="year">1971</td>
        <td class="rank">2</td>
    </tr>
    <tr>
        <td class="name">name4</td>
        <td class="year">1983</td>
        <td class="rank">4</td>
    </tr>
</xml>

结果

<Name>Type 1-name1</Name>
<Name>Type 1-name2</Name>
<Name>Type 2-name3</Name>
<Name>Type 2-name4</Name>

【讨论】:

  • 谢谢,很好的回复,但到目前为止我遇到了一些困难。应用 //td[@class = 'name']/text() 时,我得到 2066 个元素。应用 //td[@class = 'name']/preceding::td[@class='type'][1]/text() 时,我得到 352 个元素。这是公平的,因为它显示了我总共有多少顶级和低级元素。但是应用带有concat 函数的构造只会导致第一个元素与其顶部连接。
  • @topchef - 是的,这就是 XSLT 示例的原因 - XPath 本身实际上只适合一次处理单个主题节点。我建议使用 XSLT 来解析上面的整个文档,或者使用带有 xml 解析器的语言,然后例如.SelectNodes(//td[@class = 'name']),并遍历所有评估 preceding::td[@class='type'][1]/text() 的节点。我并不是说这不能仅使用 XPath 来完成 - 例如a fold could reduce all nodes by applying a function 但这超出了我的能力
  • 再次感谢,我认为仅靠 XPath 表达式并不适合解决它。我将使用 XPath,然后使用简单的循环,希望在处理 SAX 处理程序时让我免于悲伤。
【解决方案2】:

解决方案 1

首先,找到感兴趣的td 元素。例如 name tds 的伪代码如下:

name_tds = doc.evalXPath("//td[@class = 'name']")

然后您可以使用 name td 作为上下文节点找到相应的 type td,如下所示:

type_td = name_td.evalXPath("../preceding-sibling::tr[td[@class = 'type']][1]/td")

解决方案 2

只需迭代所有tds 并记住您找到的最后一个type。伪代码:

foreach (td in doc.evalXPath("//td") {
    class = td.getAttribute("class");
    if (class == "type") {
        type = td.textContent();
    }
    else if (class == "name") {
        name = td.textContent();
        println("type: " + type + ", name: " + name);
    }
    // Same for year and rank.
}

【讨论】:

  • 是在某种编程语言的上下文中吗?我既不使用 JavaScript 也不使用 Python。
  • 是的,这是在某些其他语言的上下文中。任何带有 XPath 引擎的语言都应该可以工作。或者您有什么特殊要求?
  • 没有特殊要求,但是在我拥有的 XPath 库(R 中的 XML 包)的上下文中,解决方案 1 并不清楚。到目前为止,我实际上正在使用您的解决方案 2,谢谢!
猜你喜欢
  • 2019-01-05
  • 2011-07-18
  • 2019-03-15
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
  • 2014-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多