2009-04-14 22 views
21

Estoy usando DOMDocument para generar un nuevo archivo XML y me gustaría que el resultado del archivo se sancione muy bien para que sea fácil de seguir para un lector humano.Sangría con DOMDocument en PHP

Por ejemplo, cuando DOMDocument salidas de estos datos:

<?xml version="1.0"?> 
<this attr="that"><foo>lkjalksjdlakjdlkasd</foo><foo>lkjlkasjlkajklajslk</foo></this> 

Quiero que el archivo XML sea:

<?xml version="1.0"?> 
<this attr="that"> 
    <foo>lkjalksjdlakjdlkasd</foo> 
    <foo>lkjlkasjlkajklajslk</foo> 
</this> 

He estado buscando en busca de respuestas, y todo lo que yo ve encontrado parece decir para tratar de controlar el espacio en blanco de esta manera:

$foo = new DOMDocument(); 
$foo->preserveWhiteSpace = false; 
$foo->formatOutput = true; 

Pero esto no parece hacer nada. Quizás esto solo funciona al leer XML? Tenga en cuenta que estoy tratando de escribir nuevos documentos.

¿Hay algo incorporado en DOMDocument para hacer esto? ¿O una función que puede lograr esto fácilmente?

+1

no estoy seguro de cuál es la pregunta. El código que muestre dará el resultado que está solicitando. Prueba: http://codepad.org/4UGyRspx y http://codepad.org/bLTOFQrp - ¿Estás preguntando sobre el nivel de sangría, p. la cantidad de espacios usados? – Gordon

+0

Hay una buena función directa (basado en expresiones regulares) aquí: [formatos XML con PHP] (http://recurser.com/articles/2007/04/05/format-xml-with-php/) – Tomalak

+0

relacionados como siempre que la sangría se refiere a: [en la conversión de la sangría con preg_replace (sin devolución de llamada)] (http://stackoverflow.com/questions/8616594/converting-indentation-with-preg-replace-no-callback) – hakre

Respuesta

3

He intentado ejecutar el código a continuación, estableciendo formatOutput y de diferentes maneras, y el único miembro que tiene algún efecto en la salida es formatOutput. ¿Puedes ejecutar el script a continuación y ver si funciona?

<?php 
    echo "<pre>"; 
    $foo = new DOMDocument(); 
    //$foo->preserveWhiteSpace = false; 
    $foo->formatOutput = true; 
    $root = $foo->createElement("root"); 
    $root->setAttribute("attr", "that"); 
    $bar = $foo->createElement("bar", "some text in bar"); 
    $baz = $foo->createElement("baz", "some text in baz"); 
    $foo->appendChild($root); 
    $root->appendChild($bar); 
    $root->appendChild($baz); 
    echo htmlspecialchars($foo->saveXML()); 
    echo "</pre>"; 
?> 
+0

Su código funciona bien, pero no funciona para mí con la forma en que lo configuré. Tengo una clase xml y dentro de esa clase creo una variable $ this-> xml que contiene una instancia de DOMDocument, y no parece funcionar con esa configuración. También preferiría tener pestañas reales en lugar de solo espacios. –

+0

Esto parece un caso especial entonces. Creé una clase simple con "xml" como miembro, y todavía funcionó. Hay demasiados factores y sin su código exacto (o una versión simplificada que todavía no funciona) será imposible de reproducir. –

+0

Gracias por su ayuda John. Escribí una función de sangría básica que, con suerte, solucionará mi problema (a punto de publicarlo como respuesta si quieres echarle un vistazo). –

7

Después de un poco de ayuda de John y jugar con esto por mi cuenta, parece que incluso el soporte inherente de DOMDocument para formatear no satisfacía mis necesidades. Entonces, decidí escribir mi propia función de sangría.

Esta es una función muy cruda que acabo de lanzar rápidamente, por lo que si alguien tiene algún consejo de optimización o algo que decir al respecto en general, ¡me encantaría escucharlo!

function indent($text) 
{ 
    // Create new lines where necessary 
    $find = array('>', '</', "\n\n"); 
    $replace = array(">\n", "\n</", "\n"); 
    $text = str_replace($find, $replace, $text); 
    $text = trim($text); // for the \n that was added after the final tag 

    $text_array = explode("\n", $text); 
    $open_tags = 0; 
    foreach ($text_array AS $key => $line) 
    { 
     if (($key == 0) || ($key == 1)) // The first line shouldn't affect the indentation 
      $tabs = ''; 
     else 
     { 
      for ($i = 1; $i <= $open_tags; $i++) 
       $tabs .= "\t"; 
     } 

     if ($key != 0) 
     { 
      if ((strpos($line, '</') === false) && (strpos($line, '>') !== false)) 
       $open_tags++; 
      else if ($open_tags > 0) 
       $open_tags--; 
     } 

     $new_array[] = $tabs . $line; 

     unset($tabs); 
    } 
    $indented_text = implode("\n", $new_array); 

    return $indented_text; 
} 
+2

Una observación rápida: There str_repeat() para crear las pestañas. El resto de la función me parece bastante bien. Puede configurar una pequeña comparación de rendimiento con la que he encontrado. Como una idea alternativa, puede usar strtok() para tokenizar la entrada de forma iterativa (en lugar de reemplazar/explotar). – Tomalak

+0

¡Gracias! De hecho, me gusta la función que encontraste mejor que la mía, ya que descubrí que tiene un formato mucho más profundo. Y no sabía acerca de cualquiera de str_repeat() o strtok(), así que gracias por eso también! –

-2
header("Content-Type: text/xml"); 

$str = ""; 
$str .= "<customer>"; 
$str .= "<offer>"; 
$str .= "<opened></opened>"; 
$str .= "<redeemed></redeemed>"; 
$str .= "</offer>"; 
echo $str .= "</customer>"; 

Si está utilizando una extensión distinta a .xml continuación, establezca primero la cabecera cabecera Content-Type al valor correcto.

1

¿A qué método llama al imprimir el xml?

Yo uso este:

$doc = new DOMDocument('1.0', 'utf-8'); 
$root = $doc->createElement('root'); 
$doc->appendChild($root); 

(...)

$doc->formatOutput = true; 
$doc->saveXML($root); 

Funciona perfectamente, pero imprime sólo el elemento, por lo que debe imprimir la parte <?xml ... ?> manualmente ..

24

DomDocument hará el truco, yo personalmente pasé par de horas Googling y tratando de resolver esto y me di cuenta que si utiliza

$xmlDoc = new DOMDocument(); 
$xmlDoc->loadXML ($xml); 
$xmlDoc->preserveWhiteSpace = false; 
$xmlDoc->formatOutput = true; 
$xmlDoc->save($xml_file); 

En ese orden, es simplemente no funciona, pero, si se utiliza el mismo código, pero en este orden:

$xmlDoc = new DOMDocument(); 
$xmlDoc->preserveWhiteSpace = false; 
$xmlDoc->formatOutput = true; 
$xmlDoc->loadXML ($xml); 
$xmlDoc->save($archivoxml); 

funciona como un encanto, espero que esta ayuda

+2

¡Amigo! ¡Rock! Gracias por detectar esto! –

+3

Maldición ... Esto solo parece funcionar con XML, HTML todavía se ve feo. =/ –

+0

Sí, parece que no funciona para HTML – 3zzy

1

mayoría de las respuestas en este tema se refería a flujo de texto XML. Aquí hay otro enfoque que usa las funcionalidades dom para realizar el trabajo de indentación. El método dom loadXML() importa caracteres de sangrado presentes en la fuente xml como nodos de texto. La idea es eliminar dichos nodos de texto de la dom y luego volver a crear los formateados correctamente (ver los comentarios en el siguiente código para más detalles). función

El xmlIndent() se implementa como un método de la clase indentDomDocument, que se hereda de DomDocument. A continuación se muestra un ejemplo completo de cómo usarlo:

$dom = new indentDomDocument("1.0"); 
$xml = file_get_contents("books.xml"); 

$dom->loadXML($xml); 
$dom->xmlIndent(); 
echo $dom->saveXML(); 

class indentDomDocument extends domDocument { 
    public function xmlIndent() { 
     // Retrieve all text nodes using XPath 
     $x = new DOMXPath($this); 
     $nodeList = $x->query("//text()"); 
     foreach($nodeList as $node) { 
      // 1. "Trim" each text node by removing its leading and trailing spaces and newlines. 
      $node->nodeValue = preg_replace("/^[\s\r\n]+/", "", $node->nodeValue); 
      $node->nodeValue = preg_replace("/[\s\r\n]+$/", "", $node->nodeValue); 
      // 2. Resulting text node may have become "empty" (zero length nodeValue) after trim. If so, remove it from the dom. 
      if(strlen($node->nodeValue) == 0) $node->parentNode->removeChild($node); 
     } 
     // 3. Starting from root (documentElement), recursively indent each node. 
     $this->xmlIndentRecursive($this->documentElement, 0); 
    } // end function xmlIndent 

    private function xmlIndentRecursive($currentNode, $depth) { 
     $indentCurrent = true; 
     if(($currentNode->nodeType == XML_TEXT_NODE) && ($currentNode->parentNode->childNodes->length == 1)) { 
      // A text node being the unique child of its parent will not be indented. 
      // In this special case, we must tell the parent node not to indent its closing tag. 
      $indentCurrent = false; 
     } 
     if($indentCurrent && $depth > 0) { 
      // Indenting a node consists of inserting before it a new text node 
      // containing a newline followed by a number of tabs corresponding 
      // to the node depth. 
      $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth)); 
      $currentNode->parentNode->insertBefore($textNode, $currentNode); 
     } 
     if($currentNode->childNodes) { 
      $indentClosingTag = false; 
      foreach($currentNode->childNodes as $childNode) $indentClosingTag = $this->xmlIndentRecursive($childNode, $depth+1); 
      if($indentClosingTag) { 
       // If children have been indented, then the closing tag 
       // of the current node must also be indented. 
       $textNode = $this->createTextNode("\n" . str_repeat("\t", $depth)); 
       $currentNode->appendChild($textNode); 
      } 
     } 
     return $indentCurrent; 
    } // end function xmlIndentRecursive 

} // end class indentDomDocument 
-1

Yo píos,

Acabamos de descubrir que, al parecer, un elemento XML raíz no puede contener los niños de texto. Esto no es intuitivo a. F. Pero aparentemente, esta es la razón por la cual, por ejemplo,

no sangrará.

https://bugs.php.net/bug.php?id=54972

Y esto es todo, h. t. h. et c.