Pull Parsing von XML mit XMLReader

deostift

Erfahrenes Mitglied
Hallo zusammen,

ich beschäftige mich gerade mal wieder mit dem Parsen von XML Files in PHP. Nachstehender Code funktioniert auch soweit ganz gut, nur habe ich trotzdem eine "VALIDE" Ausgabe wenn ich z.B. das erste <row> Tag in <row2> abändere - PHP Warning wird geworfen. Habe ich hier einen Denkfehler im XML Schema oder bekommt die Überprüfung des Parser das nicht gebacken?

Der Code:

XML Dummy FILE:
Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE datacontainer [
<!ELEMENT datacontainer (description,row*)>
<!ELEMENT description (column*)>
<!ELEMENT column (#PCDATA)>
	<!ATTLIST column
		col_ref CDATA #REQUIRED
		dict_ref CDATA #IMPLIED
		data_type CDATA #IMPLIED
		unit CDATA #IMPLIED
		data_size CDATA #IMPLIED
		data_precision CDATA #IMPLIED
	>
<!ELEMENT row (item*)>
<!ELEMENT item (#PCDATA)>
	<!ATTLIST item
		key CDATA #REQUIRED
		value CDATA #IMPLIED
	>
]>
<datacontainer>
  <description>
    <column col_ref="timestamp" dict_ref="timestamp" data_type="T" unit="" data_size="0" data_precision="1"/>
    <column col_ref="serial" dict_ref="serial" data_type="C" unit="" data_size="32" data_precision="1"/>
    <column col_ref="temperature" dict_ref="temperature" data_type="I" unit="°C" data_size="1" data_precision="0.1"/>
  </description>
  <row>
    <item key="timestamp" value="2008-01-29 07:50:00" />
    <item key="serial" value="4711" />
    <item key="temperature" value="2.0" />
  </row>
  <row>
    <item key="timestamp" value="2008-01-29 08:00:00" />
    <item key="serial" value="4711" />
    <item key="temperature" value="1.0" />
  </row>
</datacontainer>


PHP:
$xml = new XMLReader();
$xml->open("FILE.xml");
// DTD laden und während dem Parsen validieren
$xml->setParserProperty(3,true);
while ($xml->read()) {  
}

if ( $xml->isValid() ) {
  echo "VALIDE";
}
else {
  echo "NICHT VALIDE";
}
 
Zuletzt bearbeitet:
Richtig! Dein Beispiel ist nicht valide, da der Tag <row2> im Doctype nicht spezifiziert ist.

Allerdings ist folgende Variante VALIDE:

<row>
...
</row2>

Was allerdings auch stimmt. Es existieren nur Elemente <row>, von daher valide. Allerdings ist so ein XML File nicht wohlgeformt. Ich habe im Moment zwei Varianten gefunden, wie man dieses noch überprüfen kann - sicherlich eher Notkrücken (müsste eigentlich eine Funktion isWellFormed() geben als Gegenstück zu isValid()):

1. "track errors" setzen und die Variable $php_errormsg auslesen:

PHP:
error_reporting(E_ERROR | E_PARSE);
// PHP Parameter zum tracken von Fehlern setzen
ini_set("track_errors", 1);
// Fehler-Variable löschen, falls schon gesetzt
if (isset($php_errormsg)) unset($php_errormsg);

$objXMLReader = new XMLReader;
$objXMLReader->open("FILE.xml");
$objXMLReader->setParserProperty(XMLReader::VALIDATE, TRUE);

// Zuerst wird das komplette File durchlaufen, um eventuelle Fehler zu entdecken
while ( $objXMLReader->read() ) {
  if ( !$objXMLReader->isValid() ) {
    // Wenn nicht valide dann abbrechen (nur sinnvoll bei sehr grossen XML Files um den Rest nicht mehr durchlaufen zu muessen) 
    break;
  }
}

if ( isset($php_errormsg) ) {
  echo "XML FILE KORRUPT: ", $php_errormsg;
}
else if ( !$objXMLReader->isValid() ) {
  echo "XML FILE NICHT VALIDE";
}
else {
  echo "XML FILE OK";
}


2. Auslesen der LIBXML Fehler - sicherlich die elegantere Variante

PHP:
error_reporting(E_ERROR | E_PARSE);

// Standard LIBXML Fehler deaktivieren und eigenes Error Handling aktivieren
libxml_use_internal_errors(TRUE);

$objXMLReader = new XMLReader;
$objXMLReader->open("FILE.xml");
$objXMLReader->setParserProperty(XMLReader::VALIDATE, TRUE);

// Zuerst wird das komplette File durchlaufen, um eventuelle Fehler zu entdecken
while ( $objXMLReader->read() ) {
  if ( !$objXMLReader->isValid() ) {
    // Wenn nicht valide dann abbrechen (nur sinnvoll bei sehr grossen XML Files um den Rest nicht mehr durchlaufen zu muessen) 
    break;
  }
}

// Auslesen eventueller Fehler (Array)
$arrLIBXML_Errors = libxml_get_errors();
if( !empty( $arrLIBXML_Errors) ) print_r(libxml_get_errors());

Beim Verarbeiten mehreren XML Files, müsste man den Fehler Buffer leeren: libxml_clear_errors()

Grüßle, Deo

P.S: Kann sein, dass der obenstehende Code nicht fehlerfrei ist - sollte nur die Grundidee rüberbringen.
 
Zuletzt bearbeitet:
Ergänzung:

Wenn man die zweite Variante mit dem LIBXML Fehler-Buffer nimmt, kann man der ersten Durchlauf vor der eigentlichen Verarbeitung beschleunigen: next() statt read() verwenden, allerdings funktioniert dann die Überprüpfung mit isValid() nicht mehr.

Sicherlich wäre das dann besser:

PHP:
<?php

error_reporting(E_ERROR | E_PARSE);

// Standard LIBXML Fehler deaktivieren und eigenes Error Handling aktivieren
libxml_use_internal_errors(TRUE);

$objXMLReader = new XMLReader;
// wohl erst ab PHP Version >= 5.1.2
$objXMLReader->open("wr.xml", NULL, LIBXML_DTDVALID);

// Zuerst wird das komplette File durchlaufen, um eventuelle Fehler zu entdecken
while ( $objXMLReader->next() ) {
  // mache nix :)
}

// Auslesen eventueller Fehler (Array)
$arrLIBXML_Errors = libxml_get_errors();
if( !empty( $arrLIBXML_Errors) ) print_r(libxml_get_errors());
 
Zurück