Transformation basique de XML en liste Tcl

 

Kroc - 29 Juin 2006

Le format XML est très à la mode, mais proprement imbuvable quand on l'attaque via Tdom dans le cadre d'une utilisation basique (c'est un avis personnel). C'est pourquoi (en me basant sur une page de Richard Suchenwirth sur le Wiki international [1]), j'ai écrit deux procédures pour ouvrir, valider et transformer en liste tcl des fichiers XML.

Attention : ces procédures ne gèrent pas les erreurs, vous devez donc les réserver au traitement de fichiers XML dont vous maîtrisez le contenu.

Le code :

	# Ouverture d'un fichier xml :
	proc open_xml {fichier doctype} {
	    if {![file readable $fichier]} {return}
	    set res ""
	    set fin [open $fichier r]
	    # On contrôle que c'est un xml valide :
	    set ctrl [gets $fin]
	    if {[lindex $ctrl 0] eq "<?xml" && [lindex $ctrl end] eq "?>"} {
	        foreach paire [split [string range $ctrl 6 end-3] " "] {
	            foreach "id val" [split $paire "="] {
	                set $id [string tolower [lindex $val 0]]
	            }
	        }
	        if {$version ne "1.0" || $standalone ne "yes" || ![info exists encoding]} {
	            # Pas un XML valide :
	            return ""
	        }
	    }
	    # On contrôle que le type de document est conforme :
	    set ctrl [string map {"<" "" ">" ""} [gets $fin]]
	    if {[lindex $ctrl 0] ne "!DOCTYPE" || [lindex $ctrl end] ne $doctype} {
	        # Pas le bon type de document :
	        return ""
	    }
	    # C'est tout bon, on peut parser le reste :
	    set data [encoding convertfrom $encoding [read $fin]]
	    close $fin
	    return [xml2list $data]
	}

	# Conversion de données XML en liste tcl :
	proc xml2list {xml} {
	    regsub -all {>\s*<} [string trim $xml " \n\t<>"] "\} \{" xml
	    set xml [string map {> "\} \{\x0E " < "\} \{" "?xml" \x0F}  $xml]
	    set res ""
	    set stack {}
	    set rest {}
	    foreach item "{$xml}" {
	        switch -regexp -- $item {
	            ^\x0F {
	                # Déclaration XML :
	                continue
	            }
	            ^!DOCTYPE {
	                # Déclaration type de document :
	                continue
	            }
	            ^!-- {
	                # Commentaire :
	                continue
	            }
	            ^\x0E {
	                append res [string range $item 2 end]
	            }
	            ^/ {
	                regexp {/(.+)} $item -> tagname
	                set expected [lindex $stack end]
	                if {$tagname!=$expected} {error "$item != $expected"}
	                set stack [lrange $stack 0 end-1]
	                append res "\}\} "
	            }
	            /$ {
	                regexp {([^ ]+)( (.+))?/$} $item -> tagname - rest
	                set rest [lrange [string map {= " "} $rest] 0 end]
	                append res "{$tagname [list $rest] {}} "
	            }
	            default {
	                set tagname [lindex [string map {{ "} \" {" } \"} $item] 0]
	                set rest [lrange [string map {= " "} $item] 1 end]
	                lappend stack $tagname
	                append res "\{$tagname [list $rest] \{"
	            }
	        }
	        if {[llength $rest]%2} {
	            # Le XML n'est pas valide : clé sans valeur
	            return ""
	        }
	    }
	    if {[llength $stack]} {
	        # Le XML n'est pas valide : il reste des tags non fermés
	        return ""
	    }

	    return [string map {"\} \}" "\}\}"} [lindex $res 0]]
	}

Utilisation :

Je vais prendre comme exemple le fichier ~/test.xml dont le contenu est le suivant :

	<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
	<!DOCTYPE Ma-Structure-que-jai>
	<items>
		<!-- Cette ligne est un commentaire -->
		<item name="un">simple</item>
		<item name="deux" option="true">complet</item>
		<item name="trois" size="12"  style="roman" slang="bold">arial</item>
		<item name="soleil"/>
	</items>

La première ligne est impérative et fixe (sauf l'encodage) : elle permet de valider que le fichier est un XML conforme. La deuxième est également impérative : elle vous permet d'indiquer le type de document XML attendu (passé en deuxième argument de open_xml).

Le résultat sera le suivant :

 % set xmldata [open_xml ~/test.xml Ma-Structure-que-jai]
 % foreach objet [lindex $xmldata 2] {
    puts $objet
 }
 item {name un} {simple}
 item {name deux option true} {complet}
 item {name trois size 12 style roman slang bold} {arial}
 item {name soleil} {}

Structure des listes reçues :

La structure de chaque élément du type balise attributs contenu, par exemple, la ligne XML suivante :

 <item name="deux" option="true">complet</item>

retourne la liste Tcl :

 {item {name deux option true} {complet}}

Bien entendu, les listes Tcl seront imbriquées si les balises XML le sont dans le fichier traité, dans notre exemple, c'est le cas de items qui est une liste des item.

Avantages :

Le format XML étant plus ou moins supporté par tous les langages de programmation, voilà une méthode simple pour partager des données structurées avec d'autres applications.


Transformation basique de XML en liste Tcl avec tdom