IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Plongez au coeur de Python ,De débutant à expert


précédentsommairesuivant

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.

 
Sélectionnez
"""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

 
Sélectionnez
[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

 
Sélectionnez
[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)

 
Sélectionnez
>>> 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

 
Sélectionnez
					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 (contains 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

 
Sélectionnez
>>> 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)

 
Sélectionnez
>>> from xml.dom import minidom                                          ***1***
>>> xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')  ***2***
>>> xmldoc                                                               ***3***
<xml.dom.minidom.Document instance at 010BE87C>
>>> 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

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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électionnez
>>> 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électionnez
>>> s = u'La Pe\xf1a'         ***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

 
Sélectionnez
# 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

 
Sélectionnez
>>> import sys
>>> sys.getdefaultencoding() ***1***
'iso-8859-1'
>>> s = u'La Pe\xf1a'
>>> 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 :

 
Sélectionnez
#!/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

 
Sélectionnez
<?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

 
Sélectionnez
>>> 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

 
Sélectionnez
<?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

 
Sélectionnez
>>> 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é

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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

 
Sélectionnez
>>> 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

précédentsommairesuivant
Il s'agit là encore d'une extrême simplification. Unicode a maintenant été étendu pour tenir compte des textes en chinois ancien, en coréen et en japonais, lesquels sont composés de tant de caractères que le système Unicode sur 2 octets n'aurait pu suffire à tous les représenter. Mais Python ne le supporte pas actuellement et je ne sais pas s'il y a un projet en cours pour l'y ajouter. Vous avez atteint les limites de mon expertise, désolé.