Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 11/02/2020, à 23:14

alex2423

[SED] Transformer un fichier HTML en CSV

Bonjour,

J'ai un outil qui me sort un résultat dans un tableau HTML. Autant dire que c'est difficilement exploitable.
Je souhaiterai donc transformer le tableau HTML en un fichier .CSV.


J'ai à peu près ce type de structure pour donner comme exemple :

xbionic@bionic:~/Documents$ cat test_sed 
<table>
<tr><td></td><td>Properties</td>
<tr><td>SI_NAME</td><td>nom1</td>
<tr><td>SI_ADRESSE</td><td>adresse1</td>
<tr><td>SI_MAIL</td><td>mail1</td>
<tr><td>SI_LICENCE</td><td>yes1</td>
<tr><td></td><td>Properties</td>
<tr><td>SI_NAME</td><td>nom2</td>
<tr><td>SI_ADRESSE</td><td>adresse2</td>
<tr><td>SI_MAIL</td><td>mail2</td>
<tr><td>SI_LICENCE</td><td>yes2</td>

Dans le tableau, je souhaiterai uniquement récupérer les valeurs de SI_NAME et SI_LICENCE. Cela donnerait ainsi :

nom1;yes1
nom2;yes2

Pour me simplifier la vie, je pensais dans un premier temps sélectionner toutes les lignes entière contenant soit SI_NAME ou SI_LICENSE

xbionic@bionic:~/Documents$ cat test_sed | sed -nr '/SI_NAME|SI_LICENCE/p'
<tr><td>SI_NAME</td><td>nom1</td>
<tr><td>SI_LICENCE</td><td>yes1</td>
<tr><td>SI_NAME</td><td>nom2</td>
<tr><td>SI_LICENCE</td><td>yes2</td>

Une fois les résultats filtrés par sed, j'aurai bien voulu enchainer avec un autre pattern sans passer par un fichier intermédiaire ou un pipé (plus à but pédagogique que de perf). 
J'ai essayer en rajoutant l'option -e mais ne cela ne fonctionne pas en faisant quelques tests
.

xbionic@bionic:~/Documents$ cat test_sed | sed -nr '/SI_NAME|SI_LICENCE/p' -e '/s/tr/toto/g'
sed: can't find label for jump to `r/TT/g'
xbionic@bionic:~/Documents$ cat test_sed | sed -nre '/SI_NAME|SI_LICENCE/p' -e '/s/tr/toto/g'
sed: can't find label for jump to `r/TT/g'

xbionic@bionic:~/Documents$ cat test_sed | sed -nr '/SI_NAME|SI_LICENCE/p' > test_sed_part2

Ma question est :
Comment puis je enchaîner les patterns sed sans passer par un fichier intermédiaire ou un pipe | ?



Je pense avoir utiliser la bonne méthode jusqu'à maintenant.
Et à l'étape suivante, je dois récupérer les valeurs de la 2ème colonne (nom1, yes1, nom2, yes2).

Mais je bloque à cette étape.
Je cherche en utilisant le parttern suivant <td>([a_z0-9]+)<\/td> mais sed me renvoit tout le résultat.

xbionic@bionic:~/Documents$ cat test_sed_part2
<tr><td>SI_NAME</td><td>nom1</td>
<tr><td>SI_LICENCE</td><td>yes1</td>
<tr><td>SI_NAME</td><td>nom2</td>
<tr><td>SI_LICENCE</td><td>yes2</td>
xbionic@bionic:~/Documents$ cat test_sed_part2 | sed -r 's/<td>([a_z0-9]+)<\/td>/\1/'
<tr><td>SI_NAME</td><td>nom1</td>
<tr><td>SI_LICENCE</td><td>yes1</td>
<tr><td>SI_NAME</td><td>nom2</td>
<tr><td>SI_LICENCE</td><td>yes2</td>

Ma questions est :
Comment puis je extraire les valeurs ?

Dernière modification par alex2423 (Le 11/02/2020, à 23:17)

Hors ligne

#2 Le 12/02/2020, à 00:48

Watael

Re : [SED] Transformer un fichier HTML en CSV

salut,

je suis toujours embêter quand on me demande de traiter un langage à balise comme le HTML,
parce qu'il ne respecte aucune forme.
là, il s'agit d'un tableau qui peut être formater comme tel :

$ html2text <(tidy /tmp/text.html 2>/dev/null ) | sed -n '/^SI_\(NAME\|LICENCE\)/s/[[:blank:]]\+|[[:blank:]]\+/;/p'
SI_NAME;nom1  
SI_LICENCE;yes1  
SI_NAME;nom2  
SI_LICENCE;yes2

mais je ne traiterai pas le HTML directement.


Connected \o/
Welcome to sHell. · eval is evil.

Hors ligne

#3 Le 12/02/2020, à 09:55

pingouinux

Re : [SED] Transformer un fichier HTML en CSV

Bonjour,
Si tu es sûr de la structure de ton fichier, mais ce n'est pas très joli…

$ sed -nr '/SI_NAME|SI_LICENCE/s#<tr><td>.*</td><td>(.*)</td>#\1#p' test_sed | paste -d: - -
nom1:yes1
nom2:yes2

Hors ligne

#4 Le 12/02/2020, à 13:07

kamaris

Re : [SED] Transformer un fichier HTML en CSV

Autre moyen, en supposant un minimum de choses sur la structure du fichier, mais ça n'est pas pour autant une bonne idée :

$ sed -zE 's/(SI_NAME|SI_LICENCE)(<[^>]+>)+([^<]+<)/\1\x00\3/g; s/[^\x00]*\x00([^<]+)[^\x00]*\x00([^<]+)[^\x00]*/\1:\2\n/g' fichier
nom1:yes1
nom2:yes2
$ 

Dernière modification par kamaris (Le 12/02/2020, à 13:13)

Hors ligne

#5 Le 12/02/2020, à 13:30

alex2423

Re : [SED] Transformer un fichier HTML en CSV

@Watael, la commande html2text. J'ai testé au boulot. Nous sommes en Red Hat 7.4 (et 7.6).
Mais dans tout les cas, ne connaissant pas bien "sed", c'est l'occasion de le pratiquer. La struture du tableau ne va pas changer pour l'outil.
Jusqu'à maintenant, je transformais le fichier sous Notpad++ à coup de rechercher/remplacer (Ctrl + H) + 1 macro parce que j'utilise rarement les données. Mais cette fois-ci, je me suis dit pourquoi pas essayer de faire quelque chose de plus propre d'autant plus que je ne connais pas sed.

@pingouinux, la structure est bien sur approximative, mais le principal est là, avec des balises table, tr et td et quelque argument que j'ai omis comme par exemple des valign='2'

Ta commande est magique qui fait 2 en 1.
Avec cette partie de ta commande (avant le substitue "s") : /SI_NAME|SI_LICENCE/s, tu recherches des lignes à appliquer les changements par sed. J'avais vu entre aperçu, sans trop savoir exploiter cette fonction. Un grand merci à toi de m'avoir fait découvrir un des potentiels énormes de sed.   


Ton pattern en définissant toute la ligne fonctionne à merveille : #<tr><td>.*</td><td>(.*)</td>#. Je suis surpris que ce soit obligatoire de le faire. Je n'ai pas utilisé les opérateurs de début de ligne "^" et fin de ligne "$"


J'ai essayé par exemple de chercher tout ce qui se trouve entre les balises "td"

xbionic@bionic:~/Documents$ sed -nr '/SI_NAME|SI_LICENCE/s#<td>(.*)</td>#\1#p' test_sed
<tr>SI_NAME</td><td>nom1
<tr>SI_LICENCE</td><td>yes1
<tr>SI_NAME</td><td>nom2
<tr>SI_LICENCE</td><td>yes2

En restrayant uniquement sur de l'alphanumérique en minuscule, cela ne fonctionne toujours pas.

xbionic@bionic:~/Documents$ sed -nr '/SI_NAME|SI_LICENCE/s#<td>([a-z0-9]+)</td>#\1#p' test_sed
<tr><td>SI_NAME</td>nom1
<tr><td>SI_LICENCE</td>yes1
<tr><td>SI_NAME</td>nom2
<tr><td>SI_LICENCE</td>yes2

Mais sans succès.
Résultat avec "sed", il faut à chaque fois, définir (patterner) la ligne complète. Est ce que tu peux me le confirmer?

Hors ligne

#6 Le 12/02/2020, à 13:40

pingouinux

Re : [SED] Transformer un fichier HTML en CSV

alex2423 #5 a écrit :

Résultat avec "sed", il faut à chaque fois, définir (patterner) la ligne complète. Est ce que tu peux me le confirmer?

Non, mais j'ai fait au plus simple. Pour quelque chose de plus sophistiqué, voir la réponse de kamaris #4.

Hors ligne

#7 Le 13/02/2020, à 11:32

pingouinux

Re : [SED] Transformer un fichier HTML en CSV

Voici deux autres façons de faire :

sed -nr 's/.*>(SI_NAME|SI_LICENCE)<.*>(.*)<.*/\2/p' test_sed | paste -d: - -
grep -Po '(SI_NAME|SI_LICENCE).*>\K(.*)(?=<)' test_sed | paste -d: - -

Hors ligne

#8 Le 15/02/2020, à 01:33

alex2423

Re : [SED] Transformer un fichier HTML en CSV

Merci beaucoup pour vos différentes solutions mais j’ai du mal à comprendre la construction de vos commandes.

Kamaris, j’ai décortiqué ta commande mais je bloque sur pas mal de points. Est ce que tu pourrais m’aider à comprendre ?

Je détail de ce que j’ai compris des différents groupes :

Premier pattern
-  (SI_NAME|SI_LICENCE) : début de la recherche à partir de la chaine de caractère SI_NAME ou SI_LICENCE
- (<[^>]+>) : une balise html commancant par < et se terminant par > (dans l’exemple <td></td>
(<[^>]+>)+ : le + pour signifier de 1 à n balise HTML. Pour un groupe, la dernière balise (dans l’exemple : </td>)
- ([^<]+<) : 1 à n caractère sauf < puis < (dans l’exemple « nom1< »)

/\1\x00\3/
Les lignes SI_NAME ou SI_LICENCE du fichier source sont remplacées par
SI_NAME\x00nom1<
SI_LICENCE\x00yes1<
SI_NAME\x00nom2<
SI_LICENCE\x00yes2<

Second pattern
Je détaille le second partern : [^\x00]*\x00([^<]+)[^\x00]*\x00([^<]+)[^\x00]*
[^\x00]* => 0 à n caractères sauf \x00 => correspond à SI_NAME ou SI_LICENCE
\x00  => le caractère \x00
([^<]+) => 0 à n caractères sauf < (groupe 1) => correspond à yes1 sans le <.
\x00  => le caractère \x00
([^<]+) => 0 à n caractères sauf < (groupe 2)

Enfin bon, comme tu peux le remarquer mon décryptage est bien imprécis. 
D’après la doc le caractère un peu spécial \x00 s’utilise avec l’option « -z » et s’aparrante à un retour chariot. 
Dans le premier pattern, on remplace la chaîne par un \x00 (avec 2 champs qui l'entoure) et dans le 2ème pattern, nous avons 3 caractères \x00 ! Pourquoi donc?
Pourrais tu stp me corriger dans mes affirmations et détailler certains points ?

Hors ligne

#9 Le 15/02/2020, à 03:48

kamaris

Re : [SED] Transformer un fichier HTML en CSV

Commençons par les corrections où c'est nécessaire :

alex2423 a écrit :

Premier pattern
-  (SI_NAME|SI_LICENCE) : début de la recherche à partir de la chaine de caractère SI_NAME ou SI_LICENCE
- (<[^>]+>) : une balise html commancant par < et se terminant par > (dans l’exemple <td></td>

Dans l'exemple, <td> ou </td>.

alex2423 a écrit :

(<[^>]+>)+ : le + pour signifier de 1 à n balise HTML. Pour un groupe, la dernière balise (dans l’exemple : </td>)

Je ne comprends pas bien ce que tu veux dire par « Pour un groupe, la dernière balise », mais ce motif correspond dans l'exemple à <td></td> :
- le motif précédent, sans les parenthèses et le « + », correspondait à n'importe quelle balise html (en particulier <td> ou </td>) ;
- avec les parenthèses et le « + », il correspond à n'importe quelle suite de une ou plusieurs balises html (en particulier <td></td>).

alex2423 a écrit :

- ([^<]+<) : 1 à n caractère sauf < puis < (dans l’exemple « nom1< »)

/\1\x00\3/
Les lignes SI_NAME ou SI_LICENCE du fichier source sont remplacées par
SI_NAME\x00nom1<
SI_LICENCE\x00yes1<
SI_NAME\x00nom2<
SI_LICENCE\x00yes2<

Ok

alex2423 a écrit :

Second pattern
Je détaille le second partern : [^\x00]*\x00([^<]+)[^\x00]*\x00([^<]+)[^\x00]*
[^\x00]* => 0 à n caractères sauf \x00 => correspond à SI_NAME ou SI_LICENCE

Non, ça correspond à bien plus que ça : à n'importe quelle suite de zéro ou plusieurs caractères différents du caractère nul (\x00). Donc dans l'exemple, à tout ce qui précède un caractère nul introduit précédemment.
Par exemple, pour le premier caractère nul, ça correspond à :

<table>
<tr><td></td><td>Properties</td>
<tr><td>SI_NAME

avec les sauts de ligne inclus (qui sont bien des caractères différents de \x00).

alex2423 a écrit :

\x00  => le caractère \x00
([^<]+) => 0 à n caractères sauf < (groupe 1) => correspond à yes1 sans le <.
\x00  => le caractère \x00
([^<]+) => 0 à n caractères sauf < (groupe 2)

Ok

Maintenant quelques mots sur l'approche générale : le problème du html ou du xml, ou des langages à balises d'une manière générale, c'est que leurs délimiteurs sont précisément les balises, et non les sauts de ligne, comme c'est le cas pour sed.
C'est la raison pour laquelle sed n'est pas fait pour parser ce type de fichiers, et c'est la raison pour laquelle je disais dans mon post que ma proposition, bien qu'elle suppose peu de choses sur la structure du fichier, n'était pas une bonne idée pour autant.

Mais si on veut malgré tout utiliser sed, une manière de faire est d'ignorer tout d'abord les sauts de ligne, en utilisant l'option -z, qui prend comme délimiteur de lignes le caractère nul.
Comme il n'y a pas de caractère nul dans le fichier (qui est un fichier texte), sed va considérer tout son contenu comme une seule chaine de caractères.
Mais le problème, c'est qu'à partir de là, étant donné les expressions régulières auxquelles on a droit dans sed, on ne va pas pouvoir effectuer des recherches un tant soit peu compliquées en une seule passe : il va falloir poser des repères lors d'une première passe, et s'appuyer dessus lors d'une seconde passe.
C'est cela que je fais dans la commande donnée plus haut : le premier s/// repère les motifs d'intérêt et les fait précéder d'un caractère nul, puis le second s/// s'appuie sur ces repères pour supprimer tout le reste.

alex2423 a écrit :

D’après la doc le caractère un peu spécial \x00 s’utilise avec l’option « -z » et s’aparrante à un retour chariot.

Oui, il joue le rôle d'un retour charriot pour l'option -z, à condition d'être déjà dans le fichier lorsque sed l'ouvre (avant de commencer le programme sed).
De la même façon, sans l'option -z, si tu rajoutes des sauts de ligne dans une ligne en cours de traitement, sed ne va pas considérer ces sauts de ligne comme des délimiteurs, mais comme de simples caractères.
Le fait que j'utilise le caractère nul comme repère ici est une coïncidence : je l'utilise parce que c'est typiquement le caractère qu'on ne doit pas rencontrer dans un fichier texte, mais j'aurais pu en prendre un autre « du même type », comme \x02.

alex2423 a écrit :

Dans le premier pattern, on remplace la chaîne par un \x00 (avec 2 champs qui l'entoure) et dans le 2ème pattern, nous avons 3 caractères \x00 ! Pourquoi donc?

Dans le premier pattern, on introduit un caractère nul avant chaque occurrence d'un motif d'intérêt (par le « g » à la fin de la commande s///).
Dans le second pattern, on vient :
- sélectionner ce qui se trouve avant un premier motif (motif de nom) pour suppression (au sens de non conservation): [^\x00]*\x00 ;
- sélectionner ce motif de nom ([^<]+) pour conservation ;
- sélectionner ce qui se trouve avant un second motif (motif de licence) pour suppression : [^\x00]*\x00 ;
- sélectionner ce motif de licence ([^<]+) pour conservation ;
- sélectionner ce qui se trouve après ce motif de licence pour suppression : [^\x00]*\x00 ;

Là aussi, on fait cela autant de fois que cette suite de motifs est rencontrée, par le « g » en fin de commande s///.
La première sélection pour suppression ne sert en fait que pour le début du fichier, avant le premier motif de nom.
Ensuite, c'est la troisième sélection pour suppression qui se charge de supprimer ce qui se trouve avant un nouveau motif de nom.
Cette troisième sélection est nécessaire pour bien supprimer ce qui se trouve en fin de fichier, après le dernier motif de licence.

OUF !! big_smile

Hors ligne