2010-02-10 13 views
8

Usando PHP Estoy intentando tomar una cadena HTML pasada de un editor WYSIWYG y reemplazar los elementos secundarios de un documento HTML precargado con el nuevo HTML.PHP DOMDocument replace DOMElement child with HTML string

Hasta ahora estoy cargando el documento que identifica el elemento que quiero cambiar por ID, pero el proceso para convertir un HTML a algo que se puede colocar dentro de un elemento DOMElement me está eludiendo.

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 
} 

Respuesta

13

Si la cadena HTML se puede analizar como XML, puede hacerlo (después de eliminar un elemento de todos los nodos secundarios):

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

Si $ html_string no se puede analizar como XML, se producirá un error. Si lo hace, tendrás que usar loadHTML(), que es menos estricto, pero agregará elementos alrededor del fragmento que tendrás que quitar.

A diferencia de PHP, Javascript tiene la propiedad innerHTML que le permite hacer esto muy fácilmente. Necesitaba algo así para un proyecto, por lo que amplié el DOMElement de PHP para incluir el acceso innerHTML similar al Javascript.

Con él se puede acceder a la propiedad innerHTML y cambiarlo tal como lo haría en Javascript:

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

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

+0

@Greg, ¿no debería decidir dónde van mis contribuciones? ¿Y desde cuándo hablas por el mundo? Decidí trasladar mis contribuciones a mi propio blog después de que algunas de mis contribuciones se borraron de StackOverflow y se me ocultaron. Me gustaría mantenerlo así, por favor revierte el cambio. – Keyvan

+0

Un enlace a una solución potencial siempre es bienvenido, pero agregue contexto alrededor del enlace para que los demás usuarios tengan una idea de qué es y por qué está allí. Siempre cite la parte más relevante de un enlace importante, en caso de que el sitio objetivo no esté disponible o esté permanentemente fuera de línea. Fuente: [* Cómo responder *] (http://stackoverflow.com/questions/how-to-answer) – Greg

+0

@Greg, conozco las pautas. Publiqué la respuesta aquí originalmente y la moví a mi propio sitio debido a la forma en que se manejaron mis otras contribuciones en este sitio, como mencioné anteriormente, se eliminaron y ocultaron. Por qué objeta tanto a un enlace me supera. Algo de reflexión para uno de los creadores de este sitio http://www.codinghorror.com/blog/2009/08/are-you-a-digital-sharecropper.html "¿Pueden sus contribuciones revocarse, eliminarse o permanecer de forma permanente? desconectado sin tu consentimiento? " En Stackoverflow: sí. En mi propio sitio: no. – Keyvan

1

Puede utilizar loadHTML() en un fragmento de código y luego añadir los nodos creados resultantes en el árbol DOM originales.

+0

¿Sugeriría crear un nuevo DOMDocument utilizando cargar HTML y luego tomar los elementos secundarios de la nueva etiqueta del documento y anexarlos al DOM original? ¿O hay otra función loadHTML() que me falta? – AWinter

+0

Realmente odio cómo las etiquetas html y body se agregan automáticamente cuando haces cosas como saveHTML() o loadHTML(). ¿Hay alguna solución fácil que no sea escribir un envoltorio que los desvíe? –

0

Sé que esto es un hilo de edad (pero la respuesta de esto porque también busca una solución para esto). He creado un método sencillo para reemplazar el contenido con una sola línea cuando lo utilizo. Para comprender mejor el método, también agrego algunas funciones con nombre de contexto.

Esto es ahora parte de mi biblioteca, así que esa es la razón de todos los nombres de funciones, todas las funciones comienzan con el prefijo 'su'.

Es muy fácil de usar y muy potente (y bastante menos código).

Aquí está el código:

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

Algunas funciones de contexto:

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

Cómo se usa:

En los siguientes ejemplos, supongo que ya hay contenido cargado en una variab Llamó $sMyHtml y la variable $sMyNewContent contiene algunos html nuevos. La variable $sMyHtml contiene un elemento llamado/con el 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, ¿vas a cambiar todas mis publicaciones? ¡Tontito! – Codebeat

+0

No ... Esperaba que ** lo hicieras **;) http://meta.stackexchange.com/questions/28416/what-is-the-policy-on-signatures-and-links- en-respuestas – brasofilo

+0

@brasofilo, lo siento, triste por ti, tengo otras cosas más importantes que hacer. – Codebeat

1

la respuesta aceptada actual sugiere el uso de appendXML(), pero reconoce que no se encargará de HTML complejo como lo que se devuelve desde un editor WYSISYG como se especifica en la pregunta original. Como se sugiere, loadHTML() puede abordar esto. pero nadie ha mostrado aún cómo.

Esto es lo que creo que es la mejor/correcta respuesta a la pregunta original sobre problemas de codificación, "Fragmento de documento está vacío" advertencias y errores de "error de documento incorrecto" que alguien probablemente golpeará si escriben esto desde cero. Sé que los encontré después de seguir las sugerencias en las respuestas anteriores.

Este es el código de un sitio que apoyo que inserta el contenido de la barra lateral de WordPress en el $ contenido de una publicación. Supone que $ doc es un DOMDocument válido similar a como se define $ doc en la pregunta original. También asume que $ elemento es la etiqueta después de la cual desea insertar el contenido de la barra lateral (o lo que sea).

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

Después de todo esto se hace, si no quiere tener la fuente de un documento HTML completa con etiquetas de cuerpo y lo que no, se puede generar el código HTML más localizada con esto:

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

¡Gracias por compartir esta solución! ¡Funciona de maravilla! – Damneddani

+0

@Damneddani Tenga en cuenta que savehtml ($ body) termina devolviendo HTML WITH a body tag. Si está insertando el html en otra página, eso producirá html no válido. Intente hacer algo como esto: $ rootContent = ''; foreach ($ rootNode-> childNodes as $ node) { $ rootContent. = $ RootNode-> ownerDocument-> saveHTML ($ node); } // No se devuelve el contenido completo con el contenido de la barra lateral añadida. return $ rootContent; –