【问题标题】:DOMDocument - how to replace nested elementsDOMDocument - 如何替换嵌套元素
【发布时间】:2012-10-24 13:04:43
【问题描述】:

我有这个 html 片段:

<font color="#ff0000">Lorem <font size="4">ipsum dolor</font> sit amet</font>

我想使用 DOMDocument 将每个 font 标记替换为 span。 那是我的 atm 函数:

$fonts = $xPath->query('//font');
foreach($fonts as $font){
    $style = '';
    $newFont = $dom->createElement('span',$font->nodeValue);
    if($font->hasAttribute('size')){
        $size = $font->getAttribute('size');
        $style.='font-size:'.round($size/2,1).'em; ';
    }
    if($font->hasAttribute('color')){
        $style.='color:'.$font->getAttribute('color').'; ';
    }
    if($style!='') $newFont->setAttribute('style',$style);
    $font->parentNode->replaceChild($newFont,$font);
}

我期望这个输出:

<span style="color:#ff0000; ">Lorem <span style="font-size:2em;">ipsum etc..

但我明白了:

<span style="color:#ff0000; ">Lorem ipsum dolor sit amet</span>

为什么?


我猜这是因为$font-&gt;parentNode-&gt;replaceChild($newFont,$font); 以某种方式仅用它的文本值替换了外部跨度......或者这个查询$xPath-&gt;query('//font') 是错误的。我喜欢有经验的建议...谢谢

【问题讨论】:

  • 为什么不直接使用正则表达式?
  • @rekire 我已经这样做了很长时间,但我正在尝试切换到 DOMDocument / html5lib ...codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html
  • 我知道html标签对不能用正则表达式替换,但是简单的关闭字体标签在每种情况下都可以用关闭跨度替换不是吗?
  • yup @rekire 即使使用 str_replace 和 preg_match 我也可以处理这种特殊情况......我只是想了解 DOMDocument 的工作原理,但我迷失在官方文档中;-)

标签: php domdocument domxpath


【解决方案1】:

简介

来自以下对话

rekire

为什么不简单地使用正则表达式? -

乔纳夫

rekire 我已经这样做了很长时间,但我正在尝试切换到 DOMDocument / html5lib ...codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html `

我完全同意这就是为什么我认为这不是 DomDocumentRegular Expresstion 的工作,因为您正在处理 HTML 5 不再支持的 depreciated HTML Tags 问题

含义

这意味着font 不是您可能还需要替换的唯一问题

  • 首字母缩略词
  • 小程序
  • 基本字体
  • 居中
  • 目录
  • 框架
  • 框架集
  • 无帧
  • s
  • 罢工
  • tt
  • xmp

使用整洁

我会推荐Tidy,它的设计目的是让你不必做你将要做的事情

形成 PHP 文档

Tidy 是 Tidy HTML cleanrepair 实用程序的绑定,它不仅允许您清理和以其他方式操作 HTML 文档,还可以遍历文档树

示例

$html = '<font color="#ff0000">Lorem <font size="4">ipsum dolor</font> sit amet</font>';
$config = array(
        'indent' => true,
        'show-body-only' => false,
        'clean' => true,
        'output-xhtml' => true,
        'preserve-entities' => true);

$tidy = new tidy();
echo $tidy->repairString($html, $config, 'UTF8');

输出

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <style type="text/css">
            /*<![CDATA[*/
            span.c2 {
                color: #FF0000
            }
            span.c1 {
                font-size: 120%
            }
            /*]]>*/
        </style>
    </head>
    <body><span class="c2">Lorem <span class="c1">ipsum dolor</span> sit amet</span>
    </body>
</html>

参见Cleaning HTML by removing extra/redundant formatting tags 的例子

更好的门槛:HTMLPurifier

您可以使用 HTMLPurifier,它也使用 Tidy 来清理 HTML,您只需设置 TidyLevel

HTML Purifier 是一个用 PHP 编写的符合标准的 HTML 过滤器库。 HTML Purifier 不仅会删除所有恶意代码(更广为人知的 XSS),还会使用经过全面审核、安全且许可的白名单,它还会确保您的文档符合标准,只有全面了解 W3C 规范才能实现的目标

require_once 'htmlpurifier-4.4.0/library/HTMLPurifier.auto.php';

$html = '<font color="#ff0000">Lorem <font size="4">ipsum dolor</font> sit amet</font>';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.TidyLevel', 'heavy'); 
$purifier = new HTMLPurifier($config);
$clean = $purifier->purify($html);

var_dump($clean);

输出

string '<span style="color:#ff0000;">Lorem <span style="font-size:large;">ipsum dolor</span> sit amet</span>' (length=100)

我想要 DOMDocument

如果你想要的只是 dom 而你不关心我所有的解释,那么你可以使用

$html = '<font color="#ff0000">Lorem <font size="4">ipsum dolor</font> sit amet</font>';
$dom = new DOMDocument();
$dom->loadHTML($html);
$nodes = iterator_to_array($dom->getElementsByTagName('font'));
foreach ( $nodes as $font ) {
    $css = array();
    $font->hasAttribute('size') and $css[] = 'font-size:' . round($font->getAttribute('size') / 2, 1) . 'em;';
    $font->hasAttribute('color') and $css[]  = 'color:' . $font->getAttribute('color') . ';';
    $span = $dom->createElement('span');
    $children = array();
    foreach ( $font->childNodes as $child )
        $children[] = $child;
    foreach ( $children as $child )
        $span->appendChild($child);
    $span->setAttribute('style', implode('; ', $css));
    $font->parentNode->replaceChild($span, $font);
}
echo "<pre>";
$dom->formatOutput = true;
print(htmlentities($dom->saveXML()));

【讨论】:

  • 你的 HTMLPurifier 和 DOMDocument 方法都像一个魅力!再次,非常感谢。我只剩下一个小问题:如何生成 html5 输出? HTMLPurifier 会将&lt;br&gt; 转换为&lt;br/&gt;。所以目前我将格式化的$clean 传递给html5libHTML5_Parser::parse($clean)。有没有办法只用 HTMLPurifier 达到相同的结果?
  • HTMLPurifier 实际上使用 tidy 来实现这一点...&lt;br /&gt; 现在只有 tidy 我确定它具有该功能
  • 嗯,我明白了。遗憾的是,像 DOMDocument 和 Querypath 这样的奇妙库还不支持 html5。谢谢你的时间,爸爸,我欠你一个 ;-)
  • 随时欢迎您...查看 wiki.php.net/rfc 我不确定 HTML 是否会很快得到支持
  • 这里为什么要用iterartor_to_array函数来转换DOMNodeList对象?
【解决方案2】:

使用 XSL 可以将标签更改为跨度。

<?php

$dom = new DOMDocument();

$dom->loadXML('<font color="#ff0000">Lorem <font size="4">ipsum dolor</font> sit amet</font>');

echo "Starting Point:" . $dom->saveXML() . PHP_EOL;

$xsl = new DOMDocument('1.0', 'UTF-8');
// Could be a seperate file
$xsl->loadXML(<<<XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

    <!-- Identity rule -->
    <xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template>
    <xsl:template match="text()"><xsl:value-of disable-output-escaping="yes" select="."/></xsl:template>

    <xsl:template match="font">
        <xsl:element name="span">
            <xsl:attribute name="style" xsl:space="default">
                <xsl:if test="@size">font-size: <xsl:value-of select="round(@size * 10 div 2) div 10" /> em;</xsl:if>
                <xsl:if test="@color">color: <xsl:value-of select="@color" />;</xsl:if>
            </xsl:attribute>
            <xsl:apply-templates select="node()"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>
XSLT
);

$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);
echo $proc->transformToXML($dom);

【讨论】:

  • +1 用于使用您的时间编写此答案...在我自己的情况下不起作用,因为我无法控制标记,但它可能对其他人有用跨度>
  • 非常酷!是否可以更改此设置,以便正确缩进 cmets 并且不添加 CDATA 部分,并且 HTML5 自关闭标签不会作为内联空标签出现,例如 &lt;br&gt;&lt;/br&gt;
  • xslt 中有许多选项可以调整输出。 On:w3.org/TR/xslt 可能对你有用。
【解决方案3】:

您的代码示例似乎遇到了几个不同的问题。

  1. 查询结果包含正在变化的项目
  2. $node->nodValue 不包含子节点

发现从 foreach 到 while 的变化,并多次运行查询解决了在变化的树中查找节点的问题。

$fonts = $xPath->query('//font');
while ($fonts->length > 0) {
    $font = $fonts->item(0);

    // Get bits of data before touching the tree

    $style   = '';
    if($font->hasAttribute('size')){
        $size   = $font->getAttribute('size');
        $style .= 'font-size:' . round($size/2, 1) . 'em; ';
    }
    if($font->hasAttribute('color')){
        $style .= 'color:' . $font->getAttribute('color') . '; ';
    }

    // Create the new node

    $newFont = $dom->createElement('span');
    if(!empty($style)) {
        $newFont->setAttribute('style', $style);
    }


    // Copy all children into a basic array to avoid an iterator
    // on a changing tree
    $children = iterator_to_array($font->childNodes);
    foreach ($children as $child) {
        // This has a side effect of removing the child from its old
        // location, which changes the tree
        $newFont->appendChild($child);
    }

    // Replace the parent's child, which changes the tree
    $font->parentNode->replaceChild($newFont, $font);


    // query again on the new tree
    $fonts = $xPath->query('//font');
}

【讨论】:

  • 谢谢,它有效!那么用 DOMDocument 替换嵌套元素没有简单的方法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-15
  • 2019-03-25
  • 2011-01-15
  • 1970-01-01
  • 2014-08-13
  • 1970-01-01
  • 2017-06-14
相关资源
最近更新 更多