Comment parser un fichier XML multi-noeuds avec XMLReader ?

Petit nouveau ! | 3 Messages

11 avr. 2016, 11:34

Bonjour à tous,

Je lis souvent le forum pour trouver des réponses, mais je n'y ai jamais participé. Car cette fois-ci, je n'ai pas trouvé de réponse à ma question.

Celle-ci concerne l'utilisation de XMLReader pour parser un fichier XML. Il y a peu d'aide et de ressources disponibles en ligne j'ai l'impression.

Je dois parser un fichier XML assez lourd (environ 200 mo). Impossible de le faire avec SimpleXML (cela consomme trop de mémoire). En cherchant, j'ai trouvé que dans mon cas il me fallait plutôt utiliser la librairie XMLReader.

C'est vrai que cela fonctionne bien et c'est plutôt rapide, même avec un gros fichier.

Sauf que le fichier XML que je dois parser est plutôt mal foutu.

En voici une représentation simplifiée :
<catalog>
	<fournisseur>
		<produit>
			<nom_produit>Produit 1</nom_produit>
		</produit>		
		<produit>
			<nom_produit>Produit 2</nom_produit>
		</produit>	
		<produit>
			<nom_produit>Produit 3</nom_produit>
		</produit>	
		<produit>
			<nom_produit>Produit 4</nom_produit>
		</produit>	
	</fournisseur>
	<fournisseur>
		<produit>
			<nom_produit>Produit 5</nom_produit>
		</produit>	
		<produit>
			<nom_produit>Produit 6</nom_produit>
		</produit>	
		<produit>
			<nom_produit>Produit 7</nom_produit>
		</produit>	
		<produit>
			<nom_produit>Produit 8</nom_produit>
		</produit>	
	</fournisseur>	
</catalog>
Mon but est de parser tous les noeuds "produit". Cependant, ces noeuds sont dans plusieurs noeuds "fournisseur".

Lorsque j'éxécute ce code, je ne récupère que les produits du premier noeud "fournisseur" (produits de 1 à 4 dans mon exemple) :
$z = new XMLReader;
$z->open($file);
$doc = new DOMDocument;

while ($z->read() && $z->name !== 'produit');
		
while ($z->name === 'produit')
{
	$node = simplexml_import_dom($doc->importNode($z2->expand(), true));
	echo $node->nom_produit; // juste pour tester ce que je récupère
				
	var_dump($node->element_1);
	$z2->next('produit');
}
Comment faire pour parser tous les produits, indépendamment des noeuds "fournisseur" (donc dans mon exemple plus haut, récupérer les produits 1 à 8) ?
(à savoir que je ne peux pas charger dans un tableau tout un noeud "fournisseur", ce serait beaucoup trop lourd en mémoire).

Je pense que la solution est toute bête, mais je bute dessus depuis plusieurs jours et j'ai besoin de vos lumières pour m'éclairer ! :-)

Merci par avance aux âmes charitables qui pourront me donner un coup de main !!

ynx
Mammouth du PHP | 586 Messages

11 avr. 2016, 14:41

Salut,

Une solution simple via DomDocument est d'utiliser la méthode getElementsByTagName :
$dom = new DomDocument();
$dom->loadXML('<catalog>
  <fournisseur>
    <produit>
      <nom_produit>Produit 1</nom_produit>
    </produit>		
    <produit>
      <nom_produit>Produit 2</nom_produit>
    </produit>	
    <produit>
      <nom_produit>Produit 3</nom_produit>
    </produit>	
    <produit>
      <nom_produit>Produit 4</nom_produit>
    </produit>	
  </fournisseur>
  <fournisseur>
    <produit>
      <nom_produit>Produit 5</nom_produit>
    </produit>	
    <produit>
      <nom_produit>Produit 6</nom_produit>
    </produit>	
    <produit>
      <nom_produit>Produit 7</nom_produit>
    </produit>	
    <produit>
      <nom_produit>Produit 8</nom_produit>
    </produit>	
  </fournisseur>	
</catalog>'); 

$produits = $dom->getElementsByTagName('produit');

foreach ($produits as $produit) {
    var_dump($produit);
}
Bonne journée

Petit nouveau ! | 3 Messages

11 avr. 2016, 15:00

Merci pour cette réponse, mais cela ne convient pas car il ne faut absolument pas que je charge l'intégralité du flux XML (trop lourd). D'où l'utilisation de la librairie XMLReader.

En fait, ce que je recherche à faire c'est quelquechose qui ressemblerait à cela :
$z = new XMLReader;
$z->open($file);
$doc = new DOMDocument;

while ($z->read() && $z->name !== 'fournisseur');

while ($z->name === 'fournisseur')
{
  while ($z->read() && $z->name !== 'produit');
    
  while ($z->name === 'produit')
  {
    $node = simplexml_import_dom($doc->importNode($z2->expand(), true));
    echo $node->nom_produit; // juste pour tester ce que je récupère
        
    var_dump($node->element_1);
    $z->next('produit');
  }
$z->next('fournisseur');
}
Soit une boucle sur "fournisseur" (mais sans charger le noeud) puis une boucle sur "produit" dans chaque noeud "fournisseur".

Mais, bien sûr ce code est pas du tout correct et ne fonctionne pas. Je ne sais pas comment le rendre fonctionnel... :(

Mammouth du PHP | 1967 Messages

11 avr. 2016, 15:38

Cela ne répondra peut être pas à ta question (je ne connais pas la librairie que tu utilise).
Mais si loader tous le fichier est impossible du à sa taille, et que ton fichier est aussi propre que tu le présente.
Tu peux le lire ligne par ligne avec fopen et feof et en extraire toi même les produits si la ligne correspond à ton gabarit.

Sinon tu peux toujours créer ton propre parser en lecture ligne par ligne mais c'est beaucoup plus de boulot.
Spols
pour les fan de rubik's cube ou pour les curieux ==> le portail francophone du rubik's cube

Petit nouveau ! | 3 Messages

11 avr. 2016, 15:51

J'ai finalement trouvé une solution, en lecture ligne par ligne (mais pas avec fopen comme le suggère Spols, mais toujours avec la librairie XMLReader). C'est un peu dégueu je trouve, mais ça fonctionne :
$z = new XMLReader;
$z->open($file);
$doc = new DOMDocument;

while ($z->read()) {
 if($z->name=='produit')
 {
   $node = simplexml_import_dom($doc->importNode($z->expand(), true));
   echo'<li>'.$node->nom_produit;
 }
}
Merci à tous les 2 d'avoir essayé de m'aider. (et si vous avez une solution plus "propre" je reste preneur :-D )

Bonne fin de journée !

Tony

Avatar du membre
Administrateur PHPfrance
Administrateur PHPfrance | 9783 Messages

11 avr. 2016, 18:32

Bonjour,

La solution la + simple et efficace est d'utiliser un Xpath pour extraire de ton XML uniquement les données que tu souhaites traiter.
Dans ton cas le Xpath serait le suivant pour récupérer tous les noeuds <nom_produit> :

Code : Tout sélectionner

//nom_produit
Quand tout le reste a échoué, lisez le mode d'emploi...