通过DOM解析器转换PRE标记之间的空格

时间:2011-07-16 09:23:13

标签: php html dom html-parsing

正则表达式是我作为解决方案的最初想法,尽管很快就会发现DOM解析器更合适......我想在HTML文本字符串中的PRE标记之间将空格转换为 。例如:

<table atrr="zxzx"><tr>
<td>adfa a   adfadfaf></td><td><br /> dfa  dfa</td>
</tr></table>
<pre class="abc" id="abc">
abc 123
<span class="abc">abc 123</span>
</pre>
<pre>123 123</pre>

into(注意span标记属性中的空格被保留):

<table atrr="zxzx"><tr>
<td>adfa a   adfadfaf></td><td><br /> dfa  dfa</td>
</tr></table>
<pre class="abc" id="abc">
abc&nbsp;123
<span class="abc">abc&nbsp;123</span>
</pre>
<pre>123 123</pre>

结果需要序列化为字符串格式,以便在其他地方使用。

2 个答案:

答案 0 :(得分:2)

当您想要插入&nbsp;实体而没有DOM将&符号转换为&amp;实体时,这有点棘手,因为实体是节点,空格只是字符数据。以下是如何做到这一点:

$dom = new DOMDocument;
$dom->loadHtml($html);
$xp = new DOMXPath($dom);
foreach ($xp->query('//text()[ancestor::pre]') as $textNode)
{
    $remaining = $textNode;
    while (($nextSpace = strpos($remaining->wholeText, ' ')) !== FALSE) {
        $remaining = $remaining->splitText($nextSpace);
        $remaining->nodeValue = substr($remaining->nodeValue, 1);
        $remaining->parentNode->insertBefore(
            $dom->createEntityReference('nbsp'),
            $remaining
        );
    }
}

获取所有pre元素并使用其nodeValues在这里不起作用,因为nodeValue属性将包含所有子节点的组合 DOMText值,例如:它将包括span子节点的nodeValue。在pre元素上设置nodeValue会删除它们。

因此,我们不是获取前节点,而是获取所有在其轴上某处具有前元素父节点的DOMText节点:

DOMElement pre
    DOMText "abc 123"         <-- picking this
    DOMElement span
       DOMText "abc 123"      <-- and this one
DOMElement
    DOMText "123 123"         <-- and this one

然后我们遍历每个DOMText节点并将它们拆分为每个空间的单独DOMText节点。我们删除空格并在拆分节点之前插入实体节点,因此最终会得到一个像

的树
DOMElement pre
    DOMText "abc"
    DOMEntity nbsp
    DOMText "123"
    DOMElement span
       DOMText "abc"
       DOMEntity nbsp
       DOMText "123"
DOMElement
    DOMText "123"
    DOMEntity nbsp
    DOMText "123"

因为我们只使用了DOMText节点,所以任何DOMElements都保持不变,因此它将保留pre元素中的span元素。

警告:

您的代码段无效,因为它没有根元素。当使用loadHTML时,libxml会将任何缺少的结构添加到DOM中,这意味着您将获得包含DOCTYPE,html和body标签的代码段。

如果您想要恢复原始代码段,则必须getElementsByTagName正文节点并获取所有子代码以获取innerHTML。不幸的是,there is no innerHTML function or property in PHP's DOM implementation,所以我们必须手动执行此操作:

$innerHtml = '';
foreach ($dom->getElementsByTagName('body')->item(0)->childNodes as $child) {
    $tmp_doc = new DOMDocument();
    $tmp_doc->appendChild($tmp_doc->importNode($child,true));
    $innerHtml .= $tmp_doc->saveHTML();
}
echo $innerHtml;

另见

答案 1 :(得分:1)

我看到我之前的回答很短暂。以下是在<pre>代码中保留代码的解决方法:

<?php
$test = file_get_contents('input.html');
$dom = new DOMDocument('1.0');
$dom->loadHTML($test);
$xpath = new DOMXpath($dom);
$pre = $xpath->query('//pre//text()');
// manipulate nodes of type XML_TEXT_NODE
foreach($pre as $e) {
    $e->nodeValue = str_replace(' ', '__REPLACEMELATER__', $e->nodeValue);
    // when you attempt to write &nbsp; in a dom node
    // the & will be converted to &amp; :(
}
$temp = $dom->saveHTML();
$temp = str_replace('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">', '', $temp);
$temp = str_replace('<html>', '', $temp);
$temp = str_replace('<body>', '', $temp);
$temp = str_replace('</body>', '', $temp);
$temp = str_replace('</html>', '', $temp);
$temp = str_replace('__REPLACEMELATER__', '&nbsp;', $temp);
echo $temp;
?>

输入

<p>paragraph 1 remains untouched</p>
<pre>preformatted 1</pre>
<div>
    <pre>preformatted 2</pre>
</div>
<div>
    <pre>preformatted 3 <span class="foo">span text</span> preformatted 3</pre>
</div>
<div>
    <pre>preformatted 4 <span class="foo">span <b class="bla">bold test</b> text</span> preformatted 3</pre>
</div>

输出

<p>paragraph 1 remains untouched</p>
<pre>preformatted&nbsp;1</pre>
<div>
    <pre>preformatted&nbsp;2</pre>
</div>
<div>
    <pre>preformatted&nbsp;3&nbsp;<span class="foo">span&nbsp;text</span>&nbsp;preformatted&nbsp;3</pre>
</div>
<div>
    <pre>preformatted&nbsp;4&nbsp;<span class="foo">span&nbsp;<b class="bla">bold&nbsp;test</b>&nbsp;text</span>&nbsp;preformatted&nbsp;3</pre>
</div>

注意#1

PHP中的

DOMDocument::saveHTML()方法&gt; = 5.3.6允许您指定要输出的节点。否则,您可以使用str_replace()preg_replace()来删除doctype,html和body标记。

注意#2

这个技巧似乎有效,并且只产生一行代码,但我不确定它是否可以保证工作:

$e->nodeValue = utf8_encode(str_replace(' ', "\xA0", $e->nodeValue));
// dom library will attempt to convert 0xA0 to &nbsp;
// nodeValue expects utf-8 encoded data but 0xA0 is not valid in this encoding
// hence replaced string must be utf-8 encoded