2010-08-04 18 views
7

Pasé días enteros con las funciones de DOM de PHP, pero todavía no entiendo cómo funciona. :( Tengo un archivo XML simple que se ve bien, pero no puedo usarlo como pienso cuando me he creado su estructuraSimplifique el análisis PHP DOM XML - ¿cómo?

Muestra fragmento XML:.

-pages //root element 
    -page id="1" //we can have any number of pages 
     -product id="364826" //we can have any number of products 
      -SOME_KIND_OF_VALUE 
      -ANOTHER_VALUE 
      ... 

Mi idea original era acelerar flujo de trabajo de mi cliente por lo lanzo a cabo CSVs viejos y empecé a usar XMLs

Problema 1:.. Cuando i agrupación de productos en la página que estoy usando setIdAttribute para evitar el almacenamiento de la misma página en el árbol más de una vez que esta obra bien hasta que suceda la lectura porque estos identificadores están vinculados a algún tipo de DTD (basado en getElementById).

Pregunta 1: ¿Cómo puedo escribir un DTD simple que proporciona estas informaciones necesarias para que pueda utilizar getElementById en la fase de lectura también?

Problema 2: Como tengo páginas, me gustaría cargar la menor información posible. Por eso creé el atributo id en las páginas. Ahora no puedo acceder directamente a mi página id = "2" porque el problema 1 anterior (getElementById no tiene sentido actualmente). De alguna manera me puedo logrado recuperar las informaciones necesarias sobre cada producto en una página determinada, pero mi código es el miedo:

$doc  = DOMDocument::load('data.xml'); 
$xpath = new DOMXPath($doc); 
$query = '/pages/page[' . $page . ']'; //$page is fine: was set earlier 
$products = $xpath->query($query); 
$_prods = $doc->getElementsByTagName('product'); 
foreach($_prods as $product){ 
    foreach($product->childNodes as $node){ 
     echo $node->nodeName . ": " . $node->nodeValue . "<br />"; 
    } 
} 

Queston 2: Creo que el código anterior es el ejemplo sobre cómo no para analizar un XML. Pero debido a mi conocimiento limitado de las funciones DOM de PHP, no puedo escribir uno más limpio por mi cuenta. Intenté alguna solución trivial pero ninguna de ellas funcionó para mí.

Por favor, ayúdame si puedes.

Gracias, Fabrik

+0

Supongo que acaba de dar un resumen de la estructura de su documento XML? Porque no es XML lo que has publicado (solo quiero estar seguro;)). –

+0

Por supuesto, es solo un esquema. XML valida bien y se ve bien a diferencia de mi código: o – fabrik

+0

¿las funciones de simplexml serían demasiado simples para sus necesidades? – stillstanding

Respuesta

12

resolución de problemas 1:

El W3C defines: el significado del atributo xml:id como un atributo ID en los documentos XML y define el procesamiento de este atributo para identificar ID en el ausencia de validación, sin buscar recursos externos y sin depender de un subconjunto interno.

En otras palabras, cuando se utiliza

$element->setAttribute('xml:id', 'test'); 

que no es necesario llamar a setIdAttribute, ni especificar una DTD o esquema. DOM reconocerá el atributo xml:id cuando se usa con getElementById sin tener que validar el documento ni nada. Este es el enfoque de menor esfuerzo. Sin embargo, tenga en cuenta que, dependiendo de su sistema operativo y la versión de libxml, no obtendrá getElementById para funcionar.

Resolver Problem2:

Incluso con los ID de no ser fetchable con getElementById, puede todavía muy buscarlos con XPath:

$xpath->query('/pages/page[@id=1]'); 

sin duda trabajar. Y también se puede recuperar a los niños de productos para una página específica directamente:

$xpath->query('//pages/page[@id=1]/products'); 

Aparte de esto, hay muy poco que se pueda hacer para que DOM mirada código de menos detallado, porque realmente es una interfaz detallado. Tiene que ser así, porque DOM is a language agnostic interface, again defined by the W3C.


Edición después de comentarios a continuación

está trabajando como he explicado anteriormente. Aquí hay un caso de prueba completo para usted. La primera parte es para escribiendo nuevos archivos XML con DOM. Ahí es donde debe establecer el atributo xml:id. Utiliza esto en lugar del atributo de id regular, sin nombre de espacio.

// Setup 
$dom = new DOMDocument; 
$dom->formatOutput = TRUE; 
$dom->preserveWhiteSpace = FALSE; 
$dom->loadXML('<pages/>'); 

// How to set a valid id attribute when not using a DTD or Schema 
$page1 = $dom->createElement('page'); 
$page1->setAttribute('xml:id', 'p1'); 
$page1->appendChild($dom->createElement('product', 'foo1')); 
$page1->appendChild($dom->createElement('product', 'foo2')); 

// How to set an ID attribute that requires a DTD or Schema when reloaded 
$page2 = $dom->createElement('page'); 
$page2->setAttribute('id', 'p2'); 
$page2->setIdAttribute('id', TRUE); 
$page2->appendChild($dom->createElement('product', 'bar1')); 
$page2->appendChild($dom->createElement('product', 'bar2')); 

// Appending pages and saving XML 
$dom->documentElement->appendChild($page1); 
$dom->documentElement->appendChild($page2); 
$xml = $dom->saveXML(); 
unset($dom, $page1, $page2); 
echo $xml; 

Esto creará un archivo XML como esto:

<?xml version="1.0"?> 
<pages> 
    <page xml:id="p1"> 
    <product>foo1</product> 
    <product>foo2</product> 
    </page> 
    <page id="p2"> 
    <product>bar1</product> 
    <product>bar2</product> 
    </page> 
</pages> 

Cuando leer en el XML de nuevo, la nueva instancia DOM ya no se sabe que haya declarado la no namespaced id atributo como Atributo ID con setIdAttribute. Todavía estará en XML, pero el atributo id solo será un atributo regular. You have to be aware that ID attributes are special in XML.

// Load the XML we created above 
$dom = new DOMDocument; 
$dom->loadXML($xml); 

Ahora para algunas pruebas:

echo "\n\n GETELEMENTBYID RETURNS ELEMENT WITH XML:ID \n\n"; 
foreach($dom->getElementById('p1')->childNodes as $product) { 
    echo $product->nodeValue; // Will output foo1 and foo2 with whitespace 
} 

Los trabajos anteriores, debido a que un analizador compatible DOM tiene que reconocer xml:id es un atributo ID, independientemente de cualquier DTD o esquema. Esto se explica en las especificaciones vinculadas anteriormente. El motivo por el que se generan espacios en blanco es porque, debido a la salida formateada, hay nodos DOMText entre la etiqueta de apertura, las dos etiquetas de producto y las etiquetas de cierre, por lo que estamos iterando en cinco nodos. El concepto de nodo es crucial para entender cuando se trabaja con XML.

echo "\n\n GETELEMENTBYID CANNOT FETCH NORMAL ID \n\n"; 
foreach($dom->getElementById('p2')->childNodes as $product) { 
    echo $product->nodeValue; // Will output a NOTICE and a WARNING 
} 

Lo anterior no funcionará, porque id no es un atributo ID. Para que el analizador DOM lo reconozca como tal, necesita una DTD o un Esquema y el XML debe validarse en su contra.

echo "\n\n XPATH CAN FETCH NORMAL ID \n\n"; 
$xPath = new DOMXPath($dom); 
$page2 = $xPath->query('/pages/page[@id="p2"]')->item(0); 
foreach($page2->childNodes as $product) { 
    echo $product->nodeValue; // Will output bar1 and bar2 
} 

XPath por el contrario es literal sobre los atributos, lo que significa que se puede consultar el DOM para el elemento de página con el atributo id si getElementById no está disponible. Tenga en cuenta que para consultar la página con ID p1, debe incluir el espacio de nombre, p. @xml:id="p1".

echo "\n\n XPATH CAN FETCH PRODUCTS FOR PAGE WITH ID \n\n"; 
$xPath = new DOMXPath($dom); 
foreach($xPath->query('/pages/page[@id="p2"]/product') as $product) { 
    echo $product->nodeValue; // Will output bar1 and bar2 w\out whitespace 
} 

Y como he dicho, también puede usar XPath para consultar cualquier otra cosa en el documento.Este no generará espacio en blanco, ya que solo devolverá los elementos product debajo de la página con id p2.

También puede recorrer todo el DOM desde un nodo. Es una estructura de árbol. Como DOMNode es la clase más importante en DOM, desea familiarizarse con ella.

echo "\n\n TRAVERSING UP AND DOWN \n\n"; 
$product = $dom->getElementsByTagName('product')->item(2); 
echo $product->tagName; // 'product' 
echo $dom->saveXML($product); // '<product>bar1</product>' 

// Going from bar1 to foo1 
$product = $product->parentNode // Page Node 
        ->parentNode // Pages Node 
        ->childNodes->item(1) // Page p1 
        ->childNodes->item(1); // 1st Product 

echo $product->nodeValue; // 'foo1' 

// from foo1 to foo2 it is two(!) nodes because the XML is formatted 
echo $product->nextSibling->nodeName; // '#text' with whitespace and linebreak 
echo $product->nextSibling->nextSibling->nodeName; // 'product' 
echo $product->nextSibling->nextSibling->nodeValue; // 'foo2' 

En una nota alla, sí, tengo un error ortográfico en el código original de arriba. Es product no products. Pero me parece difícil justificar que el código no funciona cuando todo lo que tiene que cambiar es un s. Eso solo se siente como querer ser cuchareado.

+0

Configurar 'id' de una página antes de escribir el archivo XML funciona bien. Cuando leo XML, no puedo/no quiero establecer atributos porque me gustaría leer el origen XML basado en estos atributos. Entonces el problema 1 no está resuelto todavía. Problema 2 definitivamente no resuelto, su primera consulta XPath falla. La segunda falla también porque no tengo los 'productos' del nodo, sino que tengo muchos nodos' producto' dentro de una página. (Eso fue definido en mi pregunta.) – fabrik

+0

@fabrik ambos problemas están resueltos. Ver mi actualización para la prueba. – Gordon

+1

¡Es increíble! Gracias por tu profunda explicación. Es rápido y hace exactamente lo que quiero. Excepto una cosa pero es mi culpa: cometí un error en el fragmento XML de muestra porque también necesito el nombre y el valor del nodo, así que necesito dos foreach una vez más: o Por supuesto que aceptaré tu respuesta porque es el truco . ¡Gracias de nuevo! – fabrik