Analyse du document XML. Analyse des données XML. Création d'une instance d'analyseur

L'analyse XML consiste essentiellement à parcourir un document XML et à renvoyer les données correspondantes. Même si un nombre croissant de services Web renvoient des données au format JSON, la plupart utilisent encore du XML. Il est donc important de maîtriser l'analyse XML si vous souhaitez utiliser la gamme complète des API disponibles.

Utiliser l'extension SimpleXML en PHP, qui a été ajouté dans PHP 5.0, travailler avec XML est très simple et facile. Dans cet article, je vais vous montrer comment procéder.

Bases d'utilisation

Commençons par l'exemple suivant langues.xml:


>

> 1972>
> Dennis Ritchie >
>

> 1995>
> Rasmus Lerdorf >
>

> 1995>
> James Gosling >
>
>

Ce document XML contient une liste de langages de programmation avec quelques informations sur chaque langage : l'année de son introduction et le nom de son créateur.

La première étape consiste à charger le XML à l'aide des fonctions soit simplexml_load_file(), ou simplexml_load_string(). Comme le nom des fonctions l'indique, la première chargera du XML à partir d'un fichier et la seconde chargera du XML à partir d'une chaîne.

Les deux fonctions lisent l'intégralité de l'arborescence DOM en mémoire et renvoient un objet SimpleXMLElement. Dans l'exemple ci-dessus, l'objet est stocké dans la variable $langues. Vous pouvez utiliser les fonctions var_dump() ou print_r() pour obtenir des détails sur l'objet retourné si vous le souhaitez.

Objet SimpleXMLElement
[lang] => Tableau
[ 0 ] => Objet SimpleXMLElement
[@attributes] => Tableau
[nom] => C
[apparu] => 1972
[créateur] => Dennis Ritchie
[ 1 ] => Objet SimpleXMLElement
[@attributes] => Tableau
[nom] => PHP
[apparu] => 1995
[créateur] => Rasmus Lerdorf
[ 2 ] => Objet SimpleXMLElement
[@attributes] => Tableau
[nom] => Java
[apparu] => 1995
[créateur] => James Gosling
)
)

Ce XML contient un élément racine langues, à l'intérieur duquel se trouvent trois éléments langue. Chaque élément du tableau correspond à un élément langue dans le document XML.

Vous pouvez accéder aux propriétés d'un objet à l'aide de l'opérateur -> . Par exemple, $langues->lang vous renverra un objet SimpleXMLElement qui correspond au premier élément langue. Cet objet contient deux propriétés : apparu et créateur.

$langues ​​-> lang [ 0 ] -> apparu ;
$langues ​​-> lang [ 0 ] -> créateur ;

Afficher une liste de langues et montrer leurs propriétés peut se faire très facilement à l'aide d'une boucle standard telle que pour chaque.

foreach ($langues -> lang comme $lang ) (
printf(
"" ,
$lang [ "nom" ] ,
$lang -> est apparu,
$lang -> créateur
) ;
}

Remarquez comment j'ai accédé au nom de l'attribut lang de l'élément pour obtenir le nom de la langue. De cette façon, vous pouvez accéder à n'importe quel attribut d'un élément représenté comme un objet SimpleXMLElement.

Travailler avec des espaces de noms

En travaillant avec le XML de divers services Web, vous rencontrerez plus d'une fois des espaces de noms d'éléments. Changeons notre langues.xml pour montrer un exemple d'utilisation d'un espace de noms :



xmlns:dc =>

> 1972>
> Dennis Ritchie >
>

> 1995>
> Rasmus Lerdorf >
>

> 1995>
> James Gosling >
>
>

Maintenant l'élément créateur s'inscrit dans l'espace de noms cc qui pointe vers http://purl.org/dc/elements/1.1/. Si vous essayez d'imprimer les créateurs de langage en utilisant notre code précédent, cela ne fonctionnera pas. Afin de lire les espaces de noms des éléments, vous devez utiliser l'une des approches suivantes.

La première approche consiste à utiliser les noms d'URI directement dans le code lors de l'accès à l'espace de noms de l'élément. L'exemple suivant montre comment procéder :

$dc = $langues -> lang [ 1 ] -> enfants( "http://purl.org/dc/elements/1.1/") ;
echo $dc -> créateur ;

Méthode enfants() prend un espace de noms et renvoie les éléments enfants qui commencent par un préfixe. Il prend deux arguments, le premier étant l'espace de noms XML et le second un argument facultatif dont la valeur par défaut est FAUX. Si le deuxième argument est défini sur TRUE, l'espace de noms sera traité comme un préfixe. Si FALSE, alors l'espace de noms sera traité comme un espace de noms d'URL.

La deuxième approche consiste à lire les noms d'URI du document et à les utiliser lors de l'accès à l'espace de noms des éléments. C'est en fait une meilleure façon d'accéder aux éléments car vous n'avez pas besoin d'être codé en dur sur l'URI.

$namespaces = $langues ​​-> getNamespaces (true) ;
$dc = $langues ​​-> lang [ 1 ] -> enfants ( ($namespaces [ "dc" ] ) ;

echo $dc -> créateur ;

Méthode Obtenir des espaces de noms() renvoie un tableau de noms de préfixes et leurs URI associés. Il accepte un paramètre supplémentaire qui est par défaut FAUX. Si vous le définissez comme vrai, cette méthode renverra les noms utilisés dans les nœuds parent et enfant. Sinon, il recherche les espaces de noms utilisés uniquement dans le nœud parent.

Vous pouvez maintenant parcourir la liste des langues comme ceci :

$langues ​​= simplexml_load_file ("langues.xml" ) ;
$ns = $langues ​​-> getNamespaces (true ) ;

foreach ($langues -> lang comme $lang ) (
$dc = $lang -> enfants ($ns [ "dc" ] ) ;
printf(
"

%s est apparu dans %d et a été créé par %s .

" ,
$lang [ "nom" ] ,
$lang -> est apparu,
$dc -> créateur
) ;
}

Exemple pratique - Analyse d'une chaîne vidéo de YouTube

Regardons un exemple qui reçoit un flux RSS de Chaîne Youtube et affiche des liens vers toutes les vidéos de celui-ci. Pour ce faire, veuillez contacter l'adresse suivante :

http://gdata.youtube.com/feeds/api/users/xxx/uploads

L'URL renvoie une liste des dernières vidéos d'une chaîne donnée au format XML. Nous analyserons le XML et obtiendrons les informations suivantes pour chaque vidéo :

  • Lien vers la vidéo
  • Miniature
  • Nom

Nous allons commencer par rechercher et charger le XML :

$canal = "Nom_canal" ;
$url = "http://gdata.youtube.com/feeds/api/users/". $canal. "/téléchargements" ;
$xml = file_get_contents($url);

$feed = simplexml_load_string ($xml) ;
$ns = $feed -> getNameSpaces ( true ) ;

Si vous regardez le flux XML, vous pouvez voir qu'il contient plusieurs éléments entité, dont chacun stocke des informations détailléesà propos d'une vidéo spécifique de la chaîne. Mais nous n'utilisons que les vignettes des images, l'URL et le titre de la vidéo. Ces trois éléments sont les descendants de l'élément groupe, qui, à son tour, est un enfant de entrée:

>

>



Titre… >

>

>

Nous allons juste passer en revue tous les éléments entrée, et pour chacun d’eux nous extrairons les informations nécessaires. noter que joueur vignette Et titre sont dans l’espace de noms du média. Il faut donc procéder comme dans l’exemple précédent. Nous obtenons les noms du document et utilisons l'espace de noms lors de l'accès aux éléments.

foreach ($feed -> entrée en tant que $entry ) (
$group = $entry -> enfants ($ns [ "media" ] ) ;
$groupe = $groupe -> groupe ;
$thumbnail_attrs = $group -> miniature [ 1 ] -> attributs () ;
$image = $thumbnail_attrs [ "url" ] ;
$joueur = $groupe -> joueur -> attributs () ;
$lien = $joueur [ "url" ] ;
$titre = $groupe -> titre ;
printf( "

" ,
$joueur, $image, $titre);
}

Conclusion

Maintenant que vous savez utiliser SimpleXML Pour analyser des données XML, vous pouvez améliorer vos compétences en analysant différents flux XML avec différentes API. Mais il est important de considérer que SimpleXML lit l'intégralité du DOM en mémoire, donc si vous analysez un grand ensemble de données, vous risquez de manquer de mémoire. Pour en savoir plus sur SimpleXML, lisez la documentation.


Si vous avez des questions, nous vous recommandons d'utiliser notre

XML Extensible Markup Language est un ensemble de règles permettant de coder des documents sous une forme lisible par machine. XML est un format populaire pour échanger des données sur Internet. Les sites qui mettent fréquemment à jour leur contenu, tels que les sites d'actualités ou les blogs, fournissent souvent un flux XML afin que les programmes externes soient informés des modifications de contenu. L'envoi et l'analyse de données XML sont une tâche courante pour les applications connectées au réseau. Cette leçon explique comment analyser des documents XML et utiliser leurs données.

Choisir un analyseur

Analyse des canaux

La première étape de l'analyse d'un flux consiste à décider quels champs de données vous intéressent. L'analyseur extrait les champs donnés et ignore tout le reste.

Voici un extrait du canal qui sera exploré dans l'exemple d'application. Chaque publication sur StackOverflow.com apparaît dans un flux sous la forme d'une balise d'entrée, qui contient plusieurs sous-balises :

android - Questions les plus récentes taguées sur Android ... ... http://stackoverflow.com/q/9439999 0 Où est mon fichier de données ? falaise2310 http://stackoverflow.com/users/1128925 2012-02-25T00:30:54Z 2012-02-25T00:30:54Z

J'ai une application qui nécessite un fichier de données...

... ...

L'exemple d'application récupère les données de la balise d'entrée et de ses sous-balises title , link et summary .

Création d'une instance d'analyseur

L'étape suivante consiste à instancier l'analyseur et à démarrer le processus d'analyse. Cet extrait initialise l'analyseur pour ne pas gérer les espaces de noms et pour utiliser le InputStream fourni comme entrée. Le processus d'analyse commence par un appel à nextTag() et appelle la méthode readFeed(), qui récupère et traite les données qui intéressent l'application :

Classe publique StackOverflowXmlParser ( // Nous n'utilisons pas d'espaces de noms private static final String ns = null; public List parse(InputStream in) throws XmlPullParserException, IOException ( try ( XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES , false); parser.setInput(in, null); parser.nextTag(); return readFeed(parser); ) enfin ( in.close(); ) ) ... )

Soustraire un canal

La méthode readFeed() effectue le travail réel de traitement du flux. Les éléments marqués de la balise « entry » constituent le point de départ du traitement récursif du canal. Si la balise suivante n'est pas une balise d'entrée, elle est ignorée. Une fois que l'intégralité du « flux » a été traitée de manière récursive, readFeed() renvoie une liste contenant les entrées (y compris les éléments de données imbriqués) qui sont récupérées du flux. Cette liste est ensuite renvoyée par l'analyseur.

La liste privée readFeed (analyseur XmlPullParser) renvoie XmlPullParserException, IOException ( entrées de liste = new ArrayList (); parser.require (XmlPullParser.START_TAG, ns, "feed"); while (parser.next() != XmlPullParser.END_TAG) (if (parser.getEventType() != XmlPullParser.START_TAG) ( continue; ) String name = parser.getName(); // Commence par rechercher la balise d'entrée if (name.equals("entry")) ( entrées.add( readEntry(parser)); ) else ( skip(parser); ) ) renvoie les entrées; )

Analyse XML

Les étapes pour analyser le flux XML sont les suivantes :

Cet extrait montre comment l'analyseur analyse l'entrée, le titre, le lien et le résumé.

Entrée de classe statique publique ( titre de chaîne final public ; lien de chaîne final public ; résumé de chaîne final public ; entrée privée (titre de chaîne, résumé de chaîne, lien de chaîne) ( this.title = titre ; this.summary = résumé ; this.link = lien ; ) ) // Analyse le contenu d'une entrée. S'il rencontre une balise de titre, de résumé ou de lien, // les transmet à leurs méthodes de « lecture » respectives pour le traitement. Sinon, ignorez la balise. L'entrée privée readEntry (analyseur XmlPullParser) renvoie XmlPullParserException, IOException ( parser.require (XmlPullParser.START_TAG, ns, "entry"); String title = null; String summary = null; String link = null; while (parser.next() ! = XmlPullParser.END_TAG) ( if (parser.getEventType() != XmlPullParser.START_TAG) ( continue; ) String name = parser.getName(); if (name.equals("title")) ( title = readTitle(analyseur) ; ) else if (name.equals("summary")) ( summary = readSummary(parser); ) else if (name.equals("link")) ( link = readLink(parser); ) else ( skip(parser) ; ) ) return new Entry(title, summary, link); ) // Traite les balises de titre dans le flux. private String readTitle(XmlPullParser parser) renvoie IOException, XmlPullParserException ( parser.require(XmlPullParser.START_TAG, ns, "title"); String title = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "title"); return title; ) // Traite les balises de lien dans le flux. chaîne privée readLink (analyseur XmlPullParser) lève IOException, XmlPullParserException ( String link = ""; parser.require (XmlPullParser.START_TAG, ns, "link"); String tag = parser.getName (); String relType = parser.getAttributeValue (null , "rel"); if (tag.equals("link")) ( if (relType.equals("alternate"))( link = parser.getAttributeValue(null, "href"); parser.nextTag(); ) ) parser.require(XmlPullParser.END_TAG, ns, "link"); return link; ) // Traite les balises récapitulatives dans le flux. private String readSummary(XmlPullParser parser) renvoie IOException, XmlPullParserException ( parser.require(XmlPullParser.START_TAG, ns, "summary"); String summary = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "summary"); return summary; ) // Pour les balises title et summary, extrait leurs valeurs de texte. private String readText (XmlPullParser parser) renvoie IOException, XmlPullParserException ( String result = ""; if (parser.next() == XmlPullParser.TEXT) ( result = parser.getText(); parser.nextTag(); ) return result; ) ... )

Sauter les éléments dont vous n'avez pas besoin

Dans l'une des étapes d'analyse XML décrites ci-dessus, l'analyseur ignore les balises qui ne nous intéressent pas. Vous trouverez ci-dessous le code de l'analyseur pour la méthode skip() :

Private void skip (analyseur XmlPullParser) lance XmlPullParserException, IOException (if (parser.getEventType() != XmlPullParser.START_TAG) ( throw new IllegalStateException(); ) int profondeur = 1; tandis que (profondeur != 0) ( switch (analyseur. next()) ( case XmlPullParser.END_TAG : profondeur-- ; break ; case XmlPullParser.START_TAG : profondeur++; break; ) ) )

Voici comment cela fonctionne:

  • La méthode lève une exception si l'événement en cours n'est pas START_TAG .
  • Il consomme START_TAG et tous les événements jusqu'à END_TAG.
  • Pour s'assurer qu'il s'arrête au bon END_TAG et non à la première balise après le START_TAG d'origine, il garde une trace de la profondeur d'imbrication.

Ainsi, si l'élément actuel a des éléments imbriqués, la valeur de profondeur ne sera pas 0 tant que l'analyseur n'aura pas traité tous les événements entre le START_TAG d'origine et son END_TAG correspondant. Par exemple, considérons comment l'analyseur passe un élément qui possède 2 éléments imbriqués, Et :

  • Lors du premier passage dans la boucle while, la prochaine balise que l'analyseur rencontre après il s'agit de START_TAG pour
  • Lors du deuxième passage dans la boucle while, la balise suivante rencontrée par l'analyseur est END_TAG.
  • Lors du troisième passage dans la boucle while, la balise suivante rencontrée par l'analyseur est START_TAG . La valeur de profondeur est augmentée à 2.
  • Lors du quatrième passage dans la boucle while, la balise suivante rencontrée par l'analyseur est END_TAG.. La valeur de profondeur est réduite à 1.
  • Lors du cinquième et dernier passage dans la boucle while, la prochaine balise rencontrée par l'analyseur est END_TAG.. La valeur de profondeur est réduite à 0, indiquant que l'élément a été ignoré avec succès.

Traitement des données XML

L’exemple d’application reçoit et analyse un flux XML dans une AsyncTask. Le traitement a lieu en dehors du thread principal de l’interface utilisateur. Une fois le traitement terminé, l'application met à jour l'interface utilisateur dans l'activité principale (NetworkActivity).

Dans l'extrait ci-dessous, la méthode loadPage() effectue les opérations suivantes :

  • Initialise une variable chaîne avec une URL pointant vers un flux XML.
  • Si les paramètres utilisateur et la connexion réseau le permettent, appelle new DownloadXmlTask().execute(url) . Cela crée un nouvel objet DownloadXmlTask ​​​​(sous-classe AsyncTask) et exécute sa méthodeexecute(), qui télécharge et analyse le canal et renvoie un résultat de chaîne qui sera affiché dans l'interface utilisateur.
classe publique NetworkActivity étend l'activité ( public static final String WIFI = "Wi-Fi"; public static final String ANY = "Any"; private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort =newest"; // S'il existe une connexion Wi-Fi. private static boolean wifiConnected = false; // S'il existe une connexion mobile. private static boolean mobileConnected = false; // Si l'affichage doit être actualisé. public static boolean rafraîchirDisplay = true; public static String sPref = null; ... // Utilise AsyncTask pour télécharger le flux XML depuis stackoverflow.com. public void loadPage() ( if((sPref.equals(ANY)) && (wifiConnected || mobileConnected )) ( new DownloadXmlTask().execute(URL); ) else if ((sPref.equals(WIFI)) && (wifiConnected)) ( new DownloadXmlTask().execute(URL); ) else ( // afficher l'erreur ) )
  • doInBackground() exécute la méthode loadXmlFromNetwork(). Il transmet l'URL de la chaîne en tant que paramètre. La méthode loadXmlFromNetwork() reçoit et traite le canal. Une fois le traitement terminé, il renvoie la chaîne résultante.
  • onPostExecute() prend la chaîne renvoyée et l'affiche dans l'interface utilisateur.
// Implémentation d'AsyncTask utilisée pour télécharger le flux XML depuis stackoverflow.com. la classe privée DownloadXmlTask ​​​​étend AsyncTask ( @Override protected String doInBackground(String... urls) ( try ( return loadXmlFromNetwork(urls); ) catch (IOException e) ( return getResources().getString(R.string.connection_error); ) catch (XmlPullParserException e) ( return getResources().getString(R.string.xml_error); ) ) @Override protected void onPostExecute(String result) ( setContentView(R.layout.main); // Affiche la chaîne HTML dans l'interface utilisateur via une WebView WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.loadData(result, "text/html", null); ) )

Vous trouverez ci-dessous la méthode loadXmlFromNetwork() qui est appelée depuis DownloadXmlTask. Il fait ce qui suit :

  1. Crée une instance de StackOverflowXmlParser. Il crée également des variables pour les objets List Entry, ainsi que le titre, l'URL et le résumé, pour stocker les valeurs extraites du flux XML pour ces champs.
  2. Appelle downloadUrl() qui télécharge le canal et le renvoie sous forme d'InputStream.
  3. Utilise StackOverflowXmlParser pour analyser un InputStream. StackOverflowXmlParser remplit les entrées de liste avec les données du flux.
  4. Traite les entrées List et combine les données du canal avec le balisage HTML.
  5. Renvoie la chaîne HTML affichée dans l'interface utilisateur de l'activité principale, AsyncTask, dans la méthode onPostExecute().
// Télécharge du XML depuis stackoverflow.com, l'analyse et le combine avec // le balisage HTML. Renvoie la chaîne HTML. private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException ( InputStream stream = null; // Instancier l'analyseur StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser(); List entrées = null ; Titre de la chaîne = null ; URL de chaîne = null ; Résumé de la chaîne = null ; Calendrier rightNow = Calendar.getInstance(); Formateur DateFormat = new SimpleDateFormat("MMM jj h:mmaa"); // Vérifie si l'utilisateur a défini la préférence pour inclure le texte récapitulatif SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); boolean pref = sharedPrefs.getBoolean("summaryPref", false); StringBuilder htmlString = new StringBuilder(); htmlString.append("

" + getResources().getString(R.string.page_title) + "

"); htmlString.append(" " + getResources().getString(R.string.updated) + " " + formatter.format(rightNow.getTime()) + ""); try ( stream = downloadUrl(urlString); entrées = stackOverflowXmlParser.parse(stream); // S'assure que l'InputStream est fermé une fois que l'application // a fini de l'utiliser. ) enfin ( if (stream != null) ( stream.close(); ) ) // StackOverflowXmlParser renvoie une liste (appelée "entrées") d'objets Entry. // Chaque objet Entry représente une seule publication dans le flux XML. // Cette section traite la liste des entrées pour les combiner. entrée avec balisage HTML. // Chaque entrée est affichée dans l'interface utilisateur sous la forme d'un lien qui inclut éventuellement // un résumé textuel. for (Entrée d'entrée : entrées) ( htmlString.append("

" + entrée.titre + "

"); // Si l'utilisateur définit la préférence pour inclure un texte de résumé, // l'ajoute à l'affichage. if (pref) ( htmlString.append(entry.summary); ) ) return htmlString.toString(); ) // Étant donné une représentation sous forme de chaîne d'une URL, établit une connexion et obtient // un flux d'entrée. private InputStream downloadUrl(String urlString) throws IOException ( URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection() ; conn.setReadTimeout(10000 /* millisecondes */); conn.setConnectTimeout(15000 /* millisecondes */); conn.setRequestMethod("GET"); conn.setDoInput(true); // Démarre la requête conn.connect( ); renvoie conn.getInputStream(); )
Auteur : Arsène Kapoulkine
Date de publication : 21 septembre 2012
Traduction : A. Panin
Date de traduction : 10 novembre 2013

Introduction

XML est un langage de balisage standardisé qui spécifie un ensemble de règles pour encoder des documents structurés hiérarchiquement dans un format texte lisible par l'homme. Le standard XML s'est répandu et est utilisé pour générer aussi bien des documents simples très compacts (comme les requêtes SOAP) que des documents de plusieurs gigaoctets (utilisés par le projet OpenStreetMap) avec des dépendances de données complexes (COLLADA). Pour traiter les documents XML, les utilisateurs ont généralement besoin d'une bibliothèque spéciale : elle doit implémenter un analyseur de documents XML qui convertit le document d'un document textuel en une représentation interne. La norme XML représente un compromis en termes de vitesse d'analyse, de lisibilité pour l'utilisateur et de complexité du code à analyser. Par conséquent, disposer d'un système rapide pour analyser les documents XML peut influencer le choix préféré de XML comme format de stockage des données d'application.

Ce chapitre décrit diverses techniques visant à améliorer les performances du système d'analyse décrit et permettant à l'auteur de développer un système d'analyse extrêmement productif en utilisant le langage de programmation C++ : pugixml. Bien que ces techniques aient été utilisées pour le système d'analyse de documents XML, la plupart d'entre elles peuvent être appliquées à des systèmes d'analyse de documents d'autres formats ou même à des composants logiciels totalement indépendants (par exemple, les algorithmes de gestion de la mémoire sont largement utilisés dans des domaines non liés aux systèmes d'analyse de documents texte). .

Étant donné qu'il existe plusieurs approches très différentes pour analyser les documents XML et que le système d'analyse doit effectuer des étapes supplémentaires que même ceux expérimentés avec les documents XML ignorent, il est important de d'abord décrire la tâche à accomplir avant d'entrer dans les détails des détails de mise en œuvre.

Modèles de systèmes d'analyse XML

Chacun des différents modèles de systèmes d'analyse XML est optimal dans certaines situations, et chacun de ces modèles possède ses propres paramètres de performances et de consommation de mémoire. Les modèles suivants sont les plus utilisés :

  • Lors de l'utilisation de systèmes d'analyse basés sur SAX (API simple pour XML), l'utilisateur se voit présenter un composant logiciel qui attend un flux de données de document en entrée et fournit plusieurs fonctions de rappel, telles que "balise ouverte", "balise de fermeture", "caractère". à l'intérieur de la balise". Le système d'analyse utilise des fonctions de rappel lors du traitement des données du document. Le contexte requis pour l'analyse est limité par la profondeur de l'arborescence de l'élément actuel, ce qui implique une réduction significative des besoins en mémoire. Ce type de système d'analyse peut être utilisé pour traiter des documents diffusés en continu lorsqu'une seule partie du document est disponible à la fois.
  • L'analyse pull est similaire à l'analyse basée sur SAX en termes de processus lui-même - un élément du document est traité à la fois, mais la méthode de gestion du processus d'analyse est modifiée : dans un système d'analyse basé sur SAX, le processus d'analyse est contrôlé par le système lui-même utilisant des fonctions de rappel, tandis que lors de l'analyse pull, l'utilisateur contrôle le processus d'analyse à l'aide d'un objet de type itérateur.
  • Lors de l'utilisation de systèmes d'analyse basés sur DOM (Document Object Model), l'utilisateur transmet au système d'analyse un document complet sous la forme d'un tampon ou d'un flux de données texte, sur la base duquel le système d'analyse génère une représentation objet en mémoire de l'ensemble. arborescence d'éléments de document, utilisant des objets distincts pour chacun d'eux, un élément ou un attribut XML spécifique, et un ensemble d'opérations valides (par exemple, "obtenir tous les enfants de ce nœud"). La bibliothèque pugxml utilise ce modèle.

Le choix du modèle de système d'analyse dépend généralement de la taille du document et de sa structure. Étant donné que pugixml analyse en fonction du DOM, il est efficace pour les documents qui :

  • sont si petits en taille qu'ils peuvent tenir complètement dans la mémoire,
  • avoir une structure complexe avec des liens entre les nœuds qui doivent être traversés, ou
  • nécessitent des transformations de documents complexes.

Solutions architecturales en pugixml

Lors du développement de la bibliothèque pugixml, l'accent a été mis en grande partie sur le problème de la création de la représentation DOM, car même si des systèmes d'analyse rapides et légers basés sur SAX (comme Expat) étaient disponibles, tous les systèmes d'analyse XML basés sur DOM étaient disponibles pour la production. utilisés au moment de la création de pugixml (2006) n'étaient ni trop légers ni trop rapides. Sur cette base, l'objectif principal du processus de développement de puixml est de créer une bibliothèque très rapide et légère pour effectuer des manipulations de documents XML basées sur DOM.


la publication de cet article n'est autorisée qu'avec un lien vers le site Internet de l'auteur de l'article

Dans cet article, je vais montrer un exemple de la façon d'analyser un gros fichier XML. Si votre serveur (hébergement) n'interdit pas d'augmenter la durée d'exécution du script, alors vous pouvez analyser un fichier XML pesant au moins des gigaoctets ; personnellement, j'ai analysé uniquement des fichiers d'ozone pesant 450 mégaoctets.

Lors de l'analyse de fichiers XML volumineux, deux problèmes surviennent :
1. Pas assez de mémoire.
2. Le temps alloué au script est insuffisant.

Le deuxième problème avec le temps peut être résolu si le serveur ne l'interdit pas.
Mais le problème de la mémoire est difficile à résoudre, même si nous parlons de votre propre serveur, alors déplacer des fichiers de 500 mégaoctets n'est pas très simple, et il n'est tout simplement pas possible d'augmenter la mémoire sur l'hébergement et le VDS.

PHP dispose de plusieurs options de traitement XML intégrées : SimpleXML, DOM, SAX.
Toutes ces options sont décrites en détail dans de nombreux articles avec des exemples, mais tous les exemples démontrent l'utilisation d'un document XML complet.

Voici un exemple, obtenir un objet à partir d'un fichier XML

Vous pouvez maintenant traiter cet objet, MAIS...
Comme vous pouvez le voir, l'intégralité du fichier XML est lue en mémoire, puis tout est analysé dans un objet.
Autrement dit, toutes les données vont en mémoire et s'il n'y a pas suffisamment de mémoire allouée, le script s'arrête.

Cette option n'est pas adaptée au traitement de fichiers volumineux, vous devez lire le fichier ligne par ligne et traiter ces données une par une.
Dans ce cas, le contrôle de validité est également effectué au fur et à mesure du traitement des données, il faut donc pouvoir annuler, par exemple, supprimer toutes les données saisies dans la base de données dans le cas d'un fichier XML invalide, ou effectuer deux passages Dans le fichier, lisez d'abord pour vérifier la validité, puis lisez pour traiter les données.

Voici un exemple théorique d'analyse d'un gros fichier XML.
Ce script lit un caractère à la fois dans un fichier, collecte ces données en blocs et les envoie à l'analyseur XML.
Cette approche résout complètement le problème de mémoire et ne provoque pas de charge, mais aggrave le problème avec le temps. Comment essayer de résoudre le problème au fil du temps, lisez ci-dessous.

Fonction webi_xml ($fichier)
{

########
### fonction de données

{
imprimer $données ;
}
############################################



{
imprimer $nom ;
print_r($attrs);
}


## fonction de balise de fermeture
fonction endElement ($parser, $name)
{
imprimer $nom ;
}
############################################

($xml_parser, "données");

// ouvre le fichier
$fp = fopen($fichier, "r");

$perviy_vxod = 1 ; $données = "" ;



{

$simvol = fgetc ($fp); $données .= $simvol ;


if($simvol != ">" ) ( continuer ;)


écho "

casser;
}

$données = "" ;
}
fclose($fp);

Webi_xml("1.xml");

?>

Dans cet exemple, j'ai tout mis dans une seule fonction webi_xml() et tout en bas vous pouvez voir son appel.
Le script lui-même se compose de trois fonctions principales :
1. Une fonction qui intercepte l'ouverture de la balise startElement()
2. Une fonction qui capture la balise de fermeture endElement()
3. Et la fonction de réception données() .

Supposons que le contenu du fichier 1.xml soit une recette



< title >Pain simple
< ingredient amount = "3" unit = "стакан" >Farine
< ingredient amount = "0.25" unit = "грамм" >Levure
< ingredient amount = "1.5" unit = "стакан" >Eau chaude
< ingredient amount = "1" unit = "чайная ложка" >Sel
< instructions >
< step > Mélanger tous les ingrédients et bien pétrir.
< step > Couvrir d'un torchon et laisser reposer une heure dans une pièce tiède..
< step > Pétrir à nouveau, déposer sur une plaque à pâtisserie et mettre au four.
< step > Visitez le site du site


Nous commençons par un défi fonction générale webi_xml("1.xml");
Ensuite, l'analyseur démarre dans cette fonction et convertit tous les noms de balises en majuscules afin que toutes les balises aient la même casse.

$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);

Nous indiquons maintenant quelles fonctions fonctionneront pour intercepter l'ouverture d'une balise, la fermeture et le traitement des données.

xml_set_element_handler($xml_parser, "startElement", "endElement");
xml_set_character_data_handler($xml_parser, "données");

Vient ensuite l'ouverture du fichier spécifié, en parcourant le fichier un caractère à la fois et chaque caractère est ajouté à la variable chaîne jusqu'à ce que le caractère soit trouvé. > .
S'il s'agit du tout premier accès au fichier, alors en cours de route, tout ce qui est inutile au début du fichier sera supprimé, tout ce qui précède , c'est la balise par laquelle XML doit commencer.
Pour la première fois, une variable chaîne contiendra une chaîne

Et envoyez-le au désassembleur
xml_parse ($xml_parser, $data, feof ($fp));
Après le traitement des données, la variable chaîne est réinitialisée et la collecte des données dans une chaîne recommence et la chaîne est formée pour la deuxième fois.

Au troisieme
</b><br>le quatrième <br><b>Pain simple

Veuillez noter qu'une variable chaîne est toujours formée à partir d'une balise complétée > et il n'est pas nécessaire d'envoyer au cambrioleur une étiquette ouverte et fermée avec des données, par exemple
Pain simple
Il est important que ce gestionnaire reçoive une balise entière ininterrompue, au moins une balise ouverte, et à l'étape suivante une balise fermée, ou reçoive immédiatement 1000 lignes d'un fichier, peu importe, l'essentiel est que la balise ne casse pas, par exemple

le>Pain nature
De cette façon, il est impossible d’envoyer des données au gestionnaire, puisque la balise est déchirée.
Vous pouvez proposer votre propre méthode d'envoi de données au gestionnaire, par exemple, collecter 1 mégaoctet de données et l'envoyer au gestionnaire pour augmenter la vitesse, assurez-vous simplement que les balises sont toujours complétées et que les données peuvent être déchirées.
Simple</b><br><b>pain

Ainsi, par parties à votre guise, vous pouvez envoyer gros fichier au gestionnaire.

Voyons maintenant comment ces données sont traitées et comment les obtenir.

Commençons par la fonction des balises d'ouverture startElement ($parser, $name, $attrs)
Supposons que le traitement ait atteint la ligne
< ingredient amount = "3" unit = "стакан" >Farine
Ensuite, à l'intérieur de la fonction, la variable $name sera égale à ingrédient c'est-à-dire le nom de la balise ouverte (il n'est pas encore venu de fermer la balise).
Dans ce cas également, un tableau d'attributs de cette balise $attrs sera disponible, qui contiendra des données montant = "3" et unité = "verre".

Après cela, les données de la balise ouverte ont été traitées par la fonction données ($ analyseur, $ données)
La variable $data contiendra tout ce qui se trouve entre les balises d'ouverture et de fermeture, dans notre cas c'est le texte Muka

Et le traitement de notre chaîne par la fonction se termine endElement ($parser, $name)
C'est le nom de la balise fermée, dans notre cas $name sera égal à ingrédient

Et après cela, tout a de nouveau tourné en rond.

L'exemple ci-dessus démontre uniquement le principe du traitement XML, mais pour une application réelle, il doit être modifié.
En règle générale, vous devez analyser du XML volumineux pour saisir des données dans la base de données et, pour traiter correctement les données, vous devez savoir à quelle balise ouverte les données appartiennent, à quel niveau d'imbrication se trouve la balise et quelles balises sont ouvertes dans la hiérarchie ci-dessus. Avec ces informations, vous pouvez traiter le fichier correctement sans aucun problème.
Pour ce faire, vous devez introduire plusieurs variables globales qui collecteront des informations sur les balises ouvertes, l'imbrication et les données.
Voici un exemple que vous pouvez utiliser

Fonction webi_xml ($fichier)
{
global $webi_profondeur ; // compteur pour suivre la profondeur d'imbrication
$webi_profondeur = 0 ;
global $webi_tag_open ; // contiendra un tableau d'open in ce moment Mots clés
$webi_tag_open = tableau();
global $webi_data_temp ; // ce tableau contiendra les données d'une balise

####################################################
### fonction de données
données de fonction ($parser, $data)
{
global $webi_profondeur ;
global $webi_tag_open ;
global $webi_data_temp ;
// ajoute des données au tableau indiquant l'imbrication et la balise actuellement ouverte
$webi_data_temp [ $webi_degree ][ $webi_tag_open [ $webi_degree ]][ "data" ].= $data ;
}
############################################

####################################################
### fonction de balise d'ouverture
fonction startElement ($parser, $name, $attrs)
{
global $webi_profondeur ;
global $webi_tag_open ;
global $webi_data_temp ;

// si le niveau d'imbrication n'est plus nul, alors une balise est déjà ouverte
// et les données qui en découlent sont déjà dans le tableau, vous pouvez les traiter
si ($webi_profondeur)
{




" ;

imprimer "
" ;
print_r($webi_tag_open); // tableau de balises ouvertes
imprimer "


" ;

// après avoir traité les données, supprimez-les pour libérer de la mémoire
unset($GLOBALS [ "webi_data_temp" ][ $webi_degree ]);
}

// maintenant la balise suivante est ouverte et la suite du traitement aura lieu à l'étape suivante
$webi_profondeur++; // augmente l'imbrication

$webi_tag_open [ $webi_degree ]= $name ; // ajoute une balise ouverte au tableau d'informations
$webi_data_temp [ $webi_owned ][ $name ][ "attrs" ]= $attrs ; // ajoute maintenant les attributs de balise

}
###############################################

#################################################
## fonction de balise de fermeture
fonction endElement ($parser, $name) (
global $webi_profondeur ;
global $webi_tag_open ;
global $webi_data_temp ;

// Le traitement des données commence ici, par exemple l'ajout à la base de données, l'enregistrement dans un fichier, etc.
// $webi_tag_open contient une chaîne de balises ouvertes par niveau d'imbrication
// par exemple $webi_tag_open[$webi_degree] contient le nom de la balise ouverte dont les informations sont en cours de traitement
// Niveau d'imbrication des balises $webi_degree
// $webi_data_temp[$webi_owned][$webi_tag_open[$webi_degree]]["attrs"] tableau d'attributs de balise
// $webi_data_temp[$webi_owned][$webi_tag_open[$webi_third]]["data"] données de balise

Imprimez "données" . $webi_tag_open [ $webi_degree ]. "--" .($webi_data_temp [ $webi_degree ][ $webi_tag_open [ $webi_degree ]][ "data" ]). "
" ;
print_r ($webi_data_temp [ $webi_owned ][ $webi_tag_open [ $webi_degree ]][ "attrs" ]);
imprimer "
" ;
print_r($webi_tag_open);
imprimer "


" ;

Non défini($GLOBALS [ "webi_data_temp" ]); // après avoir traité les données, nous supprimons tout le tableau avec les données, puisque la balise a été fermée
unset($GLOBALS [ "webi_tag_open" ][ $webi_degree ]); // supprime les informations sur cette balise ouverte... depuis sa fermeture

$webi_profondeur --; // réduit l'imbrication
}
############################################

$xml_parser = xml_parser_create();
xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true);

// indique quelles fonctions fonctionneront lors de l'ouverture et de la fermeture des balises
xml_set_element_handler($xml_parser, "startElement", "endElement");

// spécifie une fonction pour travailler avec des données
xml_set_character_data_handler($xml_parser, "données");

// ouvre le fichier
$fp = fopen($fichier, "r");

$perviy_vxod = 1 ; // drapeau pour vérifier la première entrée dans le fichier
$données = "" ; // ici, nous collectons les données du fichier en plusieurs parties et les envoyons à l'analyseur XML

// boucle jusqu'à ce que la fin du fichier soit trouvée
tandis que (! feof ($fp ) et $fp )
{
$simvol = fgetc ($fp); // lit un caractère du fichier
$données .= $simvol ; // ajoute ce caractère aux données à envoyer

// si le caractère n'est pas la balise de fin, alors revenez au début de la boucle et ajoutez un autre caractère aux données, et ainsi de suite jusqu'à ce que la balise de fin soit trouvée
if($simvol != ">" ) ( continuer ;)
// si la balise de fermeture a été trouvée, nous enverrons maintenant ces données collectées pour traitement

// vérifie s'il s'agit de la première entrée dans le fichier, alors nous supprimerons tout ce qui se trouve avant la balise// puisque parfois vous pouvez rencontrer des déchets avant le début du XML (éditeurs maladroits, ou le fichier a été reçu par un script d'un autre serveur)
if($perviy_vxod ) ( $data = strstr ($data , "

// jette maintenant les données dans l'analyseur XML
if (! xml_parse ($xml_parser, $data, feof ($fp))) (

// ici vous pouvez traiter et recevoir les erreurs de validité...
// dès qu'une erreur est rencontrée, l'analyse s'arrête
écho "
Erreur XML : " . xml_error_string(xml_get_error_code($xml_parser));
echo "à la ligne". xml_get_current_line_number ($xml_parser);
casser;
}

// après l'analyse, supprimez les données collectées pour l'étape suivante du cycle.
$données = "" ;
}
fclose($fp);
xml_parser_free($xml_parser);
// suppression des variables globales
unset($GLOBALS [ "webi_profondeur" ]);
unset($GLOBALS [ "webi_tag_open" ]);
unset($GLOBALS [ "webi_data_temp" ]);

Webi_xml("1.xml");

?>

L'exemple entier est accompagné de commentaires, maintenant testez et expérimentez.
Veuillez noter que dans la fonction de travail avec des données, les données ne sont pas simplement insérées dans un tableau, mais plutôt ajoutées à l'aide de " .=" étant donné que les données peuvent ne pas arriver dans leur intégralité et que si vous effectuez simplement une mission, vous recevrez de temps en temps les données par morceaux.

Eh bien, c'est tout, il y a désormais suffisamment de mémoire pour traiter un fichier de n'importe quelle taille, mais la durée d'exécution du script peut être augmentée de plusieurs manières.
Insérer une fonction au début du script
set_time_limit(6000);
ou
ini_set ("max_execution_time" , "6000" );

Ou ajoutez du texte au fichier .htaccess
php_value max_execution_time 6000

Ces exemples augmenteront la durée d’exécution du script à 6 000 secondes.
Vous pouvez augmenter la durée de cette manière uniquement lorsque le mode sans échec est désactivé.

Si vous avez accès à la modification du php.ini, vous pouvez augmenter le temps en utilisant
max_execution_time = 6000

Par exemple, sur un hébergement Masterhost, au moment de la rédaction de cet article, il est interdit d'augmenter le temps de script, malgré mode sans échec, mais si vous êtes un pro, vous pouvez créer votre propre build php sur masterhost, mais ce n'est pas dans cet article.