OVH Cloud OVH Cloud

ContentHandler+SAX

4 réponses
Avatar
remi
Bonjour,

Comme d'habitude ;-), je souhaite traiter un fichier XML du type suivant :
#################
<section id="1" name="section 1" description="phrase courte pour la
section 1">

<sousSection id="1" dansLaSection="1" thema="subsection 1.1">
<texte>Texte de la soussection 1.1</texte>
</sousSection>

<sousSection id="2" dansLaSection="1" thema="subsection 1.2">
<texte>Texte de la soussection 1.2 qui se fini par la section 2</texte>
</sousSection>

</section>
##################
J'ai écrit, avec beaucoup d'aide de la part des contributeurs de fclp,
le ContentHandler suivant :

###############
class autreHandler(ContentHandler):
def startDocument(self):
self._stack = []
self._in_text = False

def startElement(self, name, attr):
self._in_text = (name == 'texte')
if name == 'section':
print "###SECTION d'id :", attr['id'], ", le nom de la section
:", attr['name'], "\n"
self._stack.append(attr['id']) # stocke l'id de la section
if name == 'sousSection':
self._stack.append(name) # stocke le nom de la sous-section
self._stack.append(attr['id']) # stocke l'id de la sous-section
print "*l'id de la sousSection est:", attr['id'], "\n"

def characters(self, text):
if self._in_text:
print "texte :", text
print "On est dans la section : ", self._stack[-3], ", Le numéro
d'ordre de sous-section est :", self._stack[-1],"\n"

def endElement(self, name):
if name == 'texte':
self._in_text = False
##############
J'arrive avec ça à accéder aux éléments nécessaires à mon traitement (en
remplaçant print par d'autres commandes).
Je remercie encore les contibuteurs de fclp ! :-)
Si vous avez des suggestions pour améliorer ce code disgracieux, je suis
preneur... pas très joli les "self._stack[-3]" ;-)
Merci.
Rémi.

4 réponses

Avatar
bruno modulix
remi wrote:
Bonjour,

Comme d'habitude ;-), je souhaite traiter un fichier XML du type suivant :
#################
<section id="1" name="section 1" description="phrase courte pour la
section 1">

<sousSection id="1" dansLaSection="1" thema="subsection 1.1">
<texte>Texte de la soussection 1.1</texte>


Question idiote : ça te sert vraiment à quelquechose, la balise 'texte'?

</sousSection>

<sousSection id="2" dansLaSection="1" thema="subsection 1.2">
<texte>Texte de la soussection 1.2 qui se fini par la section 2</texte>
</sousSection>

</section>
##################
J'ai écrit, avec beaucoup d'aide de la part des contributeurs de fclp,
le ContentHandler suivant :

###############
class autreHandler(ContentHandler):
def startDocument(self):
self._stack = []
self._in_text = False


Tu a vraiment besoin des deux ? Il s'agit de deux façons différentes de
conserver des indications sur le contexte en cours, la solution avec le
flag étant pour les cas simplistes (ici : savoir si on est dans une
balise texte ou non), celle avec la pile plus générale - à condition
d'être employée comme il se doit...

def startElement(self, name, attr):
self._in_text = (name == 'texte')
if name == 'section':
print "section id %s - nom %s" % (attr['id'], attr['name'])

self._stack.append(attr['id']) # stocke l'id de la section

Ca te sert à quoi d'empiler l'id sans savoir à quoi il correspond ?

if name == 'sousSection':
self._stack.append(name) # stocke le nom de la sous-section
self._stack.append(attr['id']) # stocke l'id de la sous-section
Et là, tu empile le nom *puis* l'id ???


J'ai comme l'impression que tu n'a pas vraiment capté le principe de la
pile d'appel. Elle est supposée permettre aux autres méthodes (et
notamment à character()) de savoir dans quel contexte on se trouve au
moment de l'appel - hors, vu comment tu l'utilise, il faut connaître le
contexte pour comprendre ce qu'il y a dans la pile !

print "*l'id de la sousSection est:", attr['id'], "n"



def characters(self, text):
if self._in_text:
print "texte :", text
print "On est dans la section : ", self._stack[-3], ", Le numéro
d'ordre de sous-section est :", self._stack[-1],"n"


Yuck.

def endElement(self, name):
if name == 'texte':
self._in_text = False


Et tu dépile quand ?

##############
J'arrive avec ça à accéder aux éléments nécessaires à mon traitement (en
remplaçant print par d'autres commandes).
Je remercie encore les contibuteurs de fclp ! :-)
Si vous avez des suggestions pour améliorer ce code disgracieux, je suis
preneur... pas très joli les "self._stack[-3]" ;-)


Non, en effet... !-)

Qu'est ce que tu dirais de ça :

# ---
from xml.sax.handler import ContentHandler
from xml.sax import make_parser

class MyHandler(ContentHandler):
def startDocument(self):
self._stack = []

def startElement(self, name, attr):
self._stack.append((name, attr))

def characters(self, text):
name, attr = self._stack[-1]
if name == "texte":
self._print_stack("here we are : ")
print "+ texte : %s" % text

def endElement(self, name):
self._stack.pop()

def _print_stack(self, msg='printing stack'):
print msg
for entry in self._stack:
name, attr = entry
infos = " : ".join(["%s = %s" % item
for item in attr.items()])
print "- %s :: %s" % (name, infos)


def main():
handler = MyHandler()
parser = make_parser()
parser.setContentHandler(handler)
datasource = open("out.xml","r")
parser.parse(datasource)
datasource.close()
# ---

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in ''.split('@')])"

Avatar
remi
Bonjour,


Question idiote : ça te sert vraiment à quelquechose, la balise 'texte'?


Euh... Non. C'est vrai. C'est un biais de débutant surement...
Le code :
<sousSection id="2" dansLaSection="1" thema="subsection 1.2">
Texte de la soussection 1.2</sousSection>
marche tout aussi bien... Bien vu !


###############
class autreHandler(ContentHandler):
def startDocument(self):
self._stack = []
self._in_text = False



Tu a vraiment besoin des deux ? Il s'agit de deux façons différentes de
conserver des indications sur le contexte en cours, la solution avec le
flag étant pour les cas simplistes (ici : savoir si on est dans une
balise texte ou non), celle avec la pile plus générale - à condition
d'être employée comme il se doit...

def startElement(self, name, attr):
self._in_text = (name == 'texte')
if name == 'section':


print "section id %s - nom %s" % (attr['id'], attr['name'])
self._stack.append(attr['id']) # stocke l'id de la section

Ca te sert à quoi d'empiler l'id sans savoir à quoi il correspond ?


Il correspond à l'id de la section en cours ici. On est après un "if
name == 'section':". C'est pas très propre, c'est vrai.

if name == 'sousSection':
self._stack.append(name) # stocke le nom de la sous-section
self._stack.append(attr['id']) # stocke l'id de la sous-section


Et là, tu empile le nom *puis* l'id ???


Oui, c'est pas très élégant. ;-)


J'ai comme l'impression que tu n'a pas vraiment capté le principe de la
pile d'appel.


C'est vrai. Je crois juste commencer à comprendre....


def endElement(self, name):
if name == 'texte':
self._in_text = False



Et tu dépile quand ?


Avec :
def endElement(self, name):
if name == 'texte':
self._in_text = False
self._stack.pop()

je me retrouve avec une erreur de liste vide : "IndexError: pop from
empty list"... Oups, c'est une erreur d'identation en fait ! :$
Il faudrait plutôt :
def endElement(self, name):
if name == 'texte':
self._in_text = False
self._stack.pop()
(je n'ai besoin du contexte que dans les balises texte)

Question idiote : à quoi sert de dépiler ? (hormis le fait que la pile
peut devenir très grosse pour de très gros document). On fait toujours
appel aux derniers élements de la liste.

Qu'est ce que tu dirais de ça :


Merci beaucoup ! Je vais tester ce code... Je reviens vers le forum après.
Merci encore pour ta patience ! :-)
@+
Rémi.


Avatar
bruno modulix
remi wrote:
Bonjour,


(snip)

def startElement(self, name, attr):
self._in_text = (name == 'texte')
if name == 'section':



print "section id %s - nom %s" % (attr['id'], attr['name'])
self._stack.append(attr['id']) # stocke l'id de la section

Ca te sert à quoi d'empiler l'id sans savoir à quoi il correspond ?


Il correspond à l'id de la section en cours ici.
On est après un "if name == 'section':".


Quand tu sera dans la methode 'characters', tu aura juste un id, sans
savoir à quoi il correspond... (section, sous-section, etc...)


if name == 'sousSection':
self._stack.append(name) # stocke le nom de la sous-section
self._stack.append(attr['id']) # stocke l'id de la sous-section



Et là, tu empile le nom *puis* l'id ???



Oui, c'est pas très élégant. ;-)


C'est surtout incohérent. Un coup tu n'empile qu'un id, sans savoir à
quoi il correspond, le coup d'après tu empile 2 infos pour une seule
balise... C'est inexploitable tel que.

J'ai comme l'impression que tu n'a pas vraiment capté le principe de
la pile d'appel.



C'est vrai. Je crois juste commencer à comprendre....


L'idée de la pile est d'avoir, à un instant T, toutes les infos
nécessaires (c'est à dire la balise en cours et toutes les balises
parentes) pour reconstituer le contexte.

Avec un document genre
"""
<section id="1" name="section 1" description="phrase courte pour la
section 1">

<sousSection id="1" dansLaSection="1" thema="subsection 1.1">
<texte>Texte de la soussection 1.1</texte>
</sousSection>

<sousSection id="2" dansLaSection="1" thema="subsection 1.2">
<texte>Texte de la soussection 1.2 qui se fini par la section 2</texte>
</sousSection>

</section>
"""

quand tu arrive à la balise <texte> de la sous-section 1.1, tu dois
avoir la pile suivante:

[('section', {'id':'1', 'name':'section 1', 'description': (...) }),
('sousSection', {'id':'1','dansLaSection':'1','thema':'subsection 1.1')
('texte', {})]

Et quand tu es dans la balise <texte> de la sous-section 1.2 :
[('section', {'id':'1', 'name':'section 1', 'description': (...) }),
('sousSection', {'id':'2','dansLaSection':'2','thema':'subsection 1.2')
('texte', {})]


(snip)
Et tu dépile quand ?


Avec :
def endElement(self, name):
if name == 'texte':
self._in_text = False
self._stack.pop()

je me retrouve avec une erreur de liste vide : "IndexError: pop from
empty list"... Oups, c'est une erreur d'identation en fait ! :$
Il faudrait plutôt :
def endElement(self, name):
if name == 'texte':
self._in_text = False
self._stack.pop()
(je n'ai besoin du contexte que dans les balises texte)


Oui, mais pour avoir le contexte kivabien(tm), il faut empiler *et*
dépiler de façon cohérente. Pour le moment, tu ne fais ni l'un ni l'autre...

Question idiote : à quoi sert de dépiler ?


A ce que ta pile reflète le 'chemin' menant de la racine de
l'arborescence à l'élément en cours.

Si tu ne dépile pas, arrivé à la sous-section 2, tu te retrouve avec la
pile suivante :

[('section', {'id':'1', 'name':'section 1', 'description': (...) }),
('sousSection', {'id':'1','dansLaSection':'1','thema':'subsection 1.1')
('texte', {}),
('sousSection', {'id':'2','dansLaSection':'2','thema':'subsection 1.2')
('texte', {})]

Qui correspondrait au document suivant:

"""
<section id="1" name="section 1" description="phrase courte pour la
section 1">
<sousSection id="1" dansLaSection="1" thema="subsection 1.1">
<texte>
Texte de la soussection 1.1
<sousSection id="2" dansLaSection="1" thema="subsection 1.2">
<texte>
Texte de la soussection 1.2 qui se fini par la section 2
</texte>
</sousSection>
</texte>
</sousSection>
</section>
"""

Comme tu peux le voir, si tu ne dépile pas au fur et à mesure, tu te
retrouve avec une représentation erronée du contexte.

(hormis le fait que la pile
peut devenir très grosse pour de très gros document). On fait toujours
appel aux derniers élements de la liste.


Non, pas forcément.

--
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in ''.split('@')])"



Avatar
remi
Bonjour,

remi wrote:

(big snip)


Merci beaucoup ! :-)
J'y vois (je pense) bien plus clair avec tous ces exemples !
@+
Rémi.