<jupyter_start><jupyter_text>ParseursDans ce notebook nous utiliserons le parseur [lxml](http://lxml.de/) qui est un binding de libxml2 et [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/)   Parser de l'htmlBeautiful Soup nous permet de parser simplement du contenu html. Même si le contenu est mal formé, le module bs reconstitue un arbre et offre des fonctions faciles à utiliser pour parcourir l'arbre ou y rechercher des éléments.  Beautiful Soup n'est pas un parseur, il utilise les parseurs et offre une API simplifiée à ses utilisateurs. Nous travaillerons directement avec du contenu en ligne. Fini les exercices bidons, cette fois nous allons nous confronter à une question essentielle : combien d'accordages *open tuning* <NAME> utilise et comment sont-ils répartis dans son oeuvre ?  On trouve les infos sur les chansons de <NAME> et les accordages sur le fabuleux site [songx.se](http://songx.se/index.php) (le site ayant changé d'interface, nous utiliserons une archive de [wayback archive](http://web.archive.org/)) Avec le module `requests` [que nous avons déjà utilisé](./python-4.ipynb), nous allons pouvoir instancier un objet Beautiful Soup sans trop d'efforts<jupyter_code>from urllib.request import urlopen
from bs4 import BeautifulSoup

url = "http://web.archive.org/web/20180430090903/http://songx.se/index.php"
html = urlopen(url)
soup = BeautifulSoup(html.read(), 'lxml')<jupyter_output><empty_output><jupyter_text>Voilà nous avons maintenant un objet `soup` de classe [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/beautifulsoup).  La [doc](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) est très claire.<jupyter_code># je cherche l'élement avec le tag 'title'
print(soup.title)
# le tag de l'élément
print(soup.title.name)
# le contenu textuel de l'élément
print(soup.title.string)<jupyter_output><title>SongX.se - The Neil Young Songbook</title>
title
SongX.se - The Neil Young Songbook<jupyter_text>Les informations qui nous intéressent sont contenues dans des éléments comme celui-ci (formatté pour la lisibilité) :  ```html    Clementine    (cover)    EADGBE```Où on trouve 1. le nom de la chanson ('Clementine') et l'accord utilisé ('EADGBE') Affichez les titres et les tunings des 10 premières chansons en utilisant la méthode [find_all](https://www.crummy.com/software/BeautifulSoup/bs4/doc/find-all). La méthode renvoie un iterable.<jupyter_code>for item in soup.find_all('div', attrs={'class':'songrow'})[:10]:
    print(item.a.string, item.div.string)
# on peut aussi utiliser la notation suivante
#for item in soup.find_all('div', class_="songrow"):<jupyter_output>1956 Bubblegum Disaster EADGBE
A Dream That Can Last EADGBE
A Man Needs A Maid EADGBE
A Man Needs A Maid/Heart Of Gold (Suite) EADGBE
A New Day For Love EADGBE
A Rock Star Bucks A Coffee Shop EADGBE
Act Of Love EADGBE
After Berlin DADGBD
After The Garden EADGBE
After The Gold Rush EADGBE<jupyter_text>Créez un `dict` appelé `tunings` qui classe les chansons par tuning<jupyter_code># notre structure de données résultat
# un dictionnaire avec en clé l'accordage et en valeur la liste des chansons qui utilisent cet accordage
from collections import defaultdict
tunings = defaultdict(list)
for item in soup.find_all('div', attrs={'class':'songrow'}):
    song_title = item.a.string
    tuning = item.div.string
    tunings[tuning].append(song_title)
tunings = dict(tunings)<jupyter_output><empty_output><jupyter_text>'Harvest Moon' utilise l'accordage DADGBE, y en a-t'il d'autres ?<jupyter_code>list(tunings.keys())<jupyter_output><empty_output><jupyter_text>Affichez les accordages et le nombre de chansons pour chaque accordage, le tout trié par nombre de chansons décroissant. Pour cela, cous utiliserez la méthode `sorted` ainsi que l'argument mot-clé `key` (des exemples [ici](https://wiki.python.org/moin/HowTo/SortingKey_Functions)) :<jupyter_code>n_songs = ((tuning, len(songs)) for tuning, songs in tunings.items())
for tuning, n in sorted(n_songs, key=lambda x: x[1], reverse=True):
    print("{}: {}".format(tuning, n))<jupyter_output>EADGBE: 475
DADGBD: 30
DADGBE: 23
DGCFAD: 23
CGCFAC: 2
CGCFAD: 2
EbAbDbGbBb: 1
BbFBbEbGBb: 1
DGCFAA: 1<jupyter_text>Allez hop un histogramme<jupyter_code>%matplotlib notebook
import matplotlib.pyplot as plt
values = [len(tunings[x]) for x in tunings]
values
plt.bar(range(0, len(values)), values)
plt.xticks(range(0, len(values)), tunings.keys(), rotation=17)

plt.show()<jupyter_output><empty_output><jupyter_text>Parser de l'xml Nous allons travailler sur un fichier au format [TEI](http://www.tei-c.org/) extrait du corpus *Corpus 14*    PRAXILING - UMR 5267 (PRAXILING) (2014). Corpus 14 [Corpus]. ORTOLANG (Open Resources and TOols for LANGuage) - www.ortolang.fr, https://hdl.handle.net/11403/corpus14/v1.   Le fichier se nomme ``josephine-1-150119.xml``. Il s'agit d'une lettre d'une femme de soldat à son époux.  Nous allons extraire du fichier TEI les informations suivantes :  - titre (``/TEI/teiHeader/fileDesc/titleStmt/title``)- source (``/TEI/teiHeader/fileDesc/sourceDesc/p``)- contenu de la lettre (``/TEI/text/body``)Vous pouvez trouver des indications sur les éléments de la TEI [ici](http://www.tei-c.org/release/doc/tei-p5-doc/fr/html/) (ça pourra être utile pour les questions)  Avec lxml Pourquoi `lxml` et pas `xml.etree.ElementTree` ? Parce que : [1](http://lxml.de/intro.html) et surtout [2](http://lxml.de/performance.html)  La bonne nouvelle c'est que votre code sera aussi compatible avec `xml.etree.ElementTree` ou `xml.etree.cElementTree` parce que xml utilise l'API ElementTree. Sauf pour la méthode `xpath` qui est propre à `libxml`.<jupyter_code>from lxml import etree
tree = etree.parse('josephine-1-150119.xml')
root = tree.getroot()

# Parcours des enfants de la racine (commentaires et éléments)
for child in root:
    print(child.tag)<jupyter_output><cyfunction Comment at 0x7f57c823aa58>
{http://www.tei-c.org/ns/1.0}teiHeader
<cyfunction Comment at 0x7f57c823aa58>
{http://www.tei-c.org/ns/1.0}facsimile
{http://www.tei-c.org/ns/1.0}text<jupyter_text>Avec des requêtes ElementPath Le fichier utilise l'[espace de nom](https://fr.wikipedia.org/wiki/Espace_de_noms_XML) TEI : ````, nous devrons l'indiquer dans nos instructions de recherche.    Nous pouvons récupérer un élément particulier qui correspond à un chemin : par exemple, pour récupérer le header TEI de dont le chemin est `/TEI/teiHeader`<jupyter_code># la méthode find renvoie le premier élément qui correspond au chemin argument (ElementPath et non Xpath)
header = root.find("./tei:teiHeader", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print(header)<jupyter_output><Element {http://www.tei-c.org/ns/1.0}teiHeader at 0x7f579fe20048><jupyter_text>Récupérez le titre et affichez son tag XML ainsi que son contenu textuel (`/TEI/teiHeader/fileDesc/titleStmt/title`)<jupyter_code># la méthode find renvoie le premier élément qui correspond au chemin argument (ElementPath et non Xpath)
title = root.find("./tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print("Tag : {}".format(title.tag))
print("Texte : {}".format(title.text))<jupyter_output>Tag : {http://www.tei-c.org/ns/1.0}title
Texte : <NAME> à son époux le 19-01-1915 depuis Baillargues<jupyter_text>Idem pour la source (élément `sourceDesc`) :<jupyter_code>source = root.find("./tei:teiHeader/tei:fileDesc/tei:sourceDesc/tei:p", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
print("Tag : {}".format(source.tag))
print("Texte : {}".format(source.text))<jupyter_output>Tag : {http://www.tei-c.org/ns/1.0}p
Texte : Correspondance de <NAME>, numérisée par les Archives Départementales de l'Hérault.<jupyter_text>Avec des requêtes xpath lxml a aussi une méthode [xpath](https://lxml.de/xpathxslt.html) qui permet d'utiliser directement des [expressions xpath](https://www.w3schools.com/xml/xpath_syntax.asp) (sans oublier les espaces de noms pour notre fichier) :<jupyter_code>source = root.xpath("/tei:TEI/tei:teiHeader/tei:fileDesc/tei:sourceDesc/tei:p", namespaces={'tei':'http://www.tei-c.org/ns/1.0'})
print(type(source)) #xpath retourne une liste
print(source[0].text)
#ou bien
source = root.xpath("/tei:TEI/tei:teiHeader/tei:fileDesc/tei:sourceDesc/tei:p/text()", namespaces={'tei':'http://www.tei-c.org/ns/1.0'})
print(source[0])<jupyter_output><class 'list'>
Correspondance de <NAME>, numérisée par les Archives Départementales de l'Hérault.
Correspondance de <NAME>, numérisée par les Archives Départementales de l'Hérault.<jupyter_text>Pour le contenu il faut ruser. La difficulté ici tient à l'utilisation d'élements `` de type [milestones](http://www.tei-c.org/release/doc/tei-p5-doc/fr/html/CO.htmlCORS5) pour noter les retours à la ligne :  ```xmlje reponse a ton aimableux lettresque nous a fait plaisir en naprenasque tu et enbonne santes car ilanais de maime pour nous``` Récupérez dans un premier temps l'ensemble des balises `` en utilisant la méthode [findall](http://effbot.org/zone/element.htmsearching-for-subelements). la méthode `findall` renvoie une liste avec tous les éléments correspondant au chemin argument.<jupyter_code>body = root.findall("./tei:text/tei:body/tei:p", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
for elem in body: # tout le texte ne s'affichera pas, c'est normal !
    print(elem.text)<jupyter_output>cher Laurent

je reponse a ton aimableux lettres

cher Laurent je repons a la cartes<jupyter_text>Ici on ne récupère que les noeuds text précédant les éléments ``.Utilisez la fonction `xpath` pour récupérer tous les noeuds text du corps de la lettre. Vous intégrerez dans votre requête la fonction `text` (vue un peu plus haut) dans votre chemin xpath (vous pouvez _aussi_ fouiller [par ici](https://lxml.de/xpathxslt.html) pour avoir de la documentation supplémentaire).<jupyter_code>body = root.xpath("//tei:text/tei:body//text()", namespaces={'tei':"http://www.tei-c.org/ns/1.0"})
for text in body:
    print(text, end="")<jupyter_output>Baillargues Le 19 janvier 1915


cher Laurent


je reponse a ton aimableux lettres
que nous a fait plaisir en naprenas
que tu et enbonne santes car il
anais de maime pour nous


cher Laurent je repons a la cartes
de ma mère quelles et venue au
jourdhui pour de faire partire
partir un paquet quil aillae
les chosette sausice chocolas une
paire de chosette pour Louis
je pense que vous magerè ensenbleus
tu feras repons a la maison te
suite que tu rese vras le paquet
je te dirais que ten le midi il
fait frois il fait du vent glasais
et toi au pas de calais tu nous
dit quil pleus mai tu nous parles
pas si tu a ases pour te garendir
du froit cil te maque quelles chose

tu nas que ledire quon de len verras
verras tu nous dit que charles ta
Ecrie et ta soeux et ta dit que
je lui et envoiez la photot
plurien a te dire pour le moment
que de ten voiez une grose
carriese de tous et boutounase
de ton petit enge 
ador<NAME> encorre une foi
te plui Milles baisées te tous
ta fenme pour la vie
Josep[...]<jupyter_text>avec DOM L'API `ElementTree` est propre à Python, `DOM` est une API indépendante d'un langage de programmation. Il existe des implémentations `DOM` dans la plupart des langages de programmation modernes.<jupyter_code>from xml.dom import minidom
dom = minidom.parse("josephine-1-150119.xml")
# l'objet Document
dom
title = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'title')[0] # un seul élément 'title' dans le document
print(title) # title est un objet Element, pour accèder au contenu textuel il faut récupérer le noeud texte
print(title.lastChild.nodeName)
print(title.lastChild.nodeValue)<jupyter_output><DOM Element: title at 0x7f579fe273d8>
#text
<NAME> à son époux le 19-01-1915 depuis Baillargues<jupyter_text>idem pour la source, sauf qu'on ne peut pas se permettre de rechercher tous les éléments `p`.  Il faut trouver l'élément `p` fils de `sourceDesc`<jupyter_code>sourceDesc = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'sourceDesc')[0]
for node in sourceDesc.childNodes:
    if node.localName == "p":
        print(node.lastChild.nodeValue)<jupyter_output>Correspondance de <NAME>, numérisée par les Archives Départementales de l'Hérault.<jupyter_text>Et maintenant le contenu et ses éléments milestones.  Pour garder la forme vous réécrirez les boucles `for` suivies de `if` en listes en intension.<jupyter_code>body = dom.getElementsByTagNameNS("http://www.tei-c.org/ns/1.0", 'body')[0]
for node in body.childNodes:
    if node.localName == "p" or "opener":
        for in_node in node.childNodes:
            if in_node.nodeName == "#text":
                print(in_node.nodeValue, end="")<jupyter_output>Baillargues Le 19 janvier 1915

cher Laurent

je reponse a ton aimableux lettres
que nous a fait plaisir en naprenas
que tu et enbonne santes car il
anais de maime pour nous

cher Laurent je repons a la cartes
de ma mère quelles et venue au
jourdhui pour de faire 
partir un paquet quil 
les chosette sausice chocolas une
paire de chosette pour Louis
je pense que vous magerè ensenbleus
tu feras repons a la maison te
suite que tu rese vras le paquet
je te dirais que ten le midi il
fait frois il fait du vent glasais
et toi au pas de calais tu nous
dit quil pleus mai tu nous parles
pas si tu a ases pour te garendir
du froit cil te maque quelles chose

tu nas que ledire quon de len 
verras tu nous dit que charles ta
Ecrie et ta soeux et ta dit que
je lui et envoiez la photot
plurien a te dire pour le moment
que de ten voiez une grose
carriese de tous et boutounase
de ton petit enge 
adorè Albert encorre une foi
te plui Milles baisées te tous
ta fenme pour la vie
<NAME>
bien le bo[...]
{'fraction_non_alphanumeric': 0.103327239488117, 'fraction_numerical': 0.014405850091407678, 'mean_word_length': 5.108083966056275, 'pattern_counts': {'":': 3, '<': 73, '<?xml version=': 0, '>': 73, 'https://': 10, 'lorem ipsum': 0, 'www.': 25, 'xml': 24}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '453686', 'license': 'MIT', 'n_tokens_mistral': 5110, 'n_tokens_neox': 4973, 'n_words': 1570, 'path': 'corrections/parseurs-complet.ipynb', 'repo_name': 'Shuai-GAO/python-im-2', 'size': '13762'}