Extraction !

Nous abordons un didactitiel en dehors de la série de "pied à l'étrier" et qui va beaucoup plus loin que la simple extraction de donnée unique : il va être à la base de pratiquement toutes les extractions de données d'une page HTML vers un XML pour une utilisation vers un(des) périphérique(s) Eedomus.
On rappelle que la base c'est de créer un fichier XML contenant les données utiles afin qu'il soit interrogeable par un XPath. Si vous avez suivi tous les didacticiels, on ne reviendra pas sur les mises en forme, les problèmes d'accentuées, de syntaxe de balise etc que vous devriez arriver à résoudre seul.
Sur l'extraction proprement dite, nous n'avons vu qu'un exemple simple ou une information solitaire était identifiée par du code reconnaissable la balisant, nous n'avons donc du que supprimer ce code "parasite".
Le problème en général est tout autre, puisque nos données seront multiples, identifiées généralement de la même manière, certaines devant servir de balise, d'autres de données et il va falloir dans un premier temps, avant de les traiter, apprendre à les identifier.

Construction d'un tableau

Pour identifier des données, rassemblées en une seule variable, rien de mieux qu'un tableau multidimentionnel, donc dans un premier temps, il va falloir apprendre à créer un tel tableau à partir de notre HTML, et cela va se faire en une seule ligne de code !
Pour comprendre comment nous allons réaliser ce miracle, il va falloir revenir sur les Regex et apprendre une nouvelle instruction : preg_match_all qui va nous servir notre tableau "sur un plateau", pour peu qu'on lui donne le bon Regex qui contrairement à ce que nous avons eu l'habitude de voir, va sembler un peu compliqué de prime abord mais que nous allons construire par étape, pour se faire, il va nous falloir notre fameux Bac à Sable habituel, dans lequel nous collerons un httpQuery et nous allons partir de notre exemple de micro-bulletin météo. On se rappelle sa structure :

code avant
<div class="vigilance-bulletin-status">
<strong>Aujourd’hui dimanche 17:</strong>
<br>
<strong>Encore orageux sur la Provence-Alpes-Côte d'Azur, ensoleillé ailleurs.</strong>
</div>
code après

Collons tout d'abord notre exemple : il lui a été rajouté des retour ligne, mais on va les garder pour comprendre comment ils peuvent être gérés, par contre on laisse les données encadrées par leurs balises de mise en forme ! (il n'ya jamais de retour ligne à ce niveau et s'il ya des espaces, c'est qu'ils font partie de la donnée)
Pour simuler qu'"il y ai du code avant et après... on ajoute "code avant" et "code après".

On va donc appeler un httpQuery dans notre code, par exemple (l'url ici, n'a aucune importance, mais on pourrait, puisque cet exemple provient de meteo.orange.fr, lui donner cette adresse)

$url = httpQuery ("http://meteo.orange.fr",'GET');
On peut vérifier avec un echo que notre $url contient bien le texte HTML entré (puis on le supprime).

L'instruction preg_match_all

Nous allons donc maintenant utiliser l'instruction preg_match_all qui va aller rechercher dans le texte indiqué, toutes les occurences définies par le Regex pour les mettre dans un tableau, comme cela :

preg_match_all (Regex,texte,tableau)

En code, cela nous donnera:

preg_match_all ('##',$url,$matches);
Evidemment, pour l'instant le Regex est vide, mais l'important est de savoir comment on va le construire d'une part et pour tout de suite, comment on va contrôler le résultat, cela se fera par...

Affichage d'un tableau

On va se référer à la petite aide de Edotrucs sur l'affichage avec echo qui nous indique la commande simplifiée pour les tableaux : print_r(), cette commande nous indiquera les éléments du tableau classés par index (en commençant par 0), ainsi que leurs sous éléments (autre dimension), ce sont ces indications qui vont nous permettre les extractions, connaissant alors les index, mais nous verrons plus tard comment faire. Pour l'instant, il va falloir le construire ce tableau, donc nous ajoutons à la suite de l'instruction précédente :
print_r($matches);
On va commencer par inclure dans le Regex la première ligne de code "balise" :
<div class="vigilance-bulletin-status">
donc notre ligne devient : (ensuite on n'indiquera que le Regex)
preg_match_all ('#<div class="vigilance-bulletin-status">#',$url,$matches);
On note que cette ligne contient des guillemets, c'est pour cela que notre Regex sera identifié entre des apostrophes (on a le choix), plutôt que d'échapper les guillemets avec un antislash (on a déjà vu cela). On observe ce que cela donne (éxécution) : un indice 0 qui contient une deuxième dimension, d'indice 0 qui contient la ligne que nous avons indiqué.

Les caractères blancs

Entre cette ligne et la suivant : <strong>, il ya un retour ligne, possiblement des espaces (pas dans le fichier orange, mais comme cela peut arriver...) indiquons le par le Regex \s et comme il peut y avoir plusieurs "caractères blancs", mettons lui une étoile (= en n'importe quelle quantité), donc \s* cela donne (#Regex#):

#<div class="vigilance-bulletin-status">\s*<strong>#

Eh oui, ça commence à se compliquer, mais vous verrez qu'à la fin vous saurez écrire en hieroglyphes ;-).
Maintenant qu'obtenons nous ? tout à fait logiquement, la même chose, mais avec les 2 premières lignes.

Parenthèses capturantes

Nous allons apprendre maintenant à créer des éléments de tableaux en indiquant simplement l'élément sélectionné en le mettant entre parenthèse, l'index sera 1,2,3... dans l'ordre des parenthèses ouvrantes (donc même imbriquées) et notez que l'indice 0 sera toujours tout le Regex (vu précédemment).
C'est indispensable puisque notre ligne suivante est une donnée. Nous ne l'avons pas traité (on l'a supprimé) dans notre didacticiel sur l'extration de la micro-météo, ici, nous l'identifions afin de la traiter... ou pas. Comme nous ne savons pas de qu'elle représente, nous allons lui donner le Regex .* (le point : n'importe quel caractère puis l'étoile : en n'importe quelle quantité) que nous allons donc mettre entre parenthèse, suivit immédiatement (sinon, cela sélectionnerait tout jusqu'à la fin) de la donnée suivante qui est un code balise (celui qui est entre les données), on n'inclu pas d'espaces blancs puisque il ne peut pas y en avoir, mais on ne les oublie pas sinon !
aie aie aie ... ça commence a devenir compliqué ? alors on résume : On obtient donc notre nouveau Regex :

#<div class="vigilance-bulletin-status">\s*<strong>(.*)</strong>#

Maintenant, on obtient un tableau avec 2 entrées principales (0 et 1) et dont la 2ème contient en indice 0 le texte mis entre parenthèses, faisons de même avec la deuxième donnée et allons jusqu'à la fin :

#<div class="vigilance-bulletin-status">\s*<strong>(.*)</strong>\s*<br>\s*<strong>(.*)\s*</strong>\s*</div>#

Houlala, hein ? :-)

On obtient donc un 3eme indice (2 puisqu'on commence à 0), dont la première entrée d'indice 0 contient notre 2eme donnée; on pourrait l'identifier par [2][0] si on voulait l'afficher, exemple, si on met un echo (qui affiche une variable simple, ici un élément du tableau) à la place de l'affichage du tableau print_r():

echo $matches[2][0];
Donc voila un autre moyen, un peu plus compliqué que le précédent, mais souvent indispensable, pour obtenir notre donnée, mais la n'est pas le but, de compliquer quand on peut faire simple : le but c'est SI on avait plusieurs entrées du même type ! par exemple "météo du matin" et "prévision de l'après midi", or c'est exactement ce qu'il se passe en journée dans la page météo orange, la micro météo extraite auparavant ne prenant que la première occurence.
Dans ce cas, on aurait toujours pour les mêmes groupes d'indice (ici 1 pour la date et 2 pour le bulletin), des indices incrémentés pour les données suivantes. Vous pouvez le tester en doublant les données HTML de notre exemple et en changeant les textes de cette 2ème partie, exemple : "Cet après midi" et "Il fera beau". Vous verrez alors que cette deuxième partie aura l'index 1 dans chaque index principal.


NB: A partir de ce point, les données sont imaginaires
et conçues pour illustrer la méthode de recherche de donnée,
de code, la construction du tableau et l'extraction du XML

Pour aller plus loin : parenthèses imbriquées

Si vous avez besoin d'imbriquer des captures, vous pouvez aussi décider de ne pas capturer, avec ?: certaines parties, vous aurez des explications dans le lien proposés sur l'entrée Regex de Eedotrucs, mais par exemple ici :

(tagada(tsoin(?:tsoin))turl)ututu
donnera : 1: tagadatsointurl 2: tsoin

Pour aller plus loin : Regex multiples

Le plus souvent, vous aurez un code principal suivit de codes secondaires, par exemple :

code1 - donnée 1 - fin code 1
code2 - donnée 2a -fin code intermédiaire donnée 2a' fin code2
code2 - donnée 2b -fin code intermédiaire donnée 2b' fin code2
code2 - donnée 2c -fin code intermédiaire donnée 2c' fin code2

Dans ce cas, vous ferez un Regex pour la première donnée et mettrez un OU (barre verticale |) avec le Regex des données suivantes: Vous obtiendrez alors en index 1 la première donnée et en index 2 et 3 les groupes de sous données abc et a'b'c'
L'important à retenir c'est que la forme du code doit être constante pour un Regex donné et ce sont les répétitions de cette forme à l'intérieur du code HTML qui incrémenteront les index !

J'ai rien compris, je veux un exemple

OK, on va repartir sur notre exemple météo, mais on va lui donner plusieurs données différentes avec des groupes de données principales, par exemple :

Aujourdhui
matin< --- >texte
midi< --- >texte
soir< --- >texte
Demain
matin< --- >texte
midi< --- >texte
Etant entendu que dans ce schéma, Aujourdhui et Demain sont encadrés par les même codes et de même pour les matin, midi et soir - on a volontairement omis le soir de Demain pour observer que la valeur de cet indice sera bien omise.
Si on "invente" un code les identifiant et les séparant, cela pourrait ressembler pour se calquer un peu sur notre exemple de début, à:

code avant

<div class="vigilance-bulletin-status">
<strong>Aujourdhui</strong></div>

code quelconque

<div class="vigilance-bulletin-données">
matin<strong>Orageux.</strong></div

<div class="vigilance-bulletin-données">
midi<strong>Moins orageux.</strong></div

<div class="vigilance-bulletin-données">
soir<strong>Les orages se terminent.</strong></div

code quelconque

<div class="vigilance-bulletin-status">
<strong>Demain</strong></div>

code quelconque

<div class="vigilance-bulletin-données">
matin<strong>Beau.</strong></div

<div class="vigilance-bulletin-données">
midi<strong>Grand soleil.</strong></div

code après

Je sais, cet exemple est un peu long, mais c'est un exercice d'analyse de structure, ici il n'y en a que 2 ! la première, pour Aujourdhui et Demain, la seconde pour tous les groupes "moment de la journée" - "temps"
On remarque que les groupes suivent simplement leur groupe principal Aujourdhui et Demain, pour organiser tout cela et obtenir notre tableau, nous allons donc faire :

  1. Un premier Regex pour la partie Aujourdhui et Demain
  2. séparé par un OU (la barre verticale |)
  3. Le deuxième Regex
Et c'est absolument tout ! construisons simplement (oui ! simplement !) notre Regex : on copie un "schema" et on insère le code "blanc" à la place des espaces et/ou des sauts de ligne puis on remplace nos données par (.*) :

Même si le résultat semble "compliqué", la construction sera rapide, on va le faire pas à pas pour le premier et on enchaînera pour la suite:

<div class="vigilance-bulletin-status"> // on insère ici le code espace blanc
<strong>(on remplace Aujourdhui par .* entre nos parenthèses capturantes)</strong></div>

On colle tout ceci en une seule ligne, cela donne :
<div class="vigilance-bulletin-status">\s*<strong>(.*)</strong></div>
et maintenant on enchaîne puisque vous savez faire, le Regex sera:

<div class="vigilance-bulletin-status">\s*<strong>(.*)</strong></div>
et (ou plutôt OU : |)
<div class="vigilance-bulletin-données">\s*(.*)<strong>(.*)</strong></div

cela nous donne notre Regex final:

#<div class="vigilance-bulletin-status">\s*<strong>(.*)</strong></div>|<div class="vigilance-bulletin-données">\s*(.*)<strong>(.*)</strong></div#
Essayons cela immédiatement sur la base précédente (avec l'affichage du tableau):
Remplaçons le test HTML dans la partie émulation
Remplaçons notre Regex et lançons : voila notre tableau bien ordonné ! reste à comprendre comment nous allons traiter tout ceci pour extraire les données voulues et dans l'ordre afin de faire un XML digne de ce nom !

Extraction !

Nous allons maintenant devoir faire des itérations sur le tableau pour passer en revue les données et les attribuer afin de les organiser en balises et superbalises
Nous savons que nous avons dans l'index principal 1 (on laisse le 0 de côté comme on l'a déjà vu) les super balises de journée, elles s'ouvrent donc et ne se referment qu'après que les sous balises d'index 2 soient refermées, les balises d'index 3 étant les données, nous voulons donc obtenir :

<meteo>
<Aujourdhui>
<matin>Orageux.</matin>
<midi>Moins orageux.</midi>
<soir>Les orages se terminent.</soir>
</Aujourd'hui>
<Demain>
<matin>Beau.</matin>
<midi>Grand soleil.</midi>
</Demain>
</meteo>

Ah ! il est beau notre XML, avec ses balises principales "meteo" non ? Bon, un peu artificiel puisque la page Orange n'est pas conçue comme avec notre exemple, facilité parceque les balises sont "prédigérées" puisqu'elles réclament des ajustements (n'arrondissez pas les yeux, ce lien a été donné au début de "Mon tout premier script" !). Mais on est d'accord ? une fois que vous saurez faire ça, vous saurez extraire n'importe quoi ! c'est parti ?
C'est parti.

foreach

foreach = pour chaque : cette instruction va parcourir des éléments du tableau, il suffira alors de vérifier leur présence, en sachant ici (dans cet exemple) que la super balise est dans l'index 1, les balises dans l'index 2 et les données dans l'index 3.

Principe

On remarque que il suffit donc de parcourir l'index des données (ici le 3), puis de vérifier dans l'index 1 (super balise) une présence : Si Oui, on écrit alors l'ouverture de super balise.
Si Non, et si il existe bien des données, on écrit l'ouverture de la balise pointée, puis de sa donnée, puis la fermeture de la balise

Dans cette démarche, on n'a pas la fermeture de la super balise, il suffit de le faire avant d'ouvrir la suivante, à condition que ce ne soit pas la première (index 0) et a la fin du foreach, on ferme la dernière super balise.
Pour se faire, on remarque que si les balises et les données peuvent être données en clair (les variables extraites par le foreach), lors de notre parcours foreach, il va falloir "stocker" notre super balise pour pouvoir l'identifier pour pouvoir la fermer ! En plus du foreach, il va donc nous falloir une variable, par exemple $sbalise et sans oublier de l'initialiser, par exemple à NULL ce qui permettra de ne la fermer que si elle contient une valeur.

Encore une petite choses et on attaque : on l'a vu dans le résultat escompté: il faut une balise globale qui encadre toutes les autres pour valider notre XML, la c'est facile, on l'écrit avant et après notre code.

Syntaxe

Notre foreach va parcourir le tableau d'index principal 3 (données) et renvoyer deux variables: $index et $donnée cela s'écrit ainsi :
foreach($matches[3] as $index => $donnée) {}
On peut vérifier (entre les accolades) qu'avec un echo, on obtient bien une liste indexée des données :
echo "l'index " . $index . " pointe sur : " . $donnée . "\n";
Maintenant, on va transcrire en code nos conditionnels, on fait un rappel :
variable: l'attribution = et l'ajout .= , le retour ligne \n, le point de concaténation ., les opérandes: == pour l'égalité, != pour différent, enfin la syntage if(){}elseif{}else{}
$xml = "<meteo>\n"; // initialisation du xml avec la balise globale
$sbalise = NULL; // définit la variable, ici avec une valeur nulle

foreach($matches[3] as $index => $donnée) {

if ($matches[1][$index] != '') {
      if ($sbalise != NULL) {$xml .= "</" . $sbalise . ">\n";} // fermeture super balise sauf si jamais définie
$sbalise = $matches[1][$index]; // on attribue la super balise courante
$xml .= "<" . $sbalise . ">\n"; // et on l'ouvre
}elseif ($donnée != ''){
$xml .= "<" . $matches[2][$index] . ">" . $donnée . "</" . $matches[2][$index] . ">\n"; // écriture de la donnée encadrée par sa balise
}

}

$xml .= "</" . $sbalise . ">\n"; // fermeture dernière super balise
$xml .= "</meteo>"; // fermeture balise globale
Maintenant on peut tester avec un echo $xml; pour retrouver ce qu'on désirait (début du chapître Extraction !
NB : pour l'XPath, quand des balises ont le même nom, il faut préciser la balise de rang supérieur jusqu'à la levée du doute : soit par exemple //Demain/matin

Exemple concret : Bulletin Ile de la Réunion (Météo-France)

Ici, on ne peut pas se calquer sur le schéma de la micro-météo obtenu sur le site Orange, puisque si on analyse le code source, on se retrouve avec non seulement des données d'encadrement non unique, à moins de remonter plus haut dans le code, ce qui nous oblige aussi à encadrer avec des données variables, ce qui nous empêche de simplement supprimer du code pour obtenir ce que nous voulons ! Par contre, nous allons pouvoir afficher directement la variable puisque la donnée est unique et il n'ya pas de XML complexe à construire.

Allons sur la page : http://www.meteofrance.re/previsions-meteo-reunion/bulletin recherchez la première ligne qui sera notre "micro-bulletin" et après avoir affiché le code source, repérez la:
Et pour formellement identifier notre première phrase "micro-bulletin, nous sélectionnons :

     </h1>
   </div>

   <div class="mod-body">
            <div class="article-row">
        <h2>JOURNEE DU SAMEDI 23 </h2>

        <p class="p-style-2" style="clear: both;"><br/><br/>Temps plus sec avec moins de vent.<br/><br/><br/>
Le réveil est lumineux .../...

Pour réaliser notre script, que nous allons tout d'abord tester, nous allons dans le bac à sable :

  1. toujours démarrer par le code php <?php
  2. copier le fichier d'émulation httpQuery
  3. y copier tout le code source
  4. en dessous, y inclure le script micro-meteo (sans le code php <?php) concernant la page Orange
  5. changer l'url d'appel $url de la page WEB
  6. supprimer les deux preg_replace qui se trouvent après le httpQuery
  7. et à la place, inclure :
un preg_match_all(Regex,$url,$matches) dont le Regex sera construit comme précédemment, soient:
on entoure de parenthèse uniquement ce que l'on désire capturer, puis on ajoute des "blancs" à chaque retour ligne / espaces et un code .* pour les données variables. Comme il n'y aura qu'une seule et unique donnée, nous savons à l'avance que l'Index principal sera 1 et le sous index sera 0 nous obtenons donc le code avec le Regex suivant :
preg_match_all('#<div class="mod-body">\s*<div class="article-row">\s*<h2>.*</h2>\s*<p class="p-style-2" style="clear: both;"><br/><br/>(.*)<br/><br/><br/>#',$url,$matches);
$url = strtolower($matches[1][0]);
Vous avez remarqué que nous ne comprenons presque plus le Regex alors que nous l'avons construit en copiant/collant une partie du code source comprenant l'élément recherché encadré par du code identifiant, en entourant de parenthèse ce que nous recherchons puis en ajoutant remplaçant juste les parties retour ligne/espaces et les données variables par du code Regex! cela s'est fait presque sans efforts !
Attention il ya un petit piège que vous découvrirez dans le 'tip' Gourmandise .

Pour finaliser notre réussite, on ne doit prendre que le code qui se trouve sous la partie émulation, mais il faudra ne pas oublier le <?php quand vous l'enregistrerez sous un autre nom, par exemple bulletin_re.php que vous enregistrerez sur l'Eedomus dans le dossier /script puis dans notre interface Eedomus, cloner (dupliquer) notre périphérique (voir en bas de sa configuration), lui donner un autre nom, une autre icône, changer l'appel php et... c'est tout.

Cet exercice conclue donc nos didacticiels...

A VOUS