Powered by |
Introduction à YAMLMichel Casabianca - casa@sweetohm.net
On trouvera une archive ZIP avec cet article au format PDF ainsi que les exemples à l'adresse : http://www.sweetohm.net/arc/introduction-yaml.zip. Qu'est ce que YAML ?Le nom YAML veut dire "YAML Ain't Markup Language", soit "YAML n'est pas un langage de balises". Si cela met d'emblée des distances avec XML, cela ne nous dit pas ce qu'est YAML. YAML est, d'après sa spécification, un langage de sérialisation de données conçu pour être lisible par des humains et travaillant bien avec les langage de programmation modernes pour les tâches de tous les jours. Concrètement, on pourrait noter la liste des ingrédients pour un petit déjeuner de la manière suivante :
Ceci est un fichier YAML valide qui représente une liste de chaînes de caractères. Pour s'en convaincre, nous pouvons écrire le script Python suivant qui parse le fichier, dont le nom est passé sur la ligne de commande, et affiche le résultat :
Ce script produira le résultat suivant :
Ce qui veut dire que le résultat de ce parsing est une liste Python contenant les chaînes de caractères appropriées ! Le parseur est donc capable de restituer des structures de données naturelles du langage utilisé pour le parsing. Écrivons maintenant un compte à rebours :
Nous obtenons le résultat suivant :
C'est toujours une liste, mais le parseur a reconnu chaque élément comme étant un entier et non une simple chaîne de caractères comme dans l'exemple précédent. Le parseur est donc capable de distinguer des types de données tels que chaînes de caractères et entiers, nombres à virgule flottante et dates. On utilise pour ce faire une syntaxe naturelle : 3 est reconnu comme un entier alors que croissants ne l'est pas car ce dernier ne peut être converti ni en entier, ni en nombre à virgule flottante, ni en tout autre type reconnu par YAML. Pour forcer YAML à interpréter 3 comme une chaîne de caractères, on peut l'entourer de guillemets. YAML peut aussi reconnaître des tableaux associatifs, ainsi on pourrait noter une commande de petit déjeuner de la manière suivante :
Qui sera chargé de la manière suivante :
En combinant les types de données de base dans les collections reconnues par YAML, on peut représenter quasiment toute structure de données. D'autre part, la représentation textuelle de ces données est très lisible et quasiment naturelle. Il est aussi possible de réaliser l'opération inverse, à savoir sérialiser des structures de données en mémoire sous forme de texte. Dans l'exemple suivant, nous écrivons sur la sortie standard un Dictionnaire Python :
Qui produira la sortie suivante :
Syntaxe de baseAprès cette brève introduction, voici une description plus exhaustive de la syntaxe YAML. ScalairesLes scalaires sont l'ensemble des types YAML qui ne sont pas des collections (liste ou tableau associatif). Ils peuvent être représentés par une liste de caractères Unicode. Voici une liste des scalaires reconnus par les parseurs YAML : Chaîne de caractèresVoici un exemple :
Qui est parsé de la manière suivante :
Le résultat de ce parsing nous amène aux commentaires suivants :
D'autre part, il est possible d'écrire des caractères Unicode à l'aide des notations suivantes :
EntiersVoici quelques exemples :
Qui est parsé de la manière suivante :
Nous constatons que les notations les plus courantes des langages de programmation (comme l'octal ou l'hexadécimal) sont gérées. A noter que toutes ces notations seront reconnues comme identiques par un parseur YAML et par conséquent seront équivalentes comme clef d'un tableau associatif par exemple. Nombres à virgule flottanteVoyons les différentes notations pour ces nombres :
Ce qui est parsé en :
Les notations classiques sont gérées ainsi que les infinis et les valeurs qui ne sont pas des nombres. DatesYAML reconnaît aussi des dates :
Qui sont parsées de la manière suivante :
Les types résultant du parsing dépendent du langage et du parseur, mais correspondent à des types naturels pour les temps considérés. DiversIl existe d'autres scalaires reconnus par les parseurs YAML :
Qui sera parsé en :
Bien sûr, le type des valeurs parsées dépend du langage et d'autres valeurs spéciales peuvent être reconnues suivant les langages et les parseurs. Par exemple, le parseur Ruby reconnaît les symboles (notés :symbole par exemple) et les parse en symboles Ruby. CollectionsIl existe deux types de collections reconnues par YAML : les listes et les tableaux associatifs. ListesCe sont des listes ordonnées, et qui peuvent contenir plusieurs éléments identiques (par opposition aux ensembles). Les éléments d'une liste sont identifiés par un tiret, comme suit :
Les éléments de la liste sont distingués grâce à l'indentation : le premier élément est identé de manière à ce que sa deuxième ligne soit reconnue comme faisant partie du premier élément de la liste. Cette syntaxe peut être comparée à celle de Python, si ce n'est qu'en YAML, les caractères de tabulation sont strictement interdits pour l'indentation. Cette dernière règle est importante et source de nombreuses erreurs de parsing. Il est important de paramétrer son éditeur de manière à interdire les tabulations pour l'indentation des fichiers YAML. Il existe une notation alternative pour les listes, semblable à celle des langages Python ou Ruby :
Cette notation, dite en flux, est plus compacte et permet parfois de gagner en lisibilité ou compacité. Tableaux associatifsAppelés Map ou Dictionnaires dans certains langages, ils associent une valeur à une clef :
La notation en flux est la suivante :
Qui est parsé de la même manière. Cette notation est identique à celle de Python ou Javascript et se rapproche de celle utilisée par Ruby. A noter qu'il est question que Ruby 2 utilise aussi cette notation. CommentairesIl est possible d'inclure des commentaires dans un document de la même manière que dans la plupart des langages de script :
A noter que ces commentaires ne doivent (et ne peuvent) contenir d'information utile au parsing dans la mesure où ils ne sont pas accessibles, généralement, au code client du parser. Documents multiplesDans un même fichier ou flux, on peut insérer plusieurs documents YAML å la suite, en les faisant commencer par une ligne composée de trois tirets (---) et en les termiant d'une ligne de trois points (...) comme dans l'exemple ci-dessous :
A noter que par défaut, les parsers YAML attendent un document par fichier et peuvent émettre une erreur s'ils rencontrent plus d'un document. Il faut alors utiliser une fonction particulière capable de parser des documents multiples (comme yaml.load_all() pour PyYaml par exemple). On peut alors extraire ces documents de manière séquentielle du flux. Syntaxe avancéeAvec la section précédente, nous avons vu le minimum vital pour se débrouiller avec YAML. Nous allons maintenant aborder des notions plus avancées dont on peut souvent se passer dans un premier temps. RéférencesLes références YAML sont semblables aux pointeurs des langages de programmation. Par exemple :
Donne, après parsing :
A noter qu'un alias, indiqué par une astérisque *, doit pointer vers une ancre valide, indiquée par une arobase &, sans quoi il en résulte une erreur de parsing. Ainsi le fichier suivant doit provoquer une erreur lors du parsing :
TagsLes tags sont les indicateurs du type de données. Par défaut, il n'est pas nécessaire d'indiquer le type des données qui est déduit de leur forme. Cependant, dans certains cas, il peut être nécessaire de forcer le type d'une donnée et YAML définit les types par défaut suivants :
Les tags correspondants commencent par deux points d'exclamation. Lors du parsing, on obtient les type suivants en Python :
A noter les deux types supplémentaires :
L'utilité des tags pour ces types par défaut est limitée. La vrai puissance des tags réside dans la possibilité de définir ses propres tags pour ses propres types de données. Par exemple, on pourrait définir son propre type pour les personnes, comportant deux champs : le nom et le prénom. On doit tout d'abord déclarer le tag au début du document, puis on peut l'utiliser dans la suite, comme dans cet exemple :
Nous verrons plus loin comment utiliser les tags avec les APIs Java et Python pour désérialiser des structures YAML en types de donnés personnalisés. Il est aussi possible de ne pas déclarer le tag et de l'expliciter dans le document, de la manière suivante :
DirectivesLes directives donnent des instructions au parser. Il en existe deux : TAGComme vu précédemment, déclare un tag dans le document. YAMLIndique la version de YAML du document. Doit être en en-tête du document, comme dans l'exemple ci-dessous :
Un parser doit refuser de traiter un document d'une version majeure supérieure. Par exemple, un parser 1.1 devrait refuser de parser un document en version YAML 2.0. Il devrait émettre un warning si on lui demande de parser un document de version mineure supérieure, comme 1.2 par exemple. Il doit parser sans protester toutes les versions égales ou inférieures, telles que 1.1 et 1.0. Jeu de caractères et encodageUn parser YAML doit accepter tout caractère Unicode, à l'exception de certains caractères spéciaux. Ces caractères peuvent être encodés en UTF-8 (encodage par défaut), UTF-16 ou UTF-32. Les parsers YAML sont capables de déterminer l'encodage du texte en examinant le premier caractère. Il est donc impossible d'utiliser tout autre encodage dans un fichier YAML et en particulier ISO-8859-1. APIs YAMLNous allons maintenant jouer avec les principales APIs YAML. JYamlJYaml est une bibliothèque OpenSource pour manipuler les documents YAML en Java. Le projet est hébergé par SourceForge et on trouvera sa page à l'adresse http://jyaml.sourceforge.net/. On trouvera sur ce site un tutoriel ainsi que les références de l'API. Utilisation de baseJYaml effectue un mapping par défaut des structures YAML en objets Java standards : il associe une liste à une instance de java.util.ArrayList, un tableau associatif à une instance de java.util.HashMap et les types primitifs de YAML à leur contrepartie Java. Ainsi, pour charger un fichier YAML dans un objet Java, on écrira le code suivant :
Par exemple, le fichier suivant :
Pourra être chargé en mémoire et affiché dans le terminal avec le source suivant :
Cela affichera dans le terminal :
Inversement, on peut sérialiser un objet Java dans un fichier YAML de la manière suivante :
Ainsi, on pourra par exemple sérialiser une structure d'objets Java avec le code suivant :
Ce code produira le fichier suivant :
A noter que ce dump est un peu décevant dans la mesure où certains types standards de YAML (comme les tableaux associatifs et les nombres à virgule flottante) sont sérialisés en types Java (comme java.util.HashMap et java.lang.Double). Un tel fichier ne sera pas chargé correctement en utilisant un autre langage de programmation (voire même une autre implémentation en Java). Usage avancéNous pouvons aussi travailler avec des types qui ne sont pas génériques et ainsi charger des instances de classes Java à partir de fichiers YAML. La première solution consiste à indiquer le type des objets avec des tags YAML. Ainsi, le fichier YAML suivant :
Sera-t-il chargé en utilisant les classes suivantes :
Et :
Ces classes respectent la convention JavaBean, à savoir qu'elles disposent d'accesseurs pour ses champs ainsi que d'un constructeur vide (sans arguments, implicite en Java). En le chargeant avec le source précédent, nous obtenons sur le terminal :
Il existe une autre solution pour charger ces objets sans avoir besoin de spécifier explicitement les types. Pour ce faire, il faut utiliser la méthode loadType() et lui passer le fichier YAML à charger ainsi que le type de l'objet racine du fichier. Ainsi, dans notre cas, nous pourrions écrire le fichier de commande de la manière suivante :
Et le charger avec le source suivant :
Cette manière de charger des types spécifiques est bien plus pratique car elle ne surcharge pas le fichier YAML avec les types Java, ce qui rend la portabilité entre langages nulle. Cependant, on pourra regretter l'obligation de déclarer le champ articles en tant que tableau d'Article. Si on le déclare du type List<Article>, JYaml charge les objets de la liste comme des instances de Map. Cependant, ceci est du au fait que l'information du type de la liste est perdu au runtime et donc JYaml ne peut connaître le type des éléments de la liste et les charge donc avec le type par défaut. Alias et ancresJYaml gère les alias et les ancres des fichiers YAML. Prenons l'exemple suivant :
On peut le charger avec le source suivant :
Et l'on constate que les alias et ancres ont été gérés correctement. Gestion des fluxIl est possible de sérialiser en YAML des objets Java dans un flux de la manière suivante :
Il existe un raccourci pour sérialiser une collection d'objets dans un flux de la manière suivante :
D'autre part, on peut désérialiser des objets Java à partir d'un flux YAML de la manière suivante :
Il existe un raccourci pour itérer sur les objets désérialisés d'un flux :
Fichier de configurationIl est possible de configurer JYaml dans un fichier, qui doit être nommé jyaml.yml et se trouver dans le répertoire courant où tourne l'application ou bien à la racine de son CLASSPATH. Voici un exemple d'un tel fichier :
Cette configuration permet en particulier de configurer des mappings entre tags YAML et classes Java. Dans l'exemple ci-dessus, le mapping suivant :
Permet de raccourcir le tag YAML permettant d'indiquer la classe Java utilisée pour la désérialisation. Ainsi, on pourra indiquer le mapping pour la classe com.blah.Company par un simple tag !company. D'autre part, ce fichier permet de lister des classes wrappers qui permettent de désérialiser des types particuliers dont les classes ne respectent pas la convention JavaBeans. On peut en trouver des exemples dans les sources du projet. Autres fonctionnalitésA noter que JYaml a plus à offrir, en particulier :
Cependant, JYaml semble soufrir de limitations, en particulier pour le parsing des dates qui ne sont pas toujours reconnues comme telles. PyYAMLPyYaml est une bibliothèque en Python permettant de gérer les fichiers YAML. On peut le télécharger sur le site http://pyyaml.org/ et l'on trouvera la documentation Documentation sur cette page. InstallationPyYaml peut utiliser la LibYaml écrite en C et très rapide ou bien une implémentation en pûr Python qui ne nécessite pas cette bibliothèque. Pour installer la bibliothèque, télécharger l'archive, la décompresser, se rendre dans le répertoire ainsi créé et taper python setup.py install, ou bien python setup.py --wihout-libyaml install pour ne pas utiliser la LibYaml. Utilisation de basePour charger un fichier YAML, dont le nom est passé sur la ligne de commande, dans un source Python, on pourra procéder somme suit :
La fonction load() prend en paramètre une chaîne d'octets, unicode, un fichier binaire ou texte. Les chaînes d'octets et les fichiers doivent être encodés en UTF-8 ou UTF-16. L'encoding est déterminé par le parser en examinant le BOM (Byte Order Mark), premier octet du fichier. Si aucun BOM n'est trouvé, l'UTF-8 est choisi. Si la chaîne ou le fichier contient plusieurs documents, on peut tous les charger à l'aide de la fonction yaml.load_all() qui renvoie un itérateur. On pourra donc écrire :
Pour sérialiser un objet Python en YAML, on pourra utiliser la fonction yaml.dump() :
La fonction yaml.dump() peut prendre un deuxième paramètre optionel qui doit être un fichier binaire ou texte ouvert. Elle écrit alors le résultat de la sérialisation dans le fichier. Pour sérialiser plusieurs objets Python dans un flux, vous pouvez utiliser ma fonction yaml.dump_all(). Cette fonction prend en paramètre une liste ou un itérateur. Les fonctions de dump prennent des paramètres supplémentaires pour indiquer des détails de formatage du YAML généré. On peut ainsi spécifier le nombre de caractères d'indentation à utiliser, le nombre de caractères par ligne, etc. En particulier, default_flow_style indique si on utilise le style par flux pour les listes et les tableaux associatifs. Ainsi, le code suivant :
Produit une représentation utilisant la notation en flux :
Alors que le code :
Produit une notation en liste :
Sérialisation et désérialisation de classes PythonIl est possible de déclarer explicitement le type Python à utiliser pour désérialiser une structure YAML donnée à l'aide d'un tag YAML. Par exemple :
Produit la sortie suivante dans le terminal :
Inversement, une classe Python peut être sérialisée en un flux YAML de la manière suivante :
Ce code produit la sortie suivante :
Nous voyons que PyYaml a sérialisé la classe Python en utilisant la même notation à base de tag YAML. A noter que la construction d'objets Python arbitraires à partir de sources non fiables (typiquement venant d'internet) peut être dangereuse. C'est pourquoi PyYaml propose la fonction yaml.safe_load() qui limite la construction d'objets aux types de base de YAML. La notation vue ci-dessus permet de désérialiser des structures YAML en instances de classes Python arbitraires. Cependant, il est possible d'utiliser une notation plus simple en faisant hériter notre classe Personne du parent yaml.YAMLObject comme suit :
Cela produit sur la console :
Bien sûr, on peut désérialiser cette structure YAML en utilisant la fonction yaml.load(). Cependant, cette notation, qui comporte le tag YAML !personne est bien plus élégante que la précédente, qui indique la nom qualifié de la classe et non un nom symbolique plus court et plus parlant. Il y a un peu de magie derrière tout cela : la classe Personne s'enregistre en tant que type Python associé au tag YAML !personne ce qui permet cette notation élégante. Il existe un moyen d'associer une expression rationnelle à une classe Python de manière à ce que, par exemple, la notation 3d6 soit associée à l'appel au constructeur Dice(3, 6). Je vous laisse le soin de creuser la question dans la section adéquate de la documentation de PyYaml. ConclusionMaintenant que nous avons une bonne idée de ce qu'est YAML, nous pouvons le comparer à des technologies proches telles que JSON et XML. YAML et JSONCes deux formats de représentation textuelle de données sont très proches, à tel point qu'à partir de la version 1.2 de la spécification YAML, tout document JSON est un document YAML valide (et peut donc être parsé par un parser YAML conforme à la version 1.2 de la spécification. Cependant, YAML bénéficie d'une plus grande lisibilité. D'autre part, il n'est pas lié à un langage de programmation particulier (comme l'est JSON avec JavaScript). YAML et XMLCes deux technologies sont assez différentes et YAML ne peut pas faire tout ce que peut faire XML. En particulier, YAML n'est pas adapté comme format de texte structuré, on ne pourrait donc pas remplacer XML par YAML pour écrire du DocBook par exemple. Par contre, dans le domaine de sérialisation de données, YAML est bien plus spécialisé et puissant de par sa reconnaissance des types primitifs usuels et des structures de données telles que listes et tableaux associatifs. D'autre part, YAML possède un énorme avantage en ce qui concerne la syntaxe et sa lisibilité, même par une personne qui n'a pas connaissance des spécification YAML. Ainsi, dans le domaine des fichiers de configuration qui doivent être manipulés par des gens qui n'ont pas de connaissance particulière, la syntaxe naturelle de YAML est-elle un énorme avantage. Utilisation de YAMLLes deux utilisations principales de YAML sont :
J'espère que cette présentation de YAML vous aura donné l'envie d'utiliser ce format de données dans vos propres applications et de répandre la bonne parole autour de vous ! RessourcesVoici quelques URLs utiles relatives à YAML :
Dernière mise à jour : 2009-02-27 |