10

我想找到应该包装在<p>标签内的所有根级#text 节点(或具有 div 父级的节点)。在下面的文本中应该有三个(甚至只有两个)最终根<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();
4

4 回答 4

8

OK, so let me rephrase my comment as an answer. If you want to match all text nodes, you should simply remove the //div part from your XPath expression. So it becomes:

//text()[not(parent::p) and normalize-space()]
于 2013-03-30T09:55:21.317 回答
2

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

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

首先,我会选择所有顶级或子级的文本节点。

(.|./div)/text()

此 xpath 与作为标记的元素相关,因为它表示 HTML 片段在加载到.<body>DOMDocument

如果是 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<p>标记替换。HTML 解析器会将它们转换成足够的<p>...</p>对,这样我就可以不用自己编写该算法(尽管这可能很有趣)。这基本上就像我曾经在其他答案中概述的那样工作,但我不再找到链接:

  1. DOM树修改后,再次获取内部HTML <body>
  2. 将设置的标记替换为"<p>"(这里我也标记了类以使其可见)
  3. 再次将 HTML 片段加载到解析器中,以使用正确的对重新创建 DOM <p>...</p>
  4. 再次从DOMDocument解析器中获取 HTML,现在是 finally。

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

$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>

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

于 2013-08-11T21:21:57.380 回答
1

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

PHP 简单的 HTML DOM 解析器

http://simplehtmldom.sourceforge.net/

特征

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

支持无效的 HTML。

使用像 jQuery 一样的选择器在 HTML 页面上查找标签。

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

于 2013-08-12T12:16:14.880 回答
1

如果您愿意,可以使用纯 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 );
}
于 2013-08-07T10:04:34.243 回答