IX. Traitement de données XML▲
IX-A. Plonger▲
Les deux prochains chapitres concernent le traitement des données XML en Python. Il serait préférable que vous sachiez préalablement à quoi ressemble un document XML, qu'il est constitué de balises structurées pour former une hiérarchie d'éléments etc. Si cela vous est étranger, lisez d'abord ce tutoriel XML, puis reprenez votre lecture.
Si vous n'êtes pas particulièrement intéressés par XML, vous devriez prendre néanmoins connaissance de ces chapitres qui couvrent des sujets importants comme les paquetages Python, l'Unicode, les arguments de la ligne de commande et la façon d'utiliser getattr pour la sélection de méthode.
Nul besoin d'être un grand connaisseur en philosophie, bien que si vous avez eu la malchance d'être confronté aux écrits d'Emmanuel Kant, vous apprécierez davantage le programme qui sert d'exemple que si votre expertise touche à une matière beaucoup plus pragmatique, telle que la programmation.
Il y a fondamentalement deux manières de travailler avec XML. L'une est appelée SAX («Simple API for XML») et fonctionne en lisant les données XML au fur et à mesure et en appelant une méthode chaque fois qu'un élément est rencontré. (Si vous lisez Chapitre 8, Traitement du HTML, cela devrait vous paraître familier, parce que c'est la façon dont le module sgmllib fonctionne.) L'autre est appelée DOM («Document Object Model»), et fonctionne en lisant le document XML dans son entier et en en créant une représentation interne au moyen de classes Python natives reliées dans une structure arborescente. Python dispose de modules standards pour chacun de ces deux traitements, mais ce chapitre ne concernera que l'utilisation du DOM.
Ce qui suit est un programme complet en Python qui génère en sortie un résultat pseudo-aléatoire basé sur une grammaire hors contexte (context-freei) définie dans un format XML. Ne vous inquiétez pas pour l'instant de n'y rien comprendre; vous examinerez plus en détail à la fois les données en entrée et en sortie du programme tout au long de ce chapitre et du chapitre à venir.
Exemple 9.1. kgp.py▲
Si vous ne l'avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.
"""Kant Generator for Python
Generates mock philosophy based on a context-free grammar
Usage: python kgp.py [options] [source]
Options:
-g ..., --grammar=... use specified grammar file or URL
-h, --help show this help
-d show debugging information while parsing
Examples:
kgp.py generates several paragraphs of Kantian philosophy
kgp.py -g husserl.xml generates several paragraphs of Husserl
kpg.py "<xref id='paragraph'/>" generates a paragraph of Kant
kgp.py template.xml reads from template.xml to decide what to generate
"""
from
xml.dom import
minidom
import
random
import
toolbox
import
sys
import
getopt
_debug =
0
class
NoSourceError
(
Exception
): pass
class
KantGenerator:
"""generates mock philosophy based on a context-free grammar"""
def
__init__
(
self, grammar, source=
None
):
self.loadGrammar
(
grammar)
self.loadSource
(
source and
source or
self.getDefaultSource
(
))
self.refresh
(
)
def
_load
(
self, source):
"""load XML input source, return parsed XML document
- a URL of a remote XML file ("http://diveintopython.org/kant.xml")
- a filename of a local XML file ("~/diveintopython/common/py/kant.xml")
- standard input ("-")
- the actual XML document, as a string
"""
sock =
toolbox.openAnything
(
source)
xmldoc =
minidom.parse
(
sock).documentElement
sock.close
(
)
return
xmldoc
def
loadGrammar
(
self, grammar):
"""load context-free grammar"""
self.grammar =
self._load
(
grammar)
self.refs =
{}
for
ref in
self.grammar.getElementsByTagName
(
"ref"
):
self.refs[ref.attributes["id"
].value] =
ref
def
loadSource
(
self, source):
"""load source"""
self.source =
self._load
(
source)
def
getDefaultSource
(
self):
"""guess default source of the current grammar
The default source will be one of the <ref>s that is not
cross-referenced. This sounds complicated but it's not.
Example: The default source for kant.xml is
"<xref id='section'/>", because 'section' is the one <ref>
that is not <xref>'d anywhere in the grammar.
In most grammars, the default source will produce the
longest (and most interesting) output.
"""
xrefs =
{}
for
xref in
self.grammar.getElementsByTagName
(
"xref"
):
xrefs[xref.attributes["id"
].value] =
1
xrefs =
xrefs.keys
(
)
standaloneXrefs =
[e for
e in
self.refs.keys
(
) if
e not
in
xrefs]
if
not
standaloneXrefs:
raise
NoSourceError, "can't guess source, and no source specified"
return
'<xref id="
%s
"/>'
%
random.choice
(
standaloneXrefs)
def
reset
(
self):
"""reset parser"""
self.pieces =
[]
self.capitalizeNextWord =
0
def
refresh
(
self):
"""reset output buffer, re-parse entire source file, and return output
Since parsing involves a good deal of randomness, this is an
easy way to get new output without having to reload a grammar file
each time.
"""
self.reset
(
)
self.parse
(
self.source)
return
self.output
(
)
def
output
(
self):
"""output generated text"""
return
""
.join
(
self.pieces)
def
randomChildElement
(
self, node):
"""choose a random child element of a node
This is a utility method used by do_xref and do_choice.
"""
choices =
[e for
e in
node.childNodes
if
e.nodeType ==
e.ELEMENT_NODE]
chosen =
random.choice
(
choices)
if
_debug:
sys.stderr.write
(
'
%s
available choices:
%s\n
'
%
\
(
len(
choices), [e.toxml
(
) for
e in
choices]))
sys.stderr.write
(
'Chosen:
%s\n
'
%
chosen.toxml
(
))
return
chosen
def
parse
(
self, node):
"""parse a single XML node
A parsed XML document (from minidom.parse) is a tree of nodes
of various types. Each node is represented by an instance of the
corresponding Python class (Element for a tag, Text for
text data, Document for the top-level document). The following
statement constructs the name of a class method based on the type
of node we're parsing ("parse_Element" for an Element node,
"parse_Text" for a Text node, etc.) and then calls the method.
"""
parseMethod =
getattr(
self, "parse_
%s
"
%
node.__class__
.__name__
)
parseMethod
(
node)
def
parse_Document
(
self, node):
"""parse the document node
The document node by itself isn't interesting (to us), but
its only child, node.documentElement, is: it's the root node
of the grammar.
"""
self.parse
(
node.documentElement)
def
parse_Text
(
self, node):
"""parse a text node
The text of a text node is usually added to the output buffer
verbatim. The one exception is that <p class='sentence'> sets
a flag to capitalize the first letter of the next word. If
that flag is set, we capitalize the text and reset the flag.
"""
text =
node.data
if
self.capitalizeNextWord:
self.pieces.append
(
text[0
].upper
(
))
self.pieces.append
(
text[1
:])
self.capitalizeNextWord =
0
else
:
self.pieces.append
(
text)
def
parse_Element
(
self, node):
"""parse an element
An XML element corresponds to an actual tag in the source:
<xref id='...'>, <p chance='...'>, <choice>, etc.
Each element type is handled in its own method. Like we did in
parse(), we construct a method name based on the name of the
element ("do_xref" for an <xref> tag, etc.) and
call the method.
"""
handlerMethod =
getattr(
self, "do_
%s
"
%
node.tagName)
handlerMethod
(
node)
def
parse_Comment
(
self, node):
"""parse a comment
The grammar can contain XML comments, but we ignore them
"""
pass
def
do_xref
(
self, node):
"""handle <xref id='...'> tag
An <xref id='...'> tag is a cross-reference to a <ref id='...'>
tag. <xref id='sentence'/> evaluates to a randomly chosen child of
<ref id='sentence'>.
"""
id =
node.attributes["id"
].value
self.parse
(
self.randomChildElement
(
self.refs[id]))
def
do_p
(
self, node):
"""handle <p> tag
The <p> tag is the core of the grammar. It can contain almost
anything: freeform text, <choice> tags, <xref> tags, even other
<p> tags. If a "class='sentence'" attribute is found, a flag
is set and the next word will be capitalized. If a "chance='X'"
attribute is found, there is an X% chance that the tag will be
evaluated (and therefore a (100-X)% chance that it will be
completely ignored)
"""
keys =
node.attributes.keys
(
)
if
"class"
in
keys:
if
node.attributes["class"
].value ==
"sentence"
:
self.capitalizeNextWord =
1
if
"chance"
in
keys:
chance =
int(
node.attributes["chance"
].value)
doit =
(
chance >
random.randrange
(
100
))
else
:
doit =
1
if
doit:
for
child in
node.childNodes: self.parse
(
child)
def
do_choice
(
self, node):
"""handle <choice> tag
A <choice> tag contains one or more <p> tags. One <p> tag
is chosen at random and evaluated; the rest are ignored.
"""
self.parse
(
self.randomChildElement
(
node))
def
usage
(
):
print
__doc__
def
main
(
argv):
grammar =
"kant.xml"
try
:
opts, args =
getopt.getopt
(
argv, "hg:d"
, ["help"
, "grammar="
])
except
getopt.GetoptError:
usage
(
)
sys.exit
(
2
)
for
opt, arg in
opts:
if
opt in
(
"-h"
, "--help"
):
usage
(
)
sys.exit
(
)
elif
opt ==
'-d'
:
global
_debug
_debug =
1
elif
opt in
(
"-g"
, "--grammar"
):
grammar =
arg
source =
""
.join
(
args)
k =
KantGenerator
(
grammar, source)
print
k.output
(
)
if
__name__
==
"__main__"
:
main
(
sys.argv[1
:])
Exemple 9.2
. toolbox.py
"""Miscellaneous utility functions"""
def
openAnything
(
source):
"""URI, filename, or string --> stream
This function lets you define parsers that take any input source
(URL, pathname to local or network file, or actual data as a string)
and deal with it in a uniform manner. Returned object is guaranteed
to have all the basic stdio read methods (read, readline, readlines).
Just .close() the object when you're done with it.
Examples:
>>>
from
xml.dom import
minidom
>>>
sock =
openAnything
(
"http://localhost/kant.xml"
)
>>>
doc =
minidom.parse
(
sock)
>>>
sock.close
(
)
>>>
sock =
openAnything
(
"c:
\\
inetpub
\\
wwwroot
\\
kant.xml"
)
>>>
doc =
minidom.parse
(
sock)
>>>
sock.close
(
)
>>>
sock =
openAnything
(
"<ref id='conjunction'><text>and</text><text>or</text></ref>"
)
>>>
doc =
minidom.parse
(
sock)
>>>
sock.close
(
)
"""
if
hasattr(
source, "read"
):
return
source
if
source ==
'-'
:
import
sys
return
sys.stdin
# try to open with urllib (if source is http, ftp, or file URL)
import
urllib
try
:
return
urllib.urlopen
(
source)
except
(
IOError
, OSError
):
pass
# try to open with native open function (if source is pathname)
try
:
return
open(
source)
except
(
IOError
, OSError
):
pass
# treat source as string
import
StringIO
return
StringIO.StringIO
(
str(
source))
Lancez le programme kgp.py, qui va alors analyser la grammaire au format XML fournie par défaut dans le fichier kant.xml, puis afficher une prose philosophique dans le style d'Emmanuel Kant.
Exemple 9.3. Exemple de sortie de kgp.py▲
[you@localhost kgp]$ python kgp.py
As is
shown in
the writings of Hume, our a priori concepts, in
reference to ends, abstract from
all content of knowledge; in
the study
of space, the discipline of human reason, in
accordance with
the
principles of philosophy, is
the clue to the discovery of the
Transcendental Deduction. The transcendental aesthetic, in
all
theoretical sciences, occupies part of the sphere of human reason
concerning the existence of our ideas in
general; still, the
never-
ending regress in
the series of empirical conditions constitutes
the whole content for
the transcendental unity of apperception. What
we have alone been able to show is
that, even as
this relates to the
architectonic of human reason, the Ideal may not
contradict itself, but
it is
still possible that it may be in
contradictions with
the
employment of the pure employment of our hypothetical judgements, but
natural causes (
and
I assert
that this is
the case) prove the validity
of the discipline of pure reason. As we have already seen, time (
and
it is
obvious that this is
true) proves the validity of time, and
the
architectonic of human reason, in
the full sense of these terms,
abstracts from
all content of knowledge. I assert
, in
the case of the
discipline of practical reason, that the Antinomies are just as
necessary as
natural causes, since knowledge of the phenomena is
a
posteriori.
The discipline of human reason, as
I have elsewhere shown, is
by
its very nature contradictory, but our ideas exclude the possibility of
the Antinomies. We can deduce that, on the contrary, the pure
employment of philosophy, on the contrary, is
by its very nature
contradictory, but our sense perceptions are a representation of, in
the case of space, metaphysics. The thing in
itself is
a
representation of philosophy. Applied logic is
the clue to the
discovery of natural causes. However, what we have alone been able to
show is
that our ideas, in
other words, should only be used as
a canon
for
the Ideal, because of our necessary ignorance of the conditions.
[...snip...]
Il s'agit, bien entendu, d'un complet charabia. Et bien, pas tout à fait. Le propos est syntaxiquement et grammaticalement correct (bien que très verbeux -- Kant n'est pas connu pour son style laconique). Quelques propositions peuvent se trouver "vraies" (ou du moins correspondre à l'esprit de la pensée kantienne), d'autres sont d'une fausseté flagrante et la majeure partie du propos demeure incohérente. Mais l'ensemble reste dans le style d'Emmanuel Kant.
Inutile de préciser à nouveau que mieux vaut avoir un solide passé de philosophe pour apprécier cette forme d'humour.
L'intérêt de ce programme n'a aucun rapport avec une quelconque connaissance de la philosophie kantienne. Tout le contenu retourné par l'exemple précédent provient du fichier de grammaire, kant.xml. Si un autre fichier de grammaire est spécifié à la ligne de commande, le résultat sera complètement différent.
Exemple 9.4. Résultat plus simple à la sortie de kgp.py▲
[you@localhost kgp]$ python kgp.py -
g binary.xml
00101001
[you@localhost kgp]$ python kgp.py -
g binary.xml
10110100
Vous approfondirez la structure du fichier de grammaire plus loin dans ce chapitre. Pour le moment, il vous suffit de savoir que ce fichier définit la structure du résultat en sortie et que le programme kgp.py parcourt cette grammaire et choisit aléatoirement des mots et leur emplacement.
IX-B. Les paquetages▲
Analyser un document XML est pour l'heure chose très simple : cela tient sur une ligne de code. Cependant, avant que vous n'abordiez cette ligne de code, une digression s'impose pour parler des paquetages.
Exemple 9.5. Charger un document XML (bref aperçu) ▲
>>>
from
xml.dom import
minidom ***
1
***
>>>
xmldoc =
minidom.parse
(
'~/diveintopython/common/py/kgp/binary.xml'
)
***1*** Vous n'aviez pas encore vu cette syntaxe. Elle ressemble presque à votre cher from module import, mais le "." montre que c'est quelque chose de plus qu'un simple import. En fait, xml est bien le paquetage, dom un paquetage imbriqué dans xml et minidom un module de xml.dom.
Cela paraît compliqué, mais il n'en est rien. Un examen de l'implémentation réelle peut aider. Les paquetages sont un peu plus que des répertoires de modules; les paquetages imbriqués sont les sous-répertoires. Les modules au sein d'un paquetage (ou d'un paquetage imbriqué) sont juste des fichiers .py, comme toujours, sauf qu'ils sont rangés dans un sous-répertoire plutôt que dans le répertoire principal lib/ de votre installation Python.
Exemple 9.6. La disposition des fichiers dans un paquetage▲
Python21/ root Python installation (home of the executable)
|
+--lib/ library directory (home of the standard library modules)
|
+-- xml/ xml package (really just a directory with other stuff in
it)
|
+--sax/ xml.sax package (again, just a directory)
|
+--dom/ xml.dom package (con
tains minidom.py)
|
+--parsers/ xml.parsers package (used internally)
Ainsi, lorsque vous écrivez from xml.dom import minidom, Python comprend la chose suivante : «rechercher dans le répertoire xml un répertoire dom, rechercher dans ce répertoire le module minidom et l'importer en tant que minidom». Mais Python est beaucoup plus astucieux que cela; non seulement il vous est possible d'importer la totalité des modules d'un paquetage, mais il est encore possible de sélectionner des classes ou des fonctions spécifiques à l'intérieur d'un module. Il vous est également possible d'importer le paquetage lui-même comme un module. La syntaxe est toujours la même; Python comprend ce à quoi vous faites référence à partir de la disposition des fichiers dans le paquetage, et s'exécute automatiquement.
Exemple 9.7. Les paquetages sont aussi des modules▲
>>>
from
xml.dom import
minidom ***
1
***
>>>
minidom
<
module 'xml.dom.minidom'
from
'C:\Python21\lib\xml\dom\minidom.pyc'
>
>>>
minidom.Element
<
class
xml.dom.minidom.Element at 01095744
>
>>>
from
xml.dom.minidom import
Element ***
2
***
>>>
Element
<
class
xml.dom.minidom.Element at 01095744
>
>>>
minidom.Element
<
class
xml.dom.minidom.Element at 01095744
>
>>>
from
xml import
dom ***
3
***
>>>
dom
<
module 'xml.dom'
from
'C:\Python21\lib\xml\dom\__init__.pyc'
>
>>>
import
xml ***
4
***
>>>
xml
<
module 'xml'
from
'C:\Python21\lib\xml\__init__.pyc'
>
***1*** Ici vous importez un module (minidom) à partir d'un paquetage imbriqué (xml.dom). Il en résulte que minidom est importé dans votre espace de nom et que pour référencer les classes du module minidom (comme Element), vous devez les faire précéder du nom du module.
***2*** Ici vous importez la classe (Element) d'un module (minidom) à partir d'un paquetage imbriqué (xml.dom). Il en résulte que Element est importé directement dans votre espace de noms. Remarquez que cela n'interfère aucunement avec la précédente importation, la classe Element peut désormais être référencée de deux façons (mais il s'agit toujours de la même classe).
***3*** Ici vous importez le paquetage dom (un paquetage imbriqué de xml) en tant que module. Chaque niveau d'un paquetage peut être considéré comme un module, comme vous le verrez plus loin. Il peut même posséder ses propres attributs et méthodes, comme les modules que vous avez vus précédemment.
***4*** Ici vous importez le paquetage racine xml comme un module.
Mais alors, comment un paquetage (c'est-à-dire un répertoire) peut-il être importé et traité comme un module (c'est-à-dire un fichier) ? Par la magie du fichier __init__.py. Les paquetages ne sont pas de simples répertoires; ce sont des répertoires qui contiennent un fichier spécifique, __init__.py. Ce fichier définit les attributs et les méthodes du paquetage. Par exemple, xml.dom contient une classe Node, laquelle est définie dans xml/dom/__init__.py. Lorsque vous importez un paquetage en tant que module (comme dom à partir de xml), vous importez aussi son fichier __init__.py.
Un paquetage est un répertoire pourvu du fichier __init__.py. Le fichier __init__.py définit les attributs et les méthodes du paquetage. Il n'est cependant pas tenu de définir quoi que ce soit; ce peut simplement être un fichier vide, mais il se doit d'être présent. Et si __init__.py n'existe pas, le répertoire reste un répertoire, pas un paquetage et ne peut ni être importé ni contenir de modules ou de paquetages imbriqués.
Pourquoi s'embêter avec des paquetages ? C'est qu'ils permettent de regrouper logiquement des modules associés. Plutôt que d'avoir un paquetage xml contenant les paquetages sax et dom, les auteurs auraient pu décider de ranger toutes les fonctionnalités de sax dans le fichier xmlsax.py et de la même façon les fonctionnalités de dom dans xmldom.py, voire même de n'en faire qu'un seul module. Mais cela aurait été peu maniable (à ce jour, le paquetage XML contient plus de 3000 lignes de code) et difficile à gérer (des fichiers sources séparés permettent à plusieurs personnes de travailler simultanément à différents endroits du code).
Si vous vous retrouvez à écrire un long sous-système en Python (ou, plus probablement, quand vous vous rendrez compte que votre petit sous-système s'est énormément développé), prenez le temps de construire une solide architecture de paquetage. C'est là encore l'un des nombreux avantages de Python, alors profitez-en.
IX-C. Analyser un document XML▲
Comme je le disais, analyser un document XML est chose très simple qui tient en une ligne de code. A vous de décider de la suite.
Exemple 9.8. Charger un document XML (version longue) ▲
>>>
from
xml.dom import
minidom ***
1
***
>>>
xmldoc =
minidom.parse
(
'~/diveintopython/common/py/kgp/binary.xml'
) ***
2
***
>>>
xmldoc ***
3
***
<
xml.dom.minidom.Document instance at 010
BE87C>
>>>
print
xmldoc.toxml
(
) ***
4
***
<
?xml version=
"1.0"
?>
<
grammar>
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
<
ref id=
"byte"
>
<
p><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/>
\
<
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/></
p>
</
ref>
</
grammar>
***1*** Comme vous l'avez vu dans la section précédente, cela importe le module minidom à partir du paquetage xml.dom.
***2*** Voici la ligne de code qui fait tout le travail : minidom.parse prend un argument et retourne une représentation analysée du document XML. Un argument peut recouvrir beaucoup de choses; dans ce cas, il s'agit du nom de fichier d'un document XML sur le disque local. (afin de poursuivre, il vous faudra modifier le chemin pour pointer vers votre répertoire d'exemples.) Mais vous pouvez aussi lui passer un objet-fichier, ou même un pseudo objet-fichier. Vous tirerez avantage de cette souplesse plus loin dans ce chapitre.
***3*** L'objet retourné par minidom.parse est un objet Document, un descendant de la classe Node. Cet objet Document est la racine d'une structure pseudo-arborescente d'objets Python en relation qui représente complètement le document XML passé à minidom.parse.
***4*** toxml est une méthode de la classe Node (et donc disponible à partir de l'objet Document que vous obtenez de minidom.parse). toxml affiche les données XML représentées par ce Node. Pour le noeud Document, il affiche le document XML en entier.
Maintenant que vous avez un document XML en mémoire, vous pouvez commencer à le parcourir.
Exemple 9.9. Obtenir les noeuds enfants▲
>>>
xmldoc.childNodes ***
1
***
[<
DOM Element: grammar at 17538908
>
]
>>>
xmldoc.childNodes[0
] ***
2
***
<
DOM Element: grammar at 17538908
>
>>>
xmldoc.firstChild ***
3
***
<
DOM Element: grammar at 17538908
>
***1*** Chaque Node possède un attribut childNodes, qui est une liste d'objets Node. Un Document n'a jamais qu'un noeud enfant : l'élément racine du document XML (dans ce cas, l'élément grammar).
***2*** Pour obtenir le premier (et, dans ce cas, le seul) noeud enfant, utilisez simplement la syntaxe normale des listes. Rappelez-vous qu'il n'y a rien de particulier à ce sujet; il ne s'agit que d'une liste d'objets Python tout à fait normaux.
***3*** Puisqu'obtenir le premier noeud enfant d'un noeud parent est une action utile et courante, la classe Node possède un attribut firstChild, lequel est synonyme de childNodes[0]. (Il existe également un attribut lastChild, qui correspond à childNodes[-1].)
Exemple 9.10. toxml fonctionne pour tout noeud ▲
>>>
grammarNode =
xmldoc.firstChild
>>>
print
grammarNode.toxml
(
) ***
1
***
<
grammar>
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
<
ref id=
"byte"
>
<
p><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/>
\
<
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/></
p>
</
ref>
</
grammar>
***1*** Puisque la méthode toxml est définie dans la classe Node, elle vaut pour tout noeud XML et pas seulement pour l'élément Document.
Exemple 9.11. Les noeuds enfants peuvent être de type texte▲
>>>
grammarNode.childNodes ***
1
***
[<
DOM Text node "
\n
"
>
, <
DOM Element: ref at 17533332
>
, \
<
DOM Text node "
\n
"
>
, <
DOM Element: ref at 17549660
>
, <
DOM Text node "
\n
"
>
]
>>>
print
grammarNode.firstChild.toxml
(
) ***
2
***
>>>
print
grammarNode.childNodes[1
].toxml
(
) ***
3
***
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
>>>
print
grammarNode.childNodes[3
].toxml
(
) ***
4
***
<
ref id=
"byte"
>
<
p><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/>
\
<
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/></
p>
</
ref>
>>>
print
grammarNode.lastChild.toxml
(
) ***
5
***
***1*** A l'examen des éléments XML de <b>binary.xml</b>, vous pourriez penser que <b>grammar</b> a seulement deux noeuds enfants, les deux éléments <b>ref</b>. Mais vous oubliez quelque chose : les retours chariot ! Après '<b><grammar></b>' et avant le premier '<b><ref></b>' se trouve un retour chariot et ce texte compte comme un noeud enfant de l'élément grammar. De la même façon, il y a un retour charriot après chaque '<b></ref></b>'; il faut également les compter comme des noeuds enfants. Aussi <b>grammar.childNodes</b> correspond en réalité à une liste de 5 objets: 3 objets <b>Text</b> et 2 objets <b>Element</b>.<br/><br/> ***2*** Le premier enfant est un objet <b>Text</b> qui représente le retour chariot après la balise '<b><grammar></b>' et avant la première balise '<b><ref></b>'.<br/><br/> ***3*** Le deuxième enfant est un objet <b>Element</b> représentant le premier élément <b>ref</b>.<br/><br/> ***4*** Le quatrième enfant est un objet <b>Element</b> représentant le second élément <b>ref</b>.<br/><br/> ***5*** Le dernier enfant est un objet <b>Text</b> représentant le retour chariot après la dernière balise '<b></ref></b>' et avant la dernière balise '</grammar>'. <br/><br/>
Exemple 9.12. Tracer une route jusqu'au texte▲
>>>
grammarNode
<
DOM Element: grammar at 19167148
>
>>>
refNode =
grammarNode.childNodes[1
] ***
1
***
>>>
refNode
<
DOM Element: ref at 17987740
>
>>>
refNode.childNodes ***
2
***
[<
DOM Text node "
\n
"
>
, <
DOM Text node " "
>
, <
DOM Element: p at 19315844
>
, \
<
DOM Text node "
\n
"
>
, <
DOM Text node " "
>
, \
<
DOM Element: p at 19462036
>
, <
DOM Text node "
\n
"
>
]
>>>
pNode =
refNode.childNodes[2
]
>>>
pNode
<
DOM Element: p at 19315844
>
>>>
print
pNode.toxml
(
) ***
3
***
<
p>
0
</
p>
>>>
pNode.firstChild ***
4
***
<
DOM Text node "0"
>
>>>
pNode.firstChild.data ***
5
***
u'0'
***1*** Comme vous l'aviez vu dans l'exemple précédent, le premier élément ref correspond à grammarNode.childNodes[1], puisque childNodes[0] est un noeud Text qui a pour valeur le retour chariot.
***2*** L'élément ref possède son propre ensemble de noeuds enfants : un pour le retour chariot, un autre pour les espaces, un troisième pour l'élément p et ainsi de suite.
***3*** Vous pouvez même utiliser ici la méthode toxml, profondément imbriqué dans le document.
***4*** L'élément p possède un seul noeud enfant (vous ne pouvez pas dire cela de cet exemple, mais regardez le pNode.childNodes si vous en me croyez pas) et il s'agit du noeud Text représentant le caractère '0'.
***5*** L'attribut .data du noeud Text vous donne la chaîne de caractères représentée par le noeud texte. Mais que signifie le 'u' qui précède cette chaîne ? La réponse mérite d'être développée dans une section à part entière.
IX-D. Le standard Unicode▲
Le standard Unicode est un mécanisme pour représenter les caractères de tous les différents langages à travers le monde. Quand Python analyse un document XML, toutes les données sont stockées en mémoire au format Unicode.
Vous y reviendrez dans une minute après un bref rappel historique.
Remarque historique. Avant l'Unicode, il y avait des systèmes d'encodage de caractères propres à chaque langage, chacun utilisant les mêmes positions (0-255) pour représenter son jeu de caractères. Certains langages (comme le Russe) disposaient de multiples standards divergents sur le manière de représenter les mêmes caractères; d'autres langages (comme le japonais) avaient tant de caractères qu'ils nécessitaient de recourir à de multiples jeu de caractères. L'échange de documents entre systèmes était difficile parce qu'il n'y avait aucun moyen pour une machine de dire avec certitude quel schéma d'encodage avait utilisé l'auteur d'un document; la machine ne voyait que des nombres et ces nombres pouvaient avoir plusieurs significations. Imaginez alors d'enregistrer ces documents au même endroit (comme dans la même table d'une base de données); vous auriez besoin d'enregistrer le jeu d'encodage avec chaque partie du texte et de vous assurer de le transmettre en même temps que le texte. Imaginez alors à quoi ressembleraient des documents multilingues rassemblant les caractères issus de différents langages. (Typiquement, ils utiliseraient des codes d'échappement pour passer d'un mode à l'autre; et hop, vous utilisez le mode russe koi8-r, et le caractère 241 signifie ceci; et hop, vous êtes maintenant dans un mode grec, et le caractère 241 signifie cela. Et ainsi de suite.) C'est pour résoudre ce problème qu'Unicode a été conçu.
Pour résoudre ces problèmes, Unicode représente chaque caractère comme un nombre codé sur 2 octets, de 0 à 65535.(6) Chaque nombre représente un unique caractère utilisé au moins dans l'un des langages existant. (Les caractères présents dans de multiples langages ont le même code numérique.) Il y a exactement un nombre par caractère et un caractère par nombre. Un caractère Unicode n'est jamais ambigu.
Bien sûr, il n'en demeure pas moins le problème de tous ces systèmes d'encodage antiques. L'ASCII 7-bit, par exemple, qui enregistre les caractères anglais dans une fourchette de 0 à 127. (65 représente le «A» majuscule, 97 le «a» minuscule et ainsi de suite.) L'anglais dispose d'un alphabet très simple et il peut être complètement exprimé en ASCII 7 bits. Les langages d'Europe de l'Ouest comme le français, l'espagnol, et l'allemand utilisent tous un système d'encodage appelé ISO-8859-1 (appelé encore «latin-1»), qui reprend les caractères de l'ASCII 7 bits pour les positions de 0 à 127, puis étend son espace de positions de 128 et 255 pour les caractères comme le 'n coiffé d'un tilde' (241), et le 'u surmonté de deux points' (252). Unicode utilise les mêmes caractères que l'ASCII 7 bits de 0 à 127, les mêmes caractères que l'ISO-8859-1 de 128 à 255, puis étend son espace de positions de 256 à 65535 pour accueillir les caractères des autres langues.
Quand vous vous occupez de données Unicode, vous pouvez avoir ponctuellement besoin de rétroconvertir ces données dans un système d'encodage archaïque. Par exemple, pour les intégrer dans un autre système informatique qui s'attend à recevoir des données dans un modèle d'encodage spécifique sur un octet, ou pour les afficher sur une console ou une imprimante qui ne gère pas l'Unicode. Ou encore, pour les stocker dans un document XML qui précise explicitement un modèle d'encodage différent.
Et sur cette remarque, retournons à Python.
Le langage Python supporte Unicode depuis la version 2.0. Le paquetage XML utilise Unicode pour mémoriser toutes les données XML analysées, mais vous pouvez utiliser Unicode partout.
Exemple 9.13. Introduction à Unicode▲
>>>
s =
u'Dive in'
***
1
***
>>>
s
u'Dive in'
>>>
print
s ***
2
***
Dive in
***1*** Pour créer une chaîne de caractères Unicode plutôt qu'une chaîne ASCII, ajoutez la lettre «u» devant la chaîne. Notez que cette chaîne particulière ne possède aucun caractère non-ASCII. Parfait; Unicode est un sur-ensemble d'ASCII (un immense sur-ensemble en vérité), ainsi toute chaîne de caractères ASCII peut être aussi stockée en Unicode.
***2*** Lorsque l'on affiche une chaîne de caractères, Python essaie de la convertir dans l'encodage par défaut, généralement l'ASCII. (Nous y venons dans une minute.) Puisque cette chaîne Unicode est constituée de caractères qui sont également des caractères ASCII, leur affichage produit dans les deux cas le même résultat; la conversion est uniforme et si vous ne saviez pas que s était une chaîne de caractères Unicode, vous ne remarquez pas la différence.
Exemple 9.14. Mémoriser des caractères non-ASCII▲
>>>
s =
u'La Pe
\xf1
a'
***
1
***
>>>
print
s ***
2
***
Traceback (
innermost last):
File "<interactive input>"
, line 1
, in
?
UnicodeError
: ASCII encoding error: ordinal not
in
range(
128
)
>>>
print
s.encode
(
'latin-1'
) ***
3
***
La Pen~
a
***1*** Le véritable avantage d'Unicode est, bien entendu, sa capacité à mémoriser des caractères non-ASCII, comme «n~» espagnol (n surmonté d'un tilde). Le caractère Unicode pour n tilde est U+0xf1 en hexadécimal (241 en décimal), que vous pouvez écrire de cette façon : \xf1.
***2*** Vous rappelez-vous que je disais que la fonction print essaie de convertir une chaîne de caractères Unicode en ASCII afin de pouvoir l'afficher ? Et bien, cela ne marche pas ici parce que notre chaîne Unicode contient des caractères non-ASCII et Python déclenche une erreur UnicodeError.
***3*** Voici comment s'effectue la conversion d'Unicode vers d'autres modèles d'encodage. s est une chaîne de caractères Unicode, mais print peut seulement afficher une chaîne de caractères normale. Pour résoudre ce problème, vous appelez la méthode encode, disponible pour toute chaîne Unicode, afin d'effectuer la conversion de l'Unicode vers le modèle d'encodage passé en paramètre. Dans ce cas, vous utilisez latin-1 (aussi connu sous le nom de iso-8859-1), qui inclut le n tilde (tandis que l'encodage ASCII par défaut ne le prend pas en charge, étant donné qu'il inclut seulement les caractères positionnés de 0 à 127).
Vous rappelez-vous que je disais que Python convertit d'habitude l'Unicode en ASCII toutes les fois qu'une chaîne Unicode a besoin d'être transformée en une chaîne normale ? Et bien, ce modèle d'encodage par défaut est une option qui peut être personnalisée.
Exemple 9.15. sitecustomize.py▲
# sitecustomize.py ***1***
# this file can be anywhere in your Python path,
# but it usually goes in ${pythondir}/lib/site-packages/
import
sys
sys.setdefaultencoding
(
'iso-8859-1'
) ***
2
***
***1*** sitecustomize.py est un script particulier; Python essaie de le charger au démarrage, si bien qu'il est lancé automatiquement. Comme il est indiqué dans le commentaire, il peut se trouver n'importe où (à condition que import puisse le retrouver), mais il est placé généralement dans le répertoire site-packages au sein du répertoire Python lib.
***2*** La fonction setdefaultencoding déclare un encodage par défaut. Python tâchera d'utiliser ce modèle d'encodage quand il aura besoin de transformer automatiquement une chaîne Unicode en une chaîne normale.
Exemple 9.16. Les effets du paramétrage de l'encodage par défaut▲
>>>
import
sys
>>>
sys.getdefaultencoding
(
) ***
1
***
'iso-8859-1'
>>>
s =
u'La Pe
\xf1
a'
>>>
print
s ***
2
***
La Pen~
a
***1*** Cette exemple suppose que vous ayez effectué les changements indiqués dans l'exemple précédent concernant le fichier sitecustomize.py et que vous ayez redémarré Python. si l'encodage par défaut signale encore 'ascii', vous n'avez pas paramétré correctement sitecustomize.py, ou bien Python n'a pas été redémarré. L'encodage par défaut peut seulement être changé au démarrage de Python; vous ne pouvez pas le modifier ultérieurement. (En raison de quelques excentricités de programmation que je ne détaillerai pas maintenant, vous ne pouvez plus appeler sys.setdefaultencoding après que Python ait démarré. Pour creuser la question, voyez site.py et recherchez «setdefaultencoding».)
***2*** Maintenant que le modèle d'encodage par défaut inclut tous les caractères que vous utilisez dans votre chaîne, Python n'a aucun problème pour la convertir automatiquement et l'afficher.
Exemple 9.17. Spécifier l'encodage des fichiers .py▲
Si vous stockez des chaînes non-ASCII dans votre code Python, vous aurez besoin de spécifier l'encodage pour chaque fichier .py en déclarant l'encodage en tête de chaque fichier. Cette déclaration définit le fichier .py au format UTF-8 :
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
Qu'en est-il maintenant des documents XML ? Et bien, chaque document XML possède un encodage spécifique. De nouveau, ISO-8859-1 est un encodage courant pour traiter les données des langages de l'Europe de l'Ouest. C'est également le cas de KOI8-R pour les documents en langue russe. Lorsque l'encodage est spécifié, il apparaît dans l'en-tête du document XML.
Exemple 9.18. russiansample.xml▲
<?xml version="1.0" encoding="koi8-r"?>
***1***
<preface>
<title>
???????????</title>
***2***
</preface>
***1*** Cet exemple est extrait d'un véritable document XML rédigé en russe; c'est une partie de la traduction de ce livre-ci. Remarquez l'encodage, koi8-r, spécifié dans l'en-tête.
***2*** Ces caractères cyrilliques composent, à ma connaissance, le mot russe qui signifie «Preface». Si vous ouvrez ce fichier dans un éditeur de texte classique, vous obtiendrez vraisemblablement une chaîne de caractères incompréhensible, parce que ces caractères sont encodés en koi8-r, mais sont affichés en iso-8859-1.
Exemple 9.19. Analyser russiansample.xml▲
>>>
from
xml.dom import
minidom
>>>
xmldoc =
minidom.parse
(
'russiansample.xml'
) ***
1
***
>>>
title =
xmldoc.getElementsByTagName
(
'title'
)[0
].firstChild.data
>>>
title 2
u'
\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435
'
>>>
print
title ***
3
***
Traceback (
innermost last):
File "<interactive input>"
, line 1
, in
?
UnicodeError
: ASCII encoding error: ordinal not
in
range(
128
)
>>>
convertedtitle =
title.encode
(
'koi8-r'
) ***
4
***
>>>
convertedtitle
'
\xf0\xd2\xc5\xc4\xc9\xd3\xcc\xcf\xd7\xc9\xc5
'
>>>
print
convertedtitle ***
5
***
???????????
***1*** Je suppose que vous avez sauvegardé l'exemple précédent en tant que russiansample.xml dans le répertoire courant. Je suppose également, pour ne rien oublier, que vous êtes revenu à un encodage en 'ascii' par défaut, en retirant le fichier sitecustomize.py, ou en commentant la ligne setdefaultencoding. ***2*** Notez que les données texte de la balise title (maintenant dans la variable title, grâce à cette longue concaténation de fonctions Python dont je ne dirai mot jusqu'à la prochaine section, au risque de vous agacer) -- les données texte de l'élément title du document XML sont mémorisées en Unicode. ***3*** Il n'est pas possible d'afficher le titre parce que cette chaîne Unicode contient des caractères non-ASCII et Python ne peut le convertir en ASCII car cela n'a aucun sens. ***4*** Vous pouvez cependant les convertir explicitement en koi8-r et dans ce cas vous obtenez une chaîne (normale, et non Unicode) de caractères codés sur un octet (f0, d2, c5, et ainsi de suite) qui sont les versions koi8-r des caractères de la chaîne Unicode d'origine. ***5*** L'affichage de la chaîne encodée en koi8-r ne produira probablement qu'un charabia sur votre écran, parce que votre IDE Python interprète ces caractères en iso-8859-1, non en koi8-r. Mais ils sont au moins affichés. (Et , si vous regardez attentivement, il s'agit du même charabia que lorsque vous aviez ouvert le document XML original dans un éditeur de texte non-Unicode. Python le convertit de koi8-r en Unicode lorsqu'il analyse le document XML et vous l'avez juste rétroconvertis.)
Pour résumer, Unicode peut paraître intimidant si vous n'y avez jamais eu à faire auparavant, mais les données en Unicode sont très faciles à manipuler avec Python. Si vos documents XML sont tous codés en ASCII 7-bit (comme les exemples de ce chapitre), vous ne vous soucierez jamais d'Unicode. Python convertira les données ASCII des documents XML en Unicode au moment du traitement et les restituera automatiquement en ASCII au besoin, sans que vous ne le remarquiez jamais. Mais s'il devient nécessaire de les gérer dans d'autres langages, Python répond présent à l'appel.
Lectures complémentaires
- Unicode.org est le site officiel du standard Unicode et propose une brève introduction technique.
- Unicode Tutorial contient beaucoup plus d'exemples sur la façon d'utiliser les fonctions Unicode de Python, y compris la manière de forcer Python à contraindre l'Unicode en ASCII, même s'il regimbe.
- PEP 263 approfondit la manière de définir un jeu d'encodage dans vos fichiers .py.
IX-E. Rechercher des éléments ▲
Parcourir des documents XML en s'arrêtant à chaque noeud peut être fastidieux. Si vous recherchez un noeud particulier, enfoui au plus profond de votre document XML, il existe un raccourci pour le retrouver rapidement : getElementsByTagName.
Pour cette section, vous utiliserez le fichier de grammaire binary.xml qui se présente comme suit :
Exemple 9.20. binary.xml▲
<?xml version="1.0"?>
<!DOCTYPE grammar PUBLIC
"-//diveintopython.org//DTD Kant Generator Pro v1.0//EN"
"kgp.dtd"
>
<grammar>
<ref
id
=
"bit"
>
<p>
0</p>
<p>
1</p>
</ref>
<ref
id
=
"byte"
>
<p><xref
id
=
"bit"
/><xref
id
=
"bit"
/><xref
id
=
"bit"
/><xref
id
=
"bit"
/>
\
<xref
id
=
"bit"
/><xref
id
=
"bit"
/><xref
id
=
"bit"
/><xref
id
=
"bit"
/></p>
</ref>
</grammar>
Il a deux refs, 'bit' et 'byte'. Un bit contient soit un '0' soit un '1' et un byte vaut 8 bits.
Exemple 9.21. Introduction à getElementsByTagName▲
>>>
from
xml.dom import
minidom
>>>
xmldoc =
minidom.parse
(
'binary.xml'
)
>>>
reflist =
xmldoc.getElementsByTagName
(
'ref'
) ***
1
***
>>>
reflist
[<
DOM Element: ref at 136138108
>
, <
DOM Element: ref at 136144292
>
]
>>>
print
reflist[0
].toxml
(
)
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
>>>
print
reflist[1
].toxml
(
)
<
ref id=
"byte"
>
<
p><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/>
\
<
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/><
xref id=
"bit"
/></
p>
</
ref>
***1*** getElementsByTagName prend pour argument le nom de l'élément à rechercher. Il retourne une liste d'objets Element, correspondant aux éléments XML qui portent ce nom. Dans ce cas, vous trouvez deux éléments ref.
Exemple 9.22. Tout élément peut être recherché▲
>>>
firstref =
reflist[0
] ***
1
***
>>>
print
firstref.toxml
(
)
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
>>>
plist =
firstref.getElementsByTagName
(
"p"
) ***
2
***
>>>
plist
[<
DOM Element: p at 136140116
>
, <
DOM Element: p at 136142172
>
]
>>>
print
plist[0
].toxml
(
) ***
3
***
<
p>
0
</
p>
>>>
print
plist[1
].toxml
(
)
<
p>
1
</
p>
***1*** Toujours avec le même exemple, le premier objet de votre reflist est l'élément ref 'bit'.
***2*** Vous pouvez utiliser la même méthode getElementsByTagName sur cet Element pour trouver tous les éléments <p> à l'intérieur de l'élément ref 'bit'.
***3*** Comme précédemment, la méthode getElementsByTagName retourne une liste de tous les éléments trouvés. Dans ce cas, il y en a deux, un pour chaque bit.
Exemple 9.23. La recherche est en réalité récursive▲
>>>
plist =
xmldoc.getElementsByTagName
(
"p"
) ***
1
***
>>>
plist
[<
DOM Element: p at 136140116
>
, <
DOM Element: p at 136142172
>
, <
DOM Element: p at 136146124
>
]
>>>
plist[0
].toxml
(
) ***
2
***
'<p>0</p>'
>>>
plist[1
].toxml
(
)
'<p>1</p>'
>>>
plist[2
].toxml
(
) ***
3
***
'<p><xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/>\
<xref id="bit"/><xref id="bit"/><xref id="bit"/><xref id="bit"/></p>'
***1*** Soyez attentif à la différence entre cet exemple et le précédent. Précédemment, vous aviez cherché les éléments p contenus dans firstref, mais ici vous recherchez les éléments p de xmldoc, l'objet racine qui représente le document XML complet. Cette instruction retrouve les éléments p contenus dans les éléments ref de l'élément racine grammar.
***2*** Les deux premiers éléments p sont contenus dans le premier élément ref (ref 'bit').
***3*** Le dernier élément p se trouve dans le second élément ref (ref 'byte').
IX-F. Accéder aux attributs d'un élément ▲
Les éléments XML peuvent avoir un ou plusieurs attributs et il est très facile d'y accéder une fois le document XML analysé.
Pour cette section, vous utiliserez le même fichier de grammaire binary.xml que dans la section précédente.
Cette section peut paraître un peu confuse dans le mesure où des terminologies se recouvrent. Les éléments d'un document XML ont des attributs, mais les objets Python ont aussi des attributs. Lorsque vous analysez un document XML, vous obtenez un paquet d'objets Python qui représentent l'ensemble des parties du document XML et certains de ces objets Python représentent les attributs des éléments XML. Mais les objets (Python) qui représentent les attributs (XML) possèdent également des attributs (Python), qui sont utilisés pour accéder à diverses parties de l'attribut (XML) que l'objet représente. Je vous avais bien dit que tout cela prêtait à confusion. Je suis ouvert à toute suggestion qui permettrait de les distinguer plus clairement.
Exemple 9.24. Accéder aux attributs d'un élément▲
>>>
xmldoc =
minidom.parse
(
'binary.xml'
)
>>>
reflist =
xmldoc.getElementsByTagName
(
'ref'
)
>>>
bitref =
reflist[0
]
>>>
print
bitref.toxml
(
)
<
ref id=
"bit"
>
<
p>
0
</
p>
<
p>
1
</
p>
</
ref>
>>>
bitref.attributes ***
1
***
<
xml.dom.minidom.NamedNodeMap instance at 0x81e0c9c
>
>>>
bitref.attributes.keys
(
) ***
2
***
***
3
***
[u'id'
]
>>>
bitref.attributes.values
(
) ***
4
***
[<
xml.dom.minidom.Attr instance at 0x81d5044
>
]
>>>
bitref.attributes["id"
] ***
5
***
<
xml.dom.minidom.Attr instance at 0x81d5044
>
***1*** Chaque objet Element a un attribut appelé attributes, qui est un objet NamedNodeMap. Pas de panique : un objet NamedNodeMap joue le même rôle qu'un dictionnaire, objet dont vous connaissez déjà l'usage. ***2*** En Considérant NamedNodeMap comme un dictionnaire, vous pouvez obtenir une liste des noms des attributs de cet élément au moyen de attributes.keys(). Cet élément a seulement un attribut, 'id'. ***3*** Les noms d'attributs, comme tout autre texte d'un document XML, sont emmagasinés en Unicode. ***4*** En considérant de nouveau NamedNodeMap comme un dictionnaire, vous pouvez obtenir une liste des valeurs des attributs au moyen de attributes.values(). Les valeurs sont elles-même des objets de type Attr. Vous verrez comment obtenir de précieuses informations à partir de cet objet dans l'exemple suivant. ***5*** En considérant toujours NamedNodeMap comme un dictionnaire, vous pouvez accéder à un attribut individuel par son nom, en ayant recours à la syntaxe courante d'un dictionnaire. (Les lecteurs extrêmement attentifs savent déjà comment la classe NamedNodeMap accomplit ce remarquable tour : en définissant une méthode spéciale __getitem__. Que les autres se rassurent, ils n'ont pas besoin d'en connaître le fonctionnemment pour l'utiliser efficacement.)
Exemple 9.25. Accéder aux attributs individuels▲
>>>
a =
bitref.attributes["id"
]
>>>
a
<
xml.dom.minidom.Attr instance at 0x81d5044
>
>>>
a.name ***
1
***
u'id'
>>>
a.value ***
2
***
u'bit'
***1*** L'objet Attr représente complétement l'attribut XML unique d'un élément XML unique. Le nom de l'attribut (le nom déjà utilisé pour trouver cet objet dans le pseudo-dictionnaire NamedNodeMap bitref.attributes) est rangé dans a.name. ***2*** La valeur courante du texte de cet attribut XML est stockée dans a.value.
A l'instar d'un dictionnaire, les attributs d'un élément XML ne sont pas ordonnés. Il se peut que les attributs apparaissent dans un certain ordre dans le document XML original et qu'ils apparaissent dans un certain ordre quand le document XML est analysé en objets Python, mais ces ordonnancements sont arbitraires et n'ont aucune signification particulière. Vous devriez toujours accéder aux attributs individuels par leur nom, comme les clés d'un dictionnaire.
IX-G. Transition▲
Voilà, c'est tout pour ce qui concerne XML en tant que tel. Le chapitre suivant reprend les mêmes programmes donnés en exemple, mais en mettant l'accent sur certains aspects qui rendent plus souple leur maniement : l'utilisation des flots de données (streams) pour le traitement des données en entrée, l'utilisation de getattr pour la sélection de méthode et l'utilisation des drapeaux de ligne de commande pour permettre aux utilisateurs de paramétrer le programme sans intervenir dans le code.
Avant de passer au chapitre suivant, vous devez être à l'aise avec ces différents points :
- Analyser les documents XML au moyen de minidom, faire une recherche dans le document analysé et accéder arbitrairement aux attributs et aux enfants d'un élément
- Organiser des bibliothèques complexes en paquetages
- Convertir des chaînes Unicode dans différents jeux d'encodage de caractères