DOM xpath 查找#text 节点并包含在段落标记中

Posted

技术标签:

【中文标题】DOM xpath 查找#text 节点并包含在段落标记中【英文标题】:DOM xpath to find #text nodes and wrap in paragraph tag 【发布时间】:2013-03-11 06:14:24 【问题描述】:

我想找到所有根级#text 节点(或具有 div 父级的节点),这些节点应包含在 <p> 标记内。在下面的文本中应该有三个(甚至只有两个)最终根<p> 标签。

<div>
    This text should be wrapped in a p tag.
</div>

This also should be wrapped.

<b>And</b> this.

这个想法是更好地格式化文本,以便将文本块分组为 html 显示的段落。但是,我一直在研究的以下 xpath 似乎无法选择文本节点。

    <?php

$html = '<div>
    This text should be wrapped in a p tag.
</div>

This also should be wrapped.

<b>And</b> this.';

libxml_use_internal_errors(TRUE);

$dom = DOMDocument::loadHTML($html);

$xp = new DOMXPath($dom);

$xpath = '//text()[not(parent::p) and normalize-space()]';

foreach($xp->query($xpath) as $node) 
    $element = $dom->createElement('p');
    $node->parentNode->replaceChild($element, $node);
    $element->appendChild($node);


print $dom->saveHTML();

【问题讨论】:

如果您还想选择divs 之外的文本节点,为什么还要将//div 放在XPath 表达式中? This fiddle 似乎在做你想做的事。 你能告诉我我上面发布的解决方案有什么问题吗?是否要将带有换行符的文本转换为多个段落? @nwellnhof,你的解决方案很好——但这不是答案,所以我不能奖励你任何东西。 @nwellnhof,太棒了!谢谢! 【参考方案1】:

好的,所以让我重新表述我的评论作为答案。如果要匹配所有文本节点,只需从 XPath 表达式中删除 //div 部分。于是就变成了:

//text()[not(parent::p) and normalize-space()]

【讨论】:

这将如何与&lt;div&gt;this text is ok&lt;/div&gt;But how about &lt;b&gt;this&lt;/b&gt;?一起工作?【参考方案2】:

您的场景有许多边缘情况,并且应该这个词被添加到顶部。我假设你想做经典的双中断开始一个新段落的事情,但是这次在父&lt;div&gt;(或者当然是其他块元素)中也是如此。

我会让 HTML 解析器完成大部分工作,但我仍然会使用文本搜索和替换(在 xpath 旁边)。所以你会看到接下来的内容有点老套,但我认为相当稳定:

首先,我会选择所有属于该 div 的***或子级的文本节点。

(.|./div)/text()

此 xpath 与 anchor 元素相关,该元素是 &lt;body&gt; 标记,因为它表示加载到 DOMDocument 时的 HTML 片段的根标记。

如果是 div 的子元素,那么我会在开头插入起始段落。

然后在任何情况下,我都会在开始新段落的序列的每次出现处插入一个断标记(这里以注释的形式)(由于空白规范化,应该是"\n\n",我可能是错的并且如果它不适用,您需要预先进行空白规范化以使其透明地工作)。

/* @var $result DOMText[] */
$result = $xp->query('(.|./div)/text()', $anchor);

foreach ($result as $i => $node)

    if ($node->parentNode->tagName == 'div')
    
        $insertBreakMarkBefore($node, true);
    

    while (FALSE !== $pos = strpos($node->data, $paragraphSequence))
    
        $node = $node->splitText($pos + $paragraphSequenceLength);
        $insertBreakMarkBefore($node);
    

这些插入的断点只是用 HTML &lt;p&gt; 标记替换。 HTML 解析器会将它们转换成足够的&lt;p&gt;...&lt;/p&gt; 对,这样我就可以省出自己编写该算法的时间(尽管这可能很有趣)。这基本上就像我曾经在其他答案中概述的那样工作,但我再也找不到链接了:

    DOM树修改后,再次获取&lt;body&gt;的内部HTML。 将设置标记替换为"&lt;p&gt;"(这里我也标记了类以使其可见) 再次将 HTML 片段加载到解析器中,以使用正确的 &lt;p&gt;...&lt;/p&gt; 对重新创建 DOM。 从DOMDocument 解析器再次获取HTML,现在终于完成了。

这些在代码中概述的步骤(暂时跳过一些函数定义):

$needle  = sprintf('%1$s<!--%2$s-->%1$s', $paragraphSequence, $paragraphComment);
$replace = sprintf("\n<p class=\"%s\">\n", $paragraphComment);
$html    = strtr($innerHTML($anchor), array($needle . $needle => $replace, $needle => $replace));

echo "HTML afterwards:\n", $innerHTML($loadHTMLFragment($html));

如图所示,双序列替换为单序列。可能最后一个也需要删除(如果适用,您也可以在此处修剪空白)。

最终的 HTML 输出:

<div>
<p class="break">

    This text should be wrapped in a p tag.
</p>
</div>
<p class="break">
This also should be wrapped.
</p>
<p class="break">
<b>And</b> this.</p>

更多的后期制作以获得良好的输出格式也很有用。实际上我认为这样做是值得的,因为它可以帮助您调整算法(Full Demo - 只是看到,空白规范化可能不适用于那里。所以小心使用)。

【讨论】:

很好的解释,但是,您的演示似乎并没有真正达到这个结果。 正如最后所写的,这是因为缺少空白规范化。确保输入使用\n 作为行分隔符,键盘似乎使用\r\n 或类似的东西,这就是你看到它不同的原因。我可以稍后再次检查,我只是注意到当我将它粘贴到那里时,代码在我自己的盒子上有效。所以你自己试试吧,它可能已经在你的盒子上工作了。【参考方案3】:

如果您愿意,可以使用纯 javascript 来实现:

var content = document.evaluate(
                                      '//text()', 
                                      document, 
                                      null, 
                                      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, 
                                      null );

for ( var i=0 ; i < content .snapshotLength; i++ )
  console.log( content .snapshotItem(i).textContent );

【讨论】:

【参考方案4】:

我知道这不是 xpath,但请检查一下:

PHP 简单 HTML DOM 解析器

http://simplehtmldom.sourceforge.net/

特点

用 PHP5+ 编写的 HTML DOM 解析器让您以非常简单的方式操作 HTML!

支持无效的 HTML。

使用类似于 jQuery 的选择器在 HTML 页面上查找标签。

在一行中从 HTML 中提取内容。

【讨论】:

请在您的回答中说明它如何与该库一起使用,否则这只是部分相关(就我个人而言,该库是一个糟糕的建议,现在 PHP 已经内置了该库的所有内容提供,PHP Simple HTML DOM Parser 库最初是为 PHP 4 编写的,没有基于 libxml 的扩展,因为它们目前不存在。

以上是关于DOM xpath 查找#text 节点并包含在段落标记中的主要内容,如果未能解决你的问题,请参考以下文章

第一百二十六节,JavaScript,XPath操作xml节点

Dom4j 操作, 节点查找 添加 删除 修改 。。。xPath

带有 dom 文档的 xpath

xpath 查找不包含子节点的节点

xpath:查找具有给定属性的节点,其值包含字符串

Selenium XPath 合并跨越文本并查找包含子字符串的元素