2010-02-10 12 views
8

Utilizzo di PHP Sto tentando di prendere una stringa HTML passata da un editor WYSIWYG e sostituire i figli di un elemento all'interno di un documento HTML precaricato con il nuovo HTML.PHP DOMDocument sostituire child DOMElement con stringa HTML

Finora sto caricando il documento identificando l'elemento che voglio modificare per ID ma il processo per convertire un HTML in qualcosa che può essere inserito all'interno di un DOMElement mi sfugge.

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 
} 

risposta

13

Se la stringa HTML può essere analizzato come XML, è possibile farlo (dopo aver eliminato l'elemento di tutti i figli):

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

Se $ html_string non può essere analizzato come XML, fallirà. Se lo fa, dovrai usare loadHTML(), che è meno severo, ma aggiungerà elementi attorno al frammento che dovrai eliminare.

A differenza di PHP, Javascript ha la proprietà innerHTML che consente di farlo molto facilmente. Avevo bisogno di qualcosa del genere per un progetto, quindi ho esteso il DOMElement di PHP per includere l'accesso innerHTML simile a Javascript.

Con esso è possibile accedere alla proprietà innerHTML e modificarlo proprio come si farebbe in Javascript:

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

Fonte: http://www.keyvan.net/2012/11/php-domdocument-replace-domelement-child-with-html-string/

+0

@Greg, non dovrei decidere dove vanno i miei contributi? E da quando parlavi per il mondo? Ho deciso di trasferire i miei contributi sul mio blog dopo che alcuni dei miei contributi sono stati cancellati da StackOverflow e nascosti da me. Mi piacerebbe mantenerlo in questo modo, quindi ti preghiamo di annullare la modifica. – Keyvan

+0

Un collegamento a una potenziale soluzione è sempre il benvenuto, ma per favore aggiungi un contesto intorno al link in modo che i tuoi colleghi possano avere un'idea di cosa sia e perché è lì. Citare sempre la parte più rilevante di un link importante, nel caso in cui il sito target non sia raggiungibile o sia permanentemente offline. Fonte: [* Come rispondere *] (http://stackoverflow.com/questions/how-to-answer) – Greg

+0

@Greg, sono a conoscenza delle linee guida. Ho postato la risposta qui in origine e l'ho spostata sul mio sito a causa del modo in cui i miei altri contributi sono stati gestiti su questo sito - come ho detto sopra, sono stati cancellati e nascosti da me. Perché ti opponi così tanto a un collegamento è oltre me. Alcuni spunti di riflessione da uno dei creatori di questo sito http://www.codinghorror.com/blog/2009/08/are-you-a-digital-sharecropper.html "I tuoi contributi possono essere revocati, cancellati o permanentemente portato offline senza il tuo consenso? " Su StackOverflow: sì. Sul mio sito: no. – Keyvan

1

È possibile utilizzare loadHTML() su un frammento di codice e quindi aggiungere i nodi creati risultanti in l'albero DOM originale.

+0

Ti suggerirei di creare un nuovo DOMDocument usando load HTML, quindi prendendo i figli del body tag del nuovo documento e aggiungendoli al DOM originale? O c'è un'altra funzione loadHTML() che mi manca. – AWinter

+0

Odio davvero come i tag html e body vengono aggiunti automaticamente quando fai cose come saveHTML() o loadHTML(). Esiste una soluzione semplice oltre a scrivere un wrapper che li spogli? –

0

So che questo è un vecchio filo (ma rispondo su questo perché anche cercando una soluzione a questo). Ho fatto un metodo semplice per sostituire il contenuto con una sola riga quando lo si utilizza. Per comprendere meglio il metodo, aggiungo anche alcune funzioni con il nome di contesto.

Questa è ora una parte della mia libreria, quindi questo è il motivo di tutti i nomi di funzioni qui, tutte le funzioni iniziano con il prefisso 'su'.

È molto facile da usare e molto potente (e molto meno codice).

Ecco il codice:

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

Alcune funzioni di contesto:

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

Come si usa:

Negli esempi che seguono, suppongo che c'è già contenuto caricato in una variabile le ha chiamato $sMyHtml e la variabile $sMyNewContent contiene qualche nuovo html. La variabile $sMyHtml contiene un elemento chiamato/con l'id '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, hai intenzione di cambiare tutti i miei post? Sciocco! – Codebeat

+0

No ... stavo sperando che ** lo farai **; http://meta.stackexchange.com/questions/28416/what-is-the-policy-on-signatures-and-links- in-risposte – brasofilo

+0

@brasofilo, mi spiace, triste per te, ho altre cose più importanti da fare. – Codebeat

1

L'attuale risposta accettata suggerisce di utilizzare appendXML(), ma riconosce che non gestirà html complesso come quello che viene restituito da un editor WYSIWYG come specificato nella domanda originale. Come suggerito, loadHTML() può risolvere questo problema. ma nessuno ha ancora dimostrato come.

Questo è ciò che credo sia la migliore risposta corretta ai problemi di codifica domanda iniziale di indirizzamento /, "Documento frammento è vuoto" avvertimenti ed errori "sbagliato errore Document" che qualcuno è probabile che a colpire se scrivono questo da zero. So di averli trovati dopo aver seguito i suggerimenti nelle risposte precedenti.

Questo è il codice di un sito che supporto che inserisce il contenuto della barra laterale di WordPress nel contenuto $ di un post. Si presuppone che $ doc sia un DOMDocument valido simile al modo in cui $ doc è definito nella domanda originale. Assume anche che $ è il tag dopo il quale si desidera inserire il sidebarcontent (o qualsiasi altra cosa).

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

Dopo tutto questo è fatto, se non si vuole avere la fonte di un documento HTML completo con tag body e cosa no, è possibile generare il codice HTML più localizzata con questo:

// 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

Grazie per aver condiviso questa soluzione! Funziona come un fascino! – Damneddani

+0

@Damneddani Si noti che il savehtml ($ body) finisce per restituire HTML WITH a body tag. Se stai inserendo l'html in un'altra pagina, questo produrrà un html non valido. Prova a fare qualcosa del genere: $ rootContent = ''; foreach ($ rootNode-> childNodes as $ node) { $ rootContent. = $ RootNode-> ownerDocument-> saveHTML ($ node); } // Nessun ritorno l'intero contenuto con contenuto della barra laterale aggiunto. return $ rootContent; –

Problemi correlati