Récemment j’avais la tâche d’extraire des données à partir de pages sans doute encodées sous Excel et sauvegardées sous forme de tableaux HTML. Comme il y avait environ 800 fichiers à traiter une solution programmatique s’imposait.
Le défi n’était pas évident mais avec un peu d’inspiration j’ai songé à utiliser des outils déjà existants. Le HTML venant aussi du SGML il n’y avait qu’un pas pour adapter des techniques utilisées avec le XML.
Voici une petite description étape par étape qui pourrait éventuellement servir pour former une couche données pour une application.
Par convention on peut placer tous les fichiers HTML à lire dans un dossier. Personnellement, j’ai utilisé des variables pour définir le path relatif et le nom du dossier comme suit:
$dataSourcePath = "html_datasource";
$file = $path. DIRECTORY_SEPARATOR . $dataSourcePath . DIRECTORY_SEPARATOR . $id . ».htm »;
On peut suppléer ce code avec un file_exists($file) pour tester la présence d’un fichier. Si les fichiers dont vous disposez ne sont pas numérotés de manière séquentielle vous pouvez générer une liste en utilisant la fonction glob:
$files = glob($dataSourcePath."/{*.html}");
Voici que vient la partie intéressante. On commence par charger un fichier HTML en DOMDocument (Document Object Model):
$doc->loadHTMLFile($file); // Load the HTML file
Ensuite on utilise cet objet pour initialiser une instance DOMXPath. Cela permet d’utiliser une requête de type XPath (ici sélectionner toutes les tables) qui renverra un node list.
$table = $xpath->query("//table")->item(0); // XPath Query to extract first table element
Disposant à présent d’un élément TABLE (notez l’appel à la méthode item pour récupérer le premier élement) sous forme de node DOM on peut en extraire les rangées de la manière suivante:
$rows = $table->getElementsByTagName("tr"); // Get table rows
En récupérant le nombre de rangées extraits on peut passer d’une rangée à une autre dans une boucle:
for( $i = 0; $i <= $numRows; $i++ ) { // Loop through rows
$row = $rows->item($i); // Extract row element
// [...]
On fini ainsi par pouvoir accéder aux valeurs des cellules mêmes:
$cells = $row -> getElementsByTagName('td');
$numCells = $cells->length;
for( $j = 0; $j <= $numCells; $j++ ) {
$cell = $cells->item($j); // Extract cell element
if( isset( $cell ) ){
echo $cell->nodeValue;
// [...]
Si certains contenus de cellules servent de pseudo-étiquettes vous pouvez utiliser un switch pour répérer les valeurs et une astuce du genre comme ci-dessous pour sauter des cellules:
case "First name":
// Skip to next TD
$j++;
$cell = $cells->item($j);
$nextValue = $cell->nodeValue;
$firstName = $nextValue;
break;
// [...]
}
Ceci résume une approche qui pourrais faire gagner beaucoup de temps et permettre de profiter d’un travail déjà effectué par un encodeur. Je vous laisse avec ces fonctions utilitaires qui pourraient servir en plus d’un trim() pour nettoyer le texte. Bonne extraction .
static function stripNonASCII( $string ){
return preg_replace("/[^(\x20-\x7F)]*/", "", $string );
}
// Réduire les espaces multiples
static function replaceMultipleSpaces( $string ){
return preg_replace('!\s+!', ' ', $string );
}
// Retirer les apostrophes
static function stripApostrophes( $string ){
return str_replace("'", "", $string );
}
// Enlever les backslash
static function stripBackSlashes( $string ){
return str_replace("\\", "", $string );
}
– James Poulson.
Ping : Recap java, semaine 27, année 2012 | Blog de la rubrique java
Ca marche nickel.
Encore merci.
Merci pour votre diligence.
Je teste tout ça.
Bonjour FredGuile,
Content d’entendre que l’article t’a rendu service. Concernant le problème évoqué cela vient probablement de la manière dont nodeValue fonctionne (extraction de texte).
Pour contourner cela ce que tu peut essayer c’est un troisième boucle en récupérant tous les sous-éléments:
cellElements = $cell->getElementsByTagName(« * ») // Wildcard
La valeurs d’attributs tel que href pourrait être extrait de la manière suivante (code non testé):
$cellElement = $cellElements->item($k);
if ($cellElement->hasAttributes()) {
foreach ($cellElement->attributes as $attr) {
$name = $attr->nodeName;
$value = $attr->nodeValue;
echo « Attribute ‘$name’ :: ‘$value’
« ;
}
}
Il y a aussi la possibilité de changer la requête XPath en amont mais on perd la rangée en tant que repère.
Voici quelques pages de documentation en espérant que cela a pu t’avancer .
http://php.net/manual/fr/domelement.getelementsbytagname.php
http://www.php.net/manual/fr/class.domnodelist.php
http://php.net/manual/fr/class.domnode.php
http://www.w3schools.com/xpath/xpath_operators.asp
http://www.w3schools.com/xpath/xpath_syntax.asp
pardon, ma ligne d’exemple est interprétée en html, j’enlève les balises
td a href=’http://monlien’ mon texte a td
Bonjour et merci pour cet article qui m’a bien aidé.
Il me reste cependant un dernier problème que je n’arrive pas à résoudre :
une de mes cellules contient, en plus du texte, un lien
mon texte
Hors, $cell->nodeValue ne me renvoi que le texte et pas le contenu complet de la cellule.
Auriez-vous une solution ?
Merci d’avance.