PHP DOMDocument заменит дочерний элемент DOMElement на строку HTML - PullRequest
8 голосов
/ 10 февраля 2010

Используя PHP, я пытаюсь взять строку HTML, переданную из редактора WYSIWYG, и заменить дочерние элементы элемента внутри предварительно загруженного документа HTML новым HTML.

Пока я загружаю документ, идентифицирующий элемент, который я хочу изменить по идентификатору, но процесс преобразования HTML во что-то, что может быть помещено в элемент DOMElement, ускользает от меня.

libxml_use_internal_errors(true);

$doc = new DOMDocument();
$doc->loadHTML($html);

$element = $doc->getElementById($item_id);
if(isset($element)){
    //Remove the old children from the element
    while($element->childNodes->length){
        $element->removeChild($element->firstChild);
    }

    //Need to build the new children from $html_string and append to $element
}

Ответы [ 4 ]

13 голосов
/ 23 августа 2010

Если строку HTML можно проанализировать как XML, вы можете сделать это (после очистки элемента всех дочерних узлов):

$fragment = $doc->createDocumentFragment();
$fragment->appendXML($html_string);
$element->appendChild($fragment);

Если $ html_string не может быть проанализирован как XML, произойдет сбой. Если это произойдет, вам придется использовать loadHTML (), которая является менее строгой - но она добавит элементы вокруг фрагмента, который вам придется удалить.

В отличие от PHP, Javascript имеет свойство innerHTML, которое позволяет вам сделать это очень легко. Мне нужно было что-то подобное для проекта, поэтому я расширил DOME-элемент PHP, включив в него доступ как Javascript innerHTML.

С его помощью вы можете получить доступ к свойству innerHTML и изменить его так же, как в Javascript:

echo $element->innerHTML;
$elem->innerHTML = '<a href="http://example.org">example</a>';

Источник: http://www.keyvan.net/2012/11/php-domdocument-replace-domelement-child-with-html-string/

1 голос
/ 19 октября 2016

Текущий принятый ответ предлагает использовать appendXML (), но признает, что он не будет обрабатывать сложные html, такие как то, что возвращается из редактора WYSISYG, как указано в исходном вопросе. Как и предполагалось, loadHTML () может решить эту проблему. но никто еще не показал, как.

Я считаю, что это лучший / правильный ответ на первоначальный вопрос, касающийся проблем кодирования, предупреждений «Фрагмент документа пуст» и ошибок «Неправильная ошибка документа», которые кто-то может получить, если напишет это с нуля. Я знаю, что нашел их после следования подсказкам в предыдущих ответах.

Это код с сайта, который я поддерживаю, который вставляет содержимое боковой панели WordPress в $ содержимое поста. Предполагается, что $ doc является действительным DOMDocument, аналогичным тому, как определено $ doc в исходном вопросе. Также предполагается, что $ element - это тег, после которого вы хотите вставить боковой контент (или любой другой).

            // NOTE: Cannot use a document fragment here as the AMP html is too complex for the appendXML function to accept.
            // Instead create it as a document element and insert that way.
            $node = new DOMDocument();
            // Note that we must encode it correctly or strange characters may appear.
            $node->loadHTML( mb_convert_encoding( $sidebarContent, 'HTML-ENTITIES', 'UTF-8') );
            // Now we need to move this document element into the scope of the content document 
            // created above or the insert/append will be rejected.
            $node = $doc->importNode( $node->documentElement, true );
            // If there is a next sibling, insert before it.
            // If not, just add it at the end of the element we did find.
            if (  $element->nextSibling ) {
                $element->parentNode->insertBefore( $node, $element->nextSibling );
            } else {
                $element->parentNode->appendChild($node);
            }

После того, как все это будет сделано, если вы не хотите иметь источник полного HTML-документа с тегами body, а что нет, вы можете сгенерировать более локализованный html с помощью этого:

    // Now because we have moved the post content into a full document, we need to get rid of the 
    // extra elements that make it a document and not a fragment
    $body = $doc->getElementsByTagName( 'body' );
    $body = $body->item(0);

    // If you need an element with a body tag, you can do this.
    // return $doc->savehtml( $body );

    // Extract the html from the body tag piece by piece to ensure valid html syntax in destination document
    $bodyContent = ''; 
    foreach( $body->childNodes as $node ) { 
            $bodyContent .= $body->ownerDocument->saveHTML( $node ); 
    } 
    // Now return the full content with the new content added. 
    return $bodyContent;
1 голос
/ 10 февраля 2010

Вы можете использовать loadHTML() для фрагмента кода, а затем добавить полученные созданные узлы в исходное дерево DOM.

0 голосов
/ 15 июля 2013

Я знаю, что это старая ветка (но ответьте на это, потому что также ищите решение этой проблемы). Я сделал простой способ заменить содержимое одной строкой при его использовании. Чтобы лучше понять метод, я также добавлю несколько контекстных именованных функций.

Теперь это часть моей библиотеки, поэтому здесь указаны все имена функций, все функции начинаются с префикса su.

Это очень простой в использовании и очень мощный (и довольно мало кода).

Вот код:

function suSetHtmlElementById( &$oDoc, &$s, $sId, $sHtml, $bAppend = false, $bInsert = false, $bAddToOuter = false )
 {
    if( suIsValidString( $s ) && suIsValidString( $sId ))
    {
     $bCreate = true;
     if( is_object( $oDoc ))
     {
       if( !( $oDoc instanceof DOMDocument ))
        { return false; }
       $bCreate = false;
     }

     if( $bCreate )
      { $oDoc = new DOMDocument(); }

     libxml_use_internal_errors(true);
     $oDoc->loadHTML($s);
     libxml_use_internal_errors(false);
     $oNode = $oDoc->getElementById( $sId );

     if( is_object( $oNode ))
     { 
       $bReplaceOuter = ( !$bAppend && !$bInsert );

       $sId = uniqid('SHEBI-');
       $aId = array( "<!-- $sId -->", "<!--$sId-->" );

       if( $bReplaceOuter )
       {
         if( suIsValidString( $sHtml ) )
         {
             $oNode->parentNode->replaceChild( $oDoc->createComment( $sId ), $oNode );
             $s = $oDoc->saveHtml();
             $s = str_replace( $aId, $sHtml, $oDoc->saveHtml());
         }
         else { $oNode->parentNode->removeChild( $oNode ); 
                $s = $oDoc->saveHtml();
              }
         return true;
       }

       $bReplaceInner = ( $bAppend && $bInsert );
       $sThis = null;

       if( !$bReplaceInner )
       {
         $sThis = $oDoc->saveHTML( $oNode );
         $sThis = ($bInsert?$sHtml:'').($bAddToOuter?$sThis:(substr($sThis,strpos($sThis,'>')+1,-(strlen($oNode->nodeName)+3)))).($bAppend?$sHtml:''); 
       }

       if( !$bReplaceInner && $bAddToOuter )
       { 
          $oNode->parentNode->replaceChild( $oDoc->createComment( $sId ), $oNode );
          $sId = &$aId;
       }
       else { $oNode->nodeValue = $sId; }

       $s = str_replace( $sId, $bReplaceInner?$sHtml:$sThis, $oDoc->saveHtml());
       return true;
     }
    } 
    return false; 
 }

// A function of my library used in the function above:
function suIsValidString( &$s, &$iLen = null, $minLen = null, $maxLen = null )
{
  if( !is_string( $s ) || !isset( $s{0} ))
   { return false; }

  if( $iLen !== null )
   { $iLen = strlen( $s ); }

  return (( $minLen===null?true:($minLen > 0 && isset( $s{$minLen-1} ))) && 
           $maxLen===null?true:($maxLen >= $minLen && !isset( $s{$maxLen})));   
}   

Некоторые функции контекста:

 function suAppendHtmlById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, true, false ); }

 function suInsertHtmlById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, false, true ); }

 function suAddHtmlBeforeById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, false, true, true ); }

 function suAddHtmlAfterById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, true, false, true ); }

 function suSetHtmlById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, true, true ); }

 function suReplaceHtmlElementById( &$s, $sId, $sHtml, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, $sHtml, false, false ); }

 function suRemoveHtmlElementById( &$s, $sId, &$oDoc = null )
 { return suSetHtmlElementById( $oDoc, $s, $sId, null, false, false ); }

Как это использовать:

В следующих примерах я предполагаю, что в переменную с именем $sMyHtml уже загружено содержимое, а переменная $sMyNewContent содержит новый html. Переменная $sMyHtml содержит элемент с именем / с идентификатором 'example_id'.

// Example 1: Append new content to the innerHTML of an element (bottom of element):
if( suAppendHtmlById( $sMyHtml, 'example_id', $sMyNewContent ))
 { echo $sMyHtml; }
 else { echo 'Element not found?'; }

// Example 2: Insert new content to the innerHTML of an element (top of element):
suInsertHtmlById( $sMyHtml, 'example_id', $sMyNewContent );    

// Example 3: Add new content ABOVE element:
suAddHtmlBeforeById( $sMyHtml, 'example_id', $sMyNewContent );    

// Example 3: Add new content BELOW/NEXT TO element:
suAddHtmlAfterById( $sMyHtml, 'example_id', $sMyNewContent );    

// Example 4: SET new innerHTML content of element:
suSetHtmlById( $sMyHtml, 'example_id', $sMyNewContent );    

// Example 5: Replace entire element with new content:
suReplaceHtmlElementById( $sMyHtml, 'example_id', $sMyNewContent );    

// Example 6: Remove entire element:
suSetHtmlElementById( $sMyHtml, 'example_id' ); 
...