防止 DOMDocument::loadHTML() 转换实体

Posted

技术标签:

【中文标题】防止 DOMDocument::loadHTML() 转换实体【英文标题】:Preventing DOMDocument::loadHTML() from converting entities 【发布时间】:2011-11-12 16:25:56 【问题描述】:

我有一个字符串值,我正在尝试为其提取列表项。我想提取文本和任何子节点,但是,DOMDocument 正在将实体转换为字符,而不是保持原始状态。

我尝试将 DOMDocument::resolveExternals 和 DOMDocument::substituteEntities 设置为 false,但这没有任何效果。应该注意的是,我在 Win7 上运行 php 5.2.17。

示例代码为:

$example = '<ul><li>text</li>'.
    '<li>&frac12; of this is <strong>strong</strong></li></ul>';

echo 'To be converted:'.PHP_EOL.$example.PHP_EOL;

$doc = new DOMDocument();
$doc->resolveExternals = false;
$doc->substituteEntities = false;

$doc->loadhtml($example);

$domNodeList = $doc->getElementsByTagName('li');
$count = $domNodeList->length;

for ($idx = 0; $idx < $count; $idx++) 
    $value = trim(_get_inner_html($domNodeList->item($idx)));
    /* remainder of processing and storing in database */
    echo 'Saved '.$value.PHP_EOL;


function _get_inner_html( $node ) 
    $innerHTML= '';
    $children = $node->childNodes;
    foreach ($children as $child) 
        $innerHTML .= $child->ownerDocument->saveXML( $child );
    

    return $innerHTML;

&amp;frac12; 最终被转换为 ½(单字符/UTF-8 版本,而不是实体版本),这不是所需的格式。

【问题讨论】:

您如何确定发生了转换?您是否以 HTML 格式显示结果? 带有回显(实际代码稍微复杂一些)。我将使用我目前正在使用的回声更新示例代码。回显结果正在输出到日志文件。结果显示在 Textpad(如记事本)中,而不是 HTML。 如何将$example 字符串加载到DOMDocument 中? 5.3.6 - php.net/manual/en/domdocument.savehtml.php(此支持$doc-&gt;saveHTML( new DOMNode('&amp;frac12;') ); @Phil。为了确保示例代码在发布之前确实有效,有一些话要说。但它确实有效。 【参考方案1】:

非 PHP 5.3.6++ 的解决方案

$html =<<<HTML
<ul><li>text</li>
<li>&frac12; of this is <strong>strong</strong></li></ul>
HTML;

$doc = new DOMDocument();
$doc->resolveExternals = false;
$doc->substituteEntities = false;
$doc->loadHTML($html);
foreach ($doc->getElementsByTagName('li') as $node)

  echo htmlentities(iconv('UTF-8', 'ISO-8859-1', $node->nodeValue)), "\n";

【讨论】:

它对待 ½正确,但剥离 。我可能会尝试 _get_inner_html() 识别 DOMElement 和 DOMText 之间的引用,并使用适当的函数进行转换(htmlentities 或递归调用)。【参考方案2】:

基于answer provided by ajreal,我扩展了示例变量以处理更多情况,并更改了_get_inner_html() 以进行递归调用并处理文本节点的实体转换。

这可能不是最好的答案,因为它对元素做了一些假设(例如没有属性)。但是由于我的特殊需求不需要传递属性(但是..我确信我的示例数据稍后会向我抛出那个),这个解决方案对我有用。

$example = '<ul><li>text</li>'.
'<li>&frac12; of this is <strong>strong</strong></li>'.
'<li>Entity <strong attr="3">in &frac12; tag</strong></li>'.
'<li>Nested nodes <strong attr="3">in &frac12; <em>tag &frac12;</em></strong></li>'.
'</ul>';

echo 'To be converted:'.PHP_EOL.$example.PHP_EOL;

$doc = new DOMDocument();
$doc->resolveExternals = true;
$doc->substituteEntities = false;

$doc->loadHTML($example);

$domNodeList = $doc->getElementsByTagName('li');
$count = $domNodeList->length;

for ($idx = 0; $idx < $count; $idx++) 
    $value = trim(_get_inner_html($domNodeList->item($idx)));

    /* remainder of processing and storing in database */
    echo 'Saved '.$value.PHP_EOL;



function _get_inner_html( $node ) 
    $innerHTML= '';
    $children = $node->childNodes;
    foreach ($children as $child) 
        echo 'Node type is '.$child->nodeType.PHP_EOL;
        switch ($child->nodeType) 
        case 3:
            $innerHTML .= htmlentities(iconv('UTF-8', 'ISO-8859-1', $child->nodeValue));
            break;
        default:
            echo 'Non text node has '.$child->childNodes->length.' children'.PHP_EOL;
            echo 'Node name '.$child->nodeName.PHP_EOL;
            $innerHTML .= '<'.$child->nodeName.'>';
            $innerHTML .= _get_inner_html( $child );
            $innerHTML .= '</'.$child->nodeName.'>';
            break;
        
    

    return $innerHTML;

【讨论】:

使用 ISO-8859-1//TRANSLIT 或 ISO-8859-1//IGNORE 以避免通知,并截断未成功转换的字符的字符串。例如,&amp;trade; 的存在会导致通知,并使用 //TRANSLIT 选项转换为 TM【参考方案3】:

不需要迭代子节点:

function innerHTML($node)
         $html=$node->ownerDocument->saveXML($node);
          return preg_replace("%^<$node->nodeName[^>]*>|</$node->nodeName>$%", '', $html);
         

【讨论】:

在这个例子中用什么代替了 htmlentites(iconv()) 调用?看起来它只是剥离了外部标签。

以上是关于防止 DOMDocument::loadHTML() 转换实体的主要内容,如果未能解决你的问题,请参考以下文章

PHP DOMDocument loadHTML 错误

DOMDocument::loadHTML(): 由于输入错误,输入转换失败

使用 php 从 html 文件中计算 li 项

Swift 防止按钮重复点击

如何防止用户重复提交数据

PHP 怎么防止GET方式提交重复数据?