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

时间:2013-03-21 16:07:17

标签: php html dom xpath

我想找到所有应该包含在<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 个答案:

答案 0 :(得分:6)

好的,所以让我把我的评论重新解释为答案。如果要匹配所有文本节点,只需从XPath表达式中删除//div部分即可。所以它变成了:

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

答案 1 :(得分:2)

您的方案有很多边缘情况,而这个词在顶部添加。我假设你想做经典的一个双重中断开始一个新的段落,但这次也是在父<div>(或者其他块元素)中。

我会让HTML解析器完成大部分工作,但我仍然会使用文本搜索和替换(在xpath旁边)。所以你会看到的是有点hackish但我觉得很稳定:

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

(.|./div)/text()

此xpath与 anchor 元素相关,该元素是<body>标记,因为它表示加载到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 <p>标记。 HTML解析器会将这些转换为足够的<p>...</p>对,这样我就可以自己编写该算法(即使这可能很有趣)。这基本上像我曾经在其他一些答案中概述的那样,但我再也找不到链接了:

  1. 修改DOM树后,再次获取<body>的固有HTML。
  2. "<p>"替换设置标记(此处我也标记了该类以使其可见)
  3. 再次将HTML片段加载到解析器中,使用正确的<p>...</p>对重新创建DOM。
  4. 再次从DOMDocument解析器获取HTML,现在终于。
  5. 这些概述的代码步骤(暂时跳过一些函数定义):

    $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 - 只是看到,空格规范化可能不适用于那里。所以要小心使用。

答案 2 :(得分: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 );
}

答案 3 :(得分:1)

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

PHP Simple HTML DOM Parser

http://simplehtmldom.sourceforge.net/

功能

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

支持无效的HTML。

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

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