Affichage des articles dont le libellé est xml. Afficher tous les articles
Affichage des articles dont le libellé est xml. Afficher tous les articles

lundi 16 mai 2011

XSD - Pas évident

Assez moches, finalement, les schémas XSD. Documentation absconse, grammaire peu claire, et bien trop de façons de générer la même chose.

C'est que j'essaie de reprendre le bon vieux mlxsd et de le rendre un peu plus respectueux des standards. Et c'est pas de la tarte.

dimanche 21 juin 2009

Développer son MMORPG - Persistance!

3ème opus de la série, où l'on parlera de la partie "persistance" dans "univers persistants". Que ce soit pour définir le monde ou pour récupérer l'état du serveur après une panne ou une séance de maintenance, pouvoir compter sur sa couche de persistance est indispensable.

Base de données relationnelle? Listes de clés-valeurs? XML? Gros fichiers binaires? Une chose est sûre, il faudra bien d'une manière ou d'une autre sauver ses précieuses données. AdH a fait très tôt le choix de la base de données relationnelle (Postgresql, pour être précis, mais cela aurait pu être Mysql, ou encore Oracle si on avait les sous).

Voici quelques avantages des bases de données relationnelles:

  • Les données sont accessibles directement à partir des outils fournis avec le système. Changer une position ou réparer un inventaire, c'est juste quelques lignes de SQL.

  • Les données sont en sécurité! Ces systèmes sont stables, utilisés dans d'innombrables projets à travers le monde. Les données sont centralisées, et les outils sont fournis pour sauvegarder et restaurer la base.

  • Il existe probablement déjà une bibliothèque pour accéder à la base depuis votre langage de programmation préféré.

  • Les données sont accessibles pour d'autres applications. Il est très aisé d'extraire des statistiques pour les afficher sur une page Web, ou encore de générer des rapports sur l'activité des joueurs.

Et quelques inconvénients:
    Ecrire le schéma prend du temps, et écrire la couche relationnelle dans le code en prend également.
  • Les changements de schéma peuvent être lourds: une colonne qui change dans une table, et il faut potentiellement changer ses index, ses procédures stockées, son SQL dans le code, et enfin le code lui-même.

  • Il faut installer une base pour faire tourner le serveur, en s'assurant que les binaires sont bien à la même version que le SQL.

Ces derniers temps, l'on a beaucoup parlé des bases de données de type clé-valeur, souvent utilisées dans le Web. Très dynamiques, elles permettent d'étendre le modèle de données très simplement, et sont d'une extensibilité sans pareille. Rajouter des paramètres à ses objets devient un jeu d'enfant. Par contre, être aussi dynamique a un coût: la base peut vite devenir bordélique, et les clés et les valeurs de plus en plus complexes et difficiles à interpréter. Sauver du XML en place des valeurs peut être une bonne manière de permettre de sauver simplement des données complexes, mais on se retrouve alors à définir un schéma et donc perdre tous les avantages du dynamisme.

Enfin, reste la solution du bon vieux fichier. Facile à lire, un peu plus difficile à modifier si l'on veut juste changer un petit quelque chose au milieu, il a l'avantage de ne nécessiter l'installation d'aucun logiciel supplémentaire. Par contre, la protection des données, la définition du schéma, leur accès en dehors de l'application, tout cela est entre vos mains. Ça fait un peu beaucoup, non?

Une fois que l'on est content de sa couche de persistance, encore faut-il décider de quand y accéder. D'un côté, il est possible de tout charger en mémoire au démarrage, et de tout sauver de temps en temps (mettons toutes les 10 minutes, dans un thread séparé pour un maximum de performances). C'est rapide. Mais cela veut dire qu'il faut mapper strictement toutes les données de la couche de persistance vers les objets du code. C'est du boulot! Enfin, il faut trouver le bon équilibre pour les sauvegardes: trop souvent, et les performances souffrent. Trop rarement, et un crash inopiné zappe une bonne partie de l'avancement des joueurs, à leur grande colère. Et enfin, arrive un moment où tout ne tient plus en mémoire... De l'autre côté, l'on peut accéder la couche de persistance uniquement quand on a besoin d'une donnée, et l'écrire à chaque fois qu'elle change. On ne perd rien en cas de crash, par contre, le système peut devenir très lent.

Il s'agit de trouver le bon équilibre entre les deux: quelles données peuvent être chargées à la volée? Peut-on les cacher en mémoire? Quelles donnés ont besoin d'être écrites immédiatement, quelles autres peuvent attendre? Comment s'assurer que tout est synchronisé? Typiquement, on fera la distinction entre les données importantes (une transaction entre deux joueurs, un avancement de quête) et celles qui le sont moins (monstre tué, ressources prises). Personne ne se plaindra si après un crash, une créature n'est pas exactement à la même position, ou si la mine contient encore les minerais que l'on a extrait (mais attention quand même, si un joueur découvre comment faire crasher le serveur, il pourrait se fabriquer une mine infinie?).

Enfin, quand il s'agit de coder tout ça, rappelez vous que moins on en écrit, mieux on se porte. Profitez au maximum des bibliothèques, écrivez du code générique pour binder vos données le plus efficacement possible, ou, encore mieux, utilisez des couches de persistance toutes faites (Hibernate?). Et sauvegardez. Tout. Tout le temps.

lundi 15 juin 2009

Développer son MMORPG - Communications client serveur

Le combat épique, le duel au sommet: binaire ou XML? AdH a beaucoup changé non seulement sur ce choix de base, mais également sur la manière de l'implémenter.

Récapitulons:


  • Performances: Le binaire est le grand vainqueur. Difficile de faire mieux, puisqu'il n'y a généralement aucune transformation entre les données internes du programme et les données envoyées sur le fil. De son côté, XML devra transformer toutes les données en texte et rajouter des balises un peu partout, et ça prend du temps.

  • Taille: Le XML est généralement plus gros, mais l'on peut parfois avoir des surprises! Par exemple, un petit entier au format texte prendra moins de place qu'en binaire. Comparez: <l>1,2,1,1,1,2,5,2,1,2</l> fait 27 octets, mais passer ces 10 entiers 32 bits en binaire prendrait 40 octets! Certes, les puristes me feront remarquer que séparer les entiers par des virgules n'est pas franchement du XML. Et enfin, compressez!

  • Compatibilité Voilà un sujet dans lequel le XML est roi. Ce n'est que du texte, dans un encodage défini dans le message, il est très difficile de se tromper. Alors qu'en binaire, l'on va devoir parler de grand endian contre petit endian, de taille des types de base, de conventions pour passer la taille d'une chaine ou d'un tableau. Et si ça marche bien chez vous, qui sait ce que ça donnera quand vous enverrez le message à un Mac 64 bits?

  • Lisibilité: Le XML est le gagnant toutes catégories. On dira ce que l'on voudra sur la lecture de gros fichiers XML, mais c'est quand même mieux que de regarder des colonnes d'hexadécimal.

  • Maintenance: C'est sur ce point capital qu'avoir une bonne stratégie fera toute la différence, que ce soit en XML ou en binaire. Tout d'abord en XML: n'écrivez pas votre parseur, malheureux! N'écrivez pas non plus le code qui traduit les structures XML en structures de votre langage. Gé - Né - Rez. De nos jours, pléthore d'outils, tels Codesynthesis XSD et gSoap pour le C++, Microsoft XSD pour .NET, JAXB pour Java, et même mlxsd pour Ocaml (non, je déconne, n'utilisez surtout pas mlxsd pour quoi que ce soit de serieux!), vous font de belles classes qui savent se sérialiser toutes seules à partir d'un beau schéma. Changement de données? On change le schéma, on génère, on compile, on résout toutes les erreurs de compilation, on recompile (car seuls les cons pilent, ah ah!), et voilàte, ça marche! Et pour le binaire, alors? Attendez, on y arrive.


Par chance, comme pour la plupart des problèmes récurrents, il existe multitudes de frameworks qui vous les résoudront, tout en apportant une panoplie de difficultés nouvelles. Mais au moins, l'on ressent un certain soulagement à changer sa peine.

Blague à part, ICE, Corba, gsoap et beaucoup d'autres utilisent l'idée que les messages doivent être définis de manière centralisée via un langage de description, utilisé ensuite par des générateurs de code. Et ça, c'est un concept qui marche aussi bien en binaire qu'en XML. En plus, pour ceux qui auraient quand même décidé de coder le client et le serveur dans des langages différents, il suffit que le générateur de code puisse causer dans ces deux langages, et on est sortis d'affaire.

AdH a démarré sur du binaire, puis est passé au XML, revenu sur le binaire puis fait un dernier petit tour en XML avant de revenir fermement en binaire. N'ayant pas à supporter plusieurs langages, j'ai été faible, et j'ai décidé d'écrire moi-même mon générateur de code. L'avantage principale de mon approche est que je passe directement dans la description les types C++ supportés par mon sérializeur, ce qui veux dire que je peux passer des types très forts, tel mon AvatarId dans l'exemple du post en lien. Tous les autres frameworks m'auraient probablement obligé à passer un entier et perdre le typage, ou à décrire chaque type fort séparément, ce qui aurait sérieusement alourdi la chose.

En conclusion, choisir du XML ou du binaire a beaucoup moins d'importance que la manière dont on va maintenir et utiliser sa structure de messages. De plus en plus, il me semble indispensable d'avoir un bon framework qui pourra générer sans erreurs des brouettes de code ennuyeux. Choppez donc en un, et faites le bosser! Et si vous décidez de l'écrire vous-même, assurez vous que les fonctionnalités que vous rajoutez par rapport à un framework existant vous feront vraiment gagner du temps. Depuis, j'ai rajouté à mon générateur de code l'option de lier certains champs à la base de données, me permettant en quelques lignes de code d'extraire mon objet de la base et de l'envoyer sur le réseau!

lundi 25 mai 2009

Une update de l'updator

Je me suis concentré ce week-end sur la vérification et le téléchargement des fichiers, et l'ensemble commence à ressembler à quelque chose.

Au moment où l'utilisateur tente de se connecter à un serveur, l'updator télécharge un fichier XML dont l'adresse lui a été indiquée par le serveur. Ce fichier XML contient la liste des ressources, et leur signature SHA1.

L'updator regarde alors les fichiers présents localement.

- Si le fichier est présent en distant, mais pas en local, il le télécharge.
- Si le fichier est présent en distant et en local, il calcule le SHA1 du fichier local et la compare avec la signature du fichier qu'il est censé recevoir. Si les signatures diffèrent, il télécharge le fichier distant et écrase le fichier local.
- Enfin, si le fichier est présent en local mais pas en distant, il le supprime.

Voilà par exemple ce que pourrait voir l'utilisateur lors de la première connexion:



L'utilisateur se connecte pour la deuxième fois, et tous les fichiers étant déjà là, le processus est beaucoup plus rapide, il suffit de vérifier les signatures.



Allons donc bidouiller le fichier cannon.obj, par exemple en supprimant une ligne. A la prochaine mise à jour, l'updator détecte que les signatures sont différentes, et télécharge à nouveau le fichier.



Le tout est fait dans un petit thread d'arrière plan, qui envoie régulièrement des messages à la GUI pour mettre à jour les logs et la barre de progression, avec l'aide des "custom events" de wxWidgets.

A priori, il est possible d'obtenir la taille du fichier avant téléchargement, ce qui me permettra d'ajouter une autre barre de progression pour chaque fichier, avec pourquoi pas la vitesse de téléchargement.

Il faudra probablement se battre un peu pour que tout cela tourne gentiment à la fois sous Linux et sous Windows, mais une fois que ce sera fait, il deviendra possible de faire une vraie release d'AdH!

mercredi 8 avril 2009

Encore de la génération de code

Moi qui d'habitude déteste les générateurs de code, voilà que je m'y mets!

Bref, je me suis ré-attaqué à mon générateur de messages, avec l'idée d'y ajouter aussi le code de base de données, pour les messages qui en sont directement extraits. J'ai refactoré violemment, en faisant quelque chose de beaucoup plus construit, et il ne reste plus que quelques détails à implémenter, dont créer un nouveau schéma XML qui permette de définir pour chaque champ s'il doit être transmis et s'il est lié à la base de données.

Le problème, c'est que plus je rend ma génération de code ajustable, plus mon XML va grossir et s'approcher d'une génération à la main!

samedi 14 mars 2009

Un outil pour générer tous mes messages

Comme vous le savez peut-être, réécrire tout le temps (presque) le même code m'ennuie à n'en plus finir. Or, à chaque fois qu'il me fallait écrire la petite classe qui représente un paquet d'informations à passer entre le client et le serveur, je me retrouve à écrire presque la même chose! Quelques inclusions, un constructeur par défaut, un constructeur qui prend toutes mes données, un empaqueteur de données et son inverse, et quelques getters.

Une solution possible aurait été d'utiliser de grandes maps de boost::variant. Pourquoi ne pas l'avoir fait, moi, le chantre du code dynamique? Peut-être l'envie d'avoir de bons getters, et de ne pas s'embêter avec tous les types qu'il aurait fallu mettre dans le variant. Bref, c'est fait.

Je me suis donc décidé à prendre exemple sur le wsdl, et écrire un petit générateur de code pour me décharger de toute cette ennuyeuse paperasse, à partir d'un fichier de description de l'interface.

Ce qui fut dit fut fait, et je suis maintenant l'heureux auteur d'un petit programme en Ocaml tout bête mais qui va me permettre de me concentrer sur le contenu plutôt que de m'embêter avec la syntaxe.

Passons par exemple la définition suivante:


<messages>
<message name="PingMsg">
<item name="data" type="std::string"/>
</message>
<message name="RadioMsg">
<item name="callsign" type="std::string"/>
<item name="message" type="std::string"/>
<item name="system" type="bool"/>
</message>
<message name="LoginMsg">
<include file="Ids.h"/>
<item name="login" type="std::string"/>
<item name="password" type="std::string"/>
<item name="avatarId" type="AvatarId"/>
</message>
</messages>


L'on remarquera le node include dans la définition du LoginMsg, qui permet d'ajouter des inclusions supplémentaires pour les types non standard, en l'occurence mon AvatarId.

L'on passe à la moulinette, et voilà le travail:


$ wc -l *Msg.{h,cpp}
26 LoginMsg.h
21 PingMsg.h
25 RadioMsg.h
47 LoginMsg.cpp
31 PingMsg.cpp
47 RadioMsg.cpp
197 total


La génération de code avec Ocaml est toujours un plaisir, la bibliothèque standard est bourrée de fonctions particulièrement utiles dans ce contexte: String.uppercase, String.capitalize, String.concat, et l'ensemble du programme tient pour l'instant en 117 lignes. Bien entendu, la génération du code est extrêmement rapide, je doute avoir un jour suffisamment de messages pour qu'elle ne paraisse plus instantanée.

Pour les curieux, le code est , mais attention, c'est très lourdement adapté à mes besoins, et donc probablement pas réutilisable en l'état.

mercredi 3 septembre 2008

Le Xml est comme la violence: si cela ne résoud pas votre problème, mettez en plus

Il me semblait bien avoir ici parlé de xsd, générateur de code C++ à partir d'un schéma xml, et qui a fait depuis peu son arrivée dans Debian, à ma plus grande joie.

En effet, je m'efforce depuis un certain temps à n'utiliser que des paquets directement disponibles sur les serveurs officiels de ma distribution. Auparavant, preux chevalier du ./configure make makeinstall, je remplissais ainsi mon /usr/local avec des trucs pas forcément faciles à retirer ensuite, provoquant d'innombrables soucis de compatibilité avec des logiciels plus récents qui s'installent via les mises à jour de la distribution. C'est maintenant fini, je n'installe que via aptitude. Et s'il y a un logiciel que je veux vraiment compiler moi-même, je le garde dans mon répertoire (./configure --prefix=~/local est mon ami), là où il est bien moins risqué de jouer du rm à tout va.

Mais je m'égare, et je reviens à xsd. Pour avoir le malheur d'utiliser tous les jours la Concurrence (non, pas de noms!), je puis vous dire que xsd est un plaisir à utiliser, et que j'apprécie tout particulièrement sa légèreté. On génère, on compile, la seule dépendance étant xerces. la Concurrence, donc, souvent requiert une lourde bibliothèque additionnelle, des DLLs, et génère des DLLs. Bref, l'horreur. Car, c'est bien connu, Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl DLL fhtagn, ce qui veux dire "Dans sa demeure de R'lyeh, le défunt Cthulhu rêve de DLLs et attend", ce qui prouve bien leur origine épouvantable.

Mais la raison première de ce post est de publier ces quelques petites lignes, qui, si elles ne servent à personne d'autre, seront au moins pour moi un bon pense-bête. C'est l'ajout d'une cible omake pour générer mon schéma.


.PHONY: schema
schema:
xsdcxx cxx-tree --root-element-all --generate-serialization --hxx-suffix .h
--cxx-suffix .cpp --namespace-map =Schema xsd/*.xsd


Attention, les deux lignes "xsdcxx" sont censées n'en être qu'une seule, mais sinon c'était trop long et c'était le bordel pour faire tenir tout ça sur le blog. Attention², xsdcxx est le nom de xsd sous Debian, puisqu'il existait déjà un utilitaire nommé xsd.

--root-element-all permet que chaque élément défini au plus haut de la hiérarchie puisse être un élément racine, c'est à dire ayant des fonctions pour sérialiser. Si vous n'avez qu'un seul document principal, vous pouvez vous en tamponner le coquillard, mais pour moi qui définit parfois plusieurs messages indépendants par fichier, c'est primordial.
--generate-serialization Plutôt utile pour faire quoi que ce soit avec le fichier sans avoir besoin de passer par les fonctions xerces.
--hxx-suffx et --cxx-suffix Moi, j'aime bien que mes fichiers soient appelés tous pareil. Mes en-têtes sont en .h, mes sources en cpp. Et pas autrement!
--namespace-map Les namespace XML, encore un concept tordu comme seul XML peut les corrompre. Cette commande permet de définir comment transformer un namespace XML en un namespace C++. Pour éviter de finir comme un héros Lovecraftien (mais le XML, c'est plus Dieux Extérieurs que Grands Anciens), je ne définis aucun namespace dans mon XML, mais j'indique à xsd que je veux avoir tout le code dans un namespace à moi.

À noter, la commande --output-dir qui permet d'éviter que tout le schéma ne se déverse dans le répertoire courant.

dimanche 11 novembre 2007

Mlxsd : Comme sur des roulettes!

Tests concluants! La génération est incroyablement plus facile qu'avant, bien moins sujette à erreurs, et surtout les éventuelles futures modifications du schéma se traduiront immédiatement par une erreur de compilation en cas d'oubli. Voilà un outil qu'il est utile!

Allez, petite démonstration:


let item1 =
{
Adh.Ident_item_t.id = 33;
Adh.Ident_item_t.name = "Raoul";
Adh.Ident_item_t.self = true
}
and item2 =
{
Adh.Ident_item_t.id = 55;
Adh.Ident_item_t.name = "Christine";
Adh.Ident_item_t.self = false
}
in
{ Adh.adh = Adh.Adh_t.Ident {Adh.Ident_t.ident_item = [item1; item2]} }

se traduira directement en

<adh>
<ident>
<ident_item>
<id>33</id>
<name>Raoul</name>
<self>true</self>
</ident_item>
<ident_item>
<id>55</id>
<name>Christine</name>
<self>false</self>
</ident_item>
</ident>
</adh>


Il ne reste maintenant qu'à porter le serveur Adh sous ce système, et basta!

samedi 10 novembre 2007

mlxsd bientôt dans les bacs!

Revision 333 : mlxsd code complete

Enfin, mon générateur de binds Xml en ocaml est fini! Au final, ce fut plutôt plus simple que ce que j'avais imaginé, développer par petits incréments et beaucoup de tests unitaires a été la technique gagnante. Maintenant, il reste à tester l'outil sur les messages XML d'AdH, et à faire une bonne batterie de tests pour s'assurer que les messages générés par un côté seront bien lisibles par l'autre.

Le développement de fonctionnalités va pouvoir reprendre!

mardi 30 octobre 2007

Compressons du XML dans la joie et la bonne humeur

Notre bande passante étant limitée, l'efficacité des communications est primordiale à une bonne montée en charge du serveur. Cependant, nous avons choisi pour des raisons de simplicité de coder nos communications en XML, qui est un format plutôt verbeux. C'est là que la magie de la compression se met en place!

L'on pourrait en théorie simplement compresser chaque message XML vers du binaire, et envoyer directement le binaire vers l'autre côté. Cependant, cette solution présente le désavantage de nécessiter un changement complet au niveau de la couche communication: il faut par exemple envoyer un en-tête avec la taille du paquet, et il est peu commode d'envoyer sur un même canal du xml compressé et non compressé.

La solution standard est de mettre le xml compressé à l'intérieur d'un autre message xml. Par exemple: "<adH><auth><login>mon login</login><password>mon password</password></auth></adh>" compressé pourra ressembler à ceci: "<adh>s0lM8bCzSSwtybCzyclPz8yzy83PUwCzbPQhA
jYFicXF5flFKWApGMdGHy5sow/Rr5+YkmEHAA</adh>"

Pour rester dans la norme, il est important que le message compressé soit composé de caractères ASCII, ce dont se moquent éperdument la plupart des systèmes de compression (au hasard, l'excellent zlib). Là encore, la solution standard est d'encoder le binaire en base 64, comme on le ferait pour un fichier attaché dans un e-mail. L'encodage en base 64 contrecarre malheureusement un peu la compression, puisqu'un message encodé prendra environ 4/3 de la taille de l'original, mais zlib derrière est très performant, ce qui permet de faire de très bons gains de bande passante quand les messages dépassent une certaine taille. Pour les messages plus petits, comme par exemple le message de login donné plus haut en exemple, le généré, comme vous pouvez le voir, est plus grand que le message non compressé. Dans ce cas, puisqu'il est si facile d'envoyer du xml compressé ou non, l'on gardera le message tel quel.



Le graphe suivant a été réalisé en compressant via zlib puis encodage base 64 un message XML de test, et en comparant la taille du message compressé à celle du message original. L'on voit bien que le taux devient intéressant (inférieur à 1) à partir de 200 octets. C'est probablement dans cette zone qu'il faut définir le seuil à partir duquel l'on compressera la trame.

Il est également intéressant de noter à quel point la compression est efficace pour des fichiers plus gros: une trame de 1500 octets sera compressée à 358 octets!

lundi 29 octobre 2007

Mlxsd : Second Generation

Eh bien, ce fut un dimanche productif. Le support pour les choix est implémenté via des constructeurs de types et des types sommes, ce qui est probablement la manière la plus propre et la plus efficace de gérer la chose (les implémentations C++ utilisent généralement d'horribles isPresent(), ou renvoient des pointeurs pouvant être nuls, vive le typage fort!).

L'implémentation en elle même a été plutôt facile, puisqu'il ne s'agissait que d'un ajout. Le code à modifier était donc très réduit. Mais c'est maintenant que tout se gâte, avec le support des cardinaux. Ce sera beaucoup plus sanglant, et c'est malheureusement nécessaire, beaucoup de messages ayant besoin de contenir des listes. L'implémentation en sera également moins efficace, puisqu'il faudra lire les éléments les uns après les autres et les comparer au modèle, plutôt que de faire du pattern matching sur toute la structure.

En guise de simplification, j'ai déjà l'intention de ne supporter que 3 cas: le cas normal (un seul élément), le cas optionnel (zéro ou un élement), et le cas de liste (de 0 à un nombre non déterminé d'éléments). Tous les autres cas (par exemple, 3 éléments exactement, ou au moins 7 éléments) seront assimilés à une liste. De toutes façons, s'il y avait vérification à faire, elle serait au niveau de l'analyseur xml, contre le schéma (et xml-light n'a pas le support des schémas, donc pas de remords à avoir).

Si cette étape est complétée en temps et en heure, je pourrai enfin m'attaquer au système de personnages!

dimanche 28 octobre 2007

Mlxsd : First Generation

Eh oui, la première génération complète de ma spécification XSD restreinte a vu le jour! Voilà un exemple simple de xsd, et du code ocaml généré (bon, d'accord, j'avoue, j'ai indenté à la main le code généré pour faciliter la lisibilité, mais bon, ça ne va pas changer à la compilation!):

simple.xsd


<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:complexType name="v_t">
<xs:sequence>
<xs:element name="x" type="xs:integer"/>
<xs:element name="y" type="xs:integer"/>
<xs:element name="z" type="xs:integer"/>
</xs:sequence>
</xs:complexType>

<xs:element name="v" type="v_t"/>

</xs:schema>


simple.ml

module V_t =
struct

type t =
{
x: int;
y: int;
z: int
}

let from_xml xml =
match xml with
[Xml.Element ("x", [], [Xml.PCData x]);
Xml.Element ("y", [], [Xml.PCData y]);
Xml.Element ("z", [], [Xml.PCData z])] ->
{x = int_of_string x;
y = int_of_string y;
z = int_of_string z}
| _ -> raise Not_found

let to_xml t =
[Xml.Element ("x", [], [Xml.PCData (string_of_int t.x)]);
Xml.Element ("y", [], [Xml.PCData (string_of_int t.y)]);
Xml.Element ("z", [], [Xml.PCData (string_of_int t.z)])]

end


type v = { v : V_t.t }

let v_from_xml xml =
match xml with
Xml.Element ("v", [], v) -> { v = V_t.from_xml v }
| _ -> raise Not_found

let v_to_xml t =
Xml.Element ("v", [], V_t.to_xml t.v)


Tout cela suivi bien entendu du petit exemple d'utilisation:


# #load "xml-light.cma";;
# #load "simple.cmo";;

# Simple.v_to_xml;;
- : Simple.v -> Xml.xml = <fun>
# Simple.v_from_xml;;
- : Xml.xml -> Simple.v = <fun>

# let input = "<v><x>9</x><y>8</y><z>7</z></v>";;
val input : string = "<v><x>9</x><y>8</y><z>7</z></v>"

# let xml = Xml.parse_string input;;
val xml : Xml.xml =
Xml.Element
("v", [],
[Xml.Element ("x", [], [Xml.PCData "9"]);
Xml.Element ("y", [], [Xml.PCData "8"]);
Xml.Element ("z", [], [Xml.PCData "7"])])

# let s = Simple.v_from_xml xml;;
val s : Simple.v =
{Simple.v = {Simple.V_t.x = 9; Simple.V_t.y = 8; Simple.V_t.z = 7}}

# Simple.v_to_xml s;;
- : Xml.xml =
Xml.Element
("v", [],
[Xml.Element ("x", [], [Xml.PCData "9"]);
Xml.Element ("y", [], [Xml.PCData "8"]);
Xml.Element ("z", [], [Xml.PCData "7"])])

# Xml.to_string (Simple.v_to_xml s);;
- : string = "<v><x>9</x><y>8</y><z>7</z></v>"


C'est maintenant que les difficultés vont commencer: le support des choix, et des cardinaux!

samedi 27 octobre 2007

Mise en abîme

En plus bien sûr des résultats espérés (c'est à dire au final un AdH de meilleure qualité, et plus rapidement sur nos disques durs), le développement de ce petit générateur xsd => xml est très intéressant à coder. Avoir non plus à réfléchir à "Qu'est-ce que ce code va faire?", mais "Qu'est-ce que ce code généré par ce code va faire?" est un exercice qui titille gentiment les neurones.

Pour l'instant, les capacités du programme sont réduites au strict minimum: pas de types imbriqués, pas de cardinaux (tous les éléments sont présent exactement une fois), pas de choix... Du fait, ces restrictions alègent beaucoup le code: le générateur est maintenant aux trois-quarts complet (il ne rèste que l'écriture du xml à partir des types ocaml), avec une taille d'environ 200 lignes de code (le double en comptant les tests unitaires).

L'ajout des choix d'abord, puis des cardinaux, va bien sûr alourdir la chose. Mais pour un début, c'est un début.

lundi 22 octobre 2007

Un générateur de XML en Ocaml

Il y a longtemps que ce genre d'outils existe en C++: Codesynthesis XSD, ou encore Roguewave Leif vont lire un schéma XML (format xsd) et générer des classes C++ correspondant aux structures définies.

Je n'ai pas trouvé d'outil équivalent en ocaml (du moins, pas que j'aie compris), et le code du serveur d'AdH était donc parsemé de bouts de code utilisant xml-light pour passer du xml aux types internes et inversement. Aujourd'hui, j'en ai donc eu assez, et je me lance dans l'aventure de l'écriture d'un mini-générateur xsd ocaml basé sur xml-light. Je n'ai aucune intention de supporter l'ensemble de la spécification xsd, et m'en tiendrai donc à ce dont les fichiers xsd d'AdH nécessitent. Pour l'instant, j'ai les types de base et quelques tests unitaires (le code est visible ici). C'est un peu dommage d'avoir à s'arrêter en milieu de développement pour faire ce genre d'outil, mais si tout fonctionne comme prévu, c'est beaucoup de temps gagné à la clé, pour la génération et la lecture de xml, bien sûr, mais également par la réduction du nombre d'erreurs de communication possibles, puisque à la fois le code C++ et le code Ocaml seront dérivés (correctement, on l'espère) des mêmes schémas.