DOMDocument — как заменить вложенные элементы

У меня есть этот фрагмент html:

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

и я хочу заменить каждый тег font на span, используя DOMDocument. Это моя функция 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->parentNode->replaceChild($newFont,$font); каким-то образом заменяет внешний диапазон только его текстовым значением... Или, может быть, этот запрос $xPath->query('//font') неверен. Мне бы понравилось предложение опытного... спасибо


person Giona    schedule 04.11.2012    source источник
comment
Почему бы вам просто не использовать регулярные выражения?   -  person rekire    schedule 06.11.2012
comment
@rekire я давно этим занимаюсь, но пытаюсь перейти на DOMDocument/html5lib... codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html   -  person Giona    schedule 06.11.2012
comment
Я знаю, что пары html-тегов нельзя заменить регулярными выражениями, но простые закрывающие теги шрифта можно заменить закрывающим интервалом в любом случае, не так ли?   -  person rekire    schedule 06.11.2012
comment
да @rekire я мог бы справиться с этим конкретным случаем даже с str_replace и preg_match ... я просто хочу понять, как работает DOMDocument, но я теряюсь в официальной документации ;-)   -  person Giona    schedule 06.11.2012


Ответы (3)


Введение

Из следующих разговоров

требовать

Почему бы вам просто не использовать регулярные выражения? –

ГионаФ

rekire я давно так делаю, но пытаюсь перейти на DOMDocument/html5lib... codinghorror.com/blog/2009/11/parsing-html-the-cthulhu-way.html`

Я полностью согласен, поэтому я считаю, что это не работа как для DomDocument, так и для Regular Expresstion, потому что вы имеете дело с проблемами depreciated HTML Tags, которые больше не поддерживаются в HTML 5.

Значение

Это означает, что font — не единственная проблема, которую вам, возможно, придется заменить.

  • акроним
  • апплет
  • базовый шрифт
  • большой
  • центр
  • директор
  • Рамка
  • набор фреймов
  • без фреймов
  • s
  • забастовка
  • tt
  • xmp

Используйте аккуратный

Я бы порекомендовал Tidy, который был разработан таким образом, чтобы у вас не было делать то, что ты собираешься сделать

ФОРМА PHP-ДОКУМЕНТА

Tidy — это привязка к утилите Tidy HTML для очистки и восстановления, которая позволяет не только очищать 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>

См. также раздел Очистка HTML путем удаления лишних/избыточных тегов форматирования. для примеров

Лучший вариант: HTMLPurifier

Вы можете использовать HTMLPurifier, который также использует Tidy для очистки HTML. установить TidyLevel

HTML Purifier — это библиотека фильтров HTML, соответствующая стандартам, написанная на PHP. 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

Если все, что вам нужно, это дом, и вам все равно на все мои объяснения, вы можете использовать

$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()));
person Baba    schedule 08.11.2012
comment
Оба ваших метода HTMLPurifier и DOMDocument работают как шарм! Еще раз большое спасибо. У меня осталась небольшая проблема: как я могу создать вывод html5? HTMLPurifier превратит <br> в <br/>. Итак, на данный момент я передаю отформатированный $clean в html5lib: HTML5_Parser::parse($clean). Есть ли способ добиться того же результата только с помощью HTMLPurifier? - person Giona; 11.11.2012
comment
HTMLPurifier на самом деле использует приборку для достижения этой цели ... <br /> на данный момент это только приборка, я уверен, что у нее есть функция - person Baba; 11.11.2012
comment
Ммм я вижу. Печально, что такие фантастические библиотеки, как DOMDocument и Querypath, пока не поддерживают html5. Спасибо за ваше время Баба, я должен вам один ;-) - person Giona; 11.11.2012
comment
Добро пожаловать в любое время ... глядя на wiki.php.net/rfc, я не уверен, что HTML будет поддерживается в ближайшее время - person Baba; 11.11.2012
comment
Почему вы используете здесь функцию iterartor_to_array для преобразования объекта DOMNodeList? - person Aftab Naveed; 22.02.2019

С помощью 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);
person rrehbein    schedule 07.11.2012
comment
+1 за использование вашего времени для написания этого ответа ... не работает в моем случае, потому что я не контролирую разметку, но это может быть полезно кому-то другому. - person Giona; 11.11.2012
comment
Очень круто! Можно ли изменить это, чтобы комментарии имели правильный отступ, а разделы CDATA НЕ добавлялись, а самозакрывающиеся теги HTML5 не отображались как встроенные пустые теги, такие как <br></br>? - person Alix Axel; 18.06.2013
comment
В xslt есть несколько опций, с которыми можно повозиться, чтобы настроить вывод. На: w3.org/TR/xslt есть ‹xsl:preserve-space/›, и ‹xsl:output method=/›, который может подойти вам. - person rrehbein; 19.06.2013

Похоже, ваш пример кода сталкивается с несколькими разными проблемами.

  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');
}
person rrehbein    schedule 06.11.2012
comment
Спасибо, это работает! Значит, нет простого способа заменить вложенные элементы на DOMDocument? - person Giona; 07.11.2012