2010-02-10 12 views
8

Używanie PHP Próbuję pobrać ciąg HTML przekazany z edytora WYSIWYG i zamienić elementy potomne elementu wewnątrz wstępnie załadowanego dokumentu HTML na nowy kod HTML.PHP DOMDocument zastępuje dziecko DOMElement ciągiem HTML

Do tej pory ładuję dokument identyfikujący element, który chcę zmienić, ale proces konwersji HTML na coś, co można umieścić w DOMElement, umyka mi.

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 
} 

Odpowiedz

13

Jeśli ciąg HTML może być analizowany jako XML, można to zrobić (po usunięciu elementu wszystkich węzłów potomnych):

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

Jeśli $ html_string nie może być analizowany jako XML, zakończy się niepowodzeniem. Jeśli tak, musisz użyć metody loadHTML(), która jest mniej restrykcyjna - ale doda elementy wokół fragmentu, który będziesz musiał usunąć.

W przeciwieństwie do PHP, JavaScript ma właściwość innerHTML, która pozwala to zrobić bardzo łatwo. Potrzebowałem czegoś podobnego do projektu, więc rozszerzyłem DOMElement PHP, tak aby obejmował JavaScript-like innerHTML access.

Dzięki niemu można uzyskać dostępu do właściwości innerHTML i zmienić go tak samo jak w JavaScript:

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

Źródło: http://www.keyvan.net/2012/11/php-domdocument-replace-domelement-child-with-html-string/

+0

@Greg, czy nie powinienem decydować, gdzie trafiają moje składki? A od kiedy mówiłeś o świecie? Postanowiłem przenieść mój wkład do mojego bloga po tym, jak część moich wkładów została usunięta ze StackOverflow i ukryta przed mną. Chciałbym zachować to w ten sposób, więc proszę, cofnij zmianę. – Keyvan

+0

Link do potencjalnego rozwiązania jest zawsze mile widziany, ale proszę dodać kontekst wokół linku, aby inni użytkownicy mieli pojęcie, co to jest i dlaczego tam jest. Zawsze podawaj najważniejszą część ważnego linku, na wypadek, gdyby strona docelowa była nieosiągalna lub stała w trybie offline. Źródło: [* How to answer *] (http://stackoverflow.com/questions/how-to-answer) – Greg

+0

@Greg, znam zasady. Wysłałem tutaj odpowiedź i przeniosłem ją na moją stronę ze względu na sposób, w jaki moje inne komentarze były przetwarzane na tej stronie - jak wspomniałem powyżej, zostały one usunięte i ukryte przede mną. To, czemu tak bardzo się sprzeciwiacie, jest poza mną. Pewne jedzenie do przemyślenia przez jednego z twórców tej strony http://www.codinghorror.com/blog/2009/08/are-you-a-digital-sharecropper.html "Czy Twoje uwagi mogą zostać odwołane, usunięte lub trwale? zrobione offline bez twojej zgody? " On Stackoverflow: tak. Na mojej własnej stronie: nie. – Keyvan

1

Można użyć loadHTML() na fragment kodu, a następnie dołączyć wynikowe utworzone węzły do ​​oryginalnego drzewa DOM.

+0

Czy sugerujesz utworzenie nowego dokumentu DOMD przy użyciu kodu ładującego HTML, a następnie zabranie dzieci ze znacznika body nowego dokumentu i dołączenie ich do oryginalnego modelu DOM? Czy istnieje inna funkcja loadHTML(), której mi brakuje. – AWinter

+0

Naprawdę nienawidzę tego, jak znaczniki html i body są dodawane automatycznie, gdy robisz takie rzeczy jak saveHTML() lub loadHTML(). Czy istnieje inne proste rozwiązanie, poza pisaniem otoki, która je odetnie? –

0

Wiem, że to stary wątek (ale odpowiadać na to dlatego, że również szukamy rozwiązania tego problemu). Utworzyłem łatwą metodę zamiany zawartości za pomocą tylko jednej linii podczas jej używania. Aby lepiej zrozumieć tę metodę, dodałem także funkcje o nazwie kontekstowej.

To jest teraz część mojej biblioteki, więc to jest powodem wszystkich nazw funkcji tutaj, wszystkie funkcje zaczynają się od prefiksu "su".

Jest bardzo łatwy w użyciu i bardzo wydajny (a także zawiera mniej kodu).

Oto kod:

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}))); 
} 

Niektóre funkcje kontekstowe:

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); } 

Jak go używać:

W poniższych przykładach zakładam, że istnieje już zawartość załadowana do zmiennej nazwała się $sMyHtml, a zmienna $sMyNewContent zawiera trochę nowego html. Zmienna $sMyHtml zawiera element o nazwie/o identyfikatorze "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'); 
+0

@brasofilo, czy zmienisz wszystkie moje posty? Głupi jesteś! – Codebeat

+0

Nie ... Miałem nadzieję, że ** zrobisz to **;) http://meta.stackexchange.com/questions/28416/what-is-the-policy-on-signatures-and-links- in-odpowiedzi – brasofilo

+0

@brasofilo, przykro mi, smutno, u, mam jeszcze kilka innych ważnych rzeczy do zrobienia. – Codebeat

1

Obecna odpowiedź Zaakceptowany sugeruje użycie appendXML(), ale przyznaje, że nie zajmie się kompleksową html, takich jak to, co jest zwracany z edytora WYSISYG jak określono w pierwotnym pytaniu. Zgodnie z sugestią loadHTML() może rozwiązać ten problem. ale nikt jeszcze nie pokazał, jak to zrobić.

Oto, jak uważam, najlepsza/prawidłowa odpowiedź na oryginalne pytanie dotyczące problemów z kodowaniem, "Dokumenty są puste", ostrzeżenia i błędy "Błędu dokumentu", które ktoś prawdopodobnie uderzy, jeśli napisze to od podstaw. Wiem, że znalazłem je po wykonaniu wskazówek z poprzednich odpowiedzi.

To jest kod z witryny, którą obsługuję, która wstawia zawartość paska bocznego WordPress do treści $ posta. Zakłada się, że $ doc jest prawidłowym DOMDocumentem podobnym do sposobu, w jaki $ doc jest zdefiniowany w pierwotnym pytaniu. Zakłada także, że element $ jest tagiem, po którym chcesz wstawić zawartość paska bocznego (lub cokolwiek innego).

  // 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); 
      } 

Po tym wszystkim jest zrobione, jeśli nie chcą mieć źródło pełnego dokumentu HTML z tagami, a czego nie można wygenerować więcej zlokalizowaną html z tym:

// 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; 
+0

Dziękujemy za udostępnienie tego rozwiązania! Działa jak marzenie! – Damneddani

+0

@Damneddani Zwróć uwagę, że savehtml ($ body) zwraca HTML ze znacznikiem body. Jeśli wstawiasz kod HTML na inną stronę, spowoduje to powstanie nieprawidłowego html. Spróbuj zrobić coś takiego: $ rootContent = ''; Foreach ($ rootNode-> childNodes jako węzeł $) { $ rootContent. = $ RootNode-> ownerDocument-> saveHTML ($ node); } // Brak zwrotu pełnej treści z dodaną treścią paska bocznego. return $ rootContent; –

Powiązane problemy