Le XML est aujourd'hui un format d'échange de données très utilisé. Il possède de nombreux avantages : il est standard, simple, et surtout facile à lire. Il peut être lu par un homme, mais ce qui le plus intéressant c'est qu'il peut être lu par un ordinateur. En effet, la puissance du XML repose sur le fait qu'il peut être analysé par un programme et le contenu de ce flux est compris par la machine. La structure de ce langage permet en effet de comprendre les relations entre toutes ces données.
De ce fait, les programmeurs ont réalisé de nombreuses API permettant d'accéder aux données XML à travers leurs langages favoris (DOM et SAX par exemple en Java). Néanmoins, ces dernières ont des inconvénients. Tous d'abord, il faut étudier et apprendre une nouvelle API. Puis, ils sont trop généraux. C'est-à-dire que le programmeur doit adapter le code à son application à chaque fois qu'il veut accéder à des données XML. Ensuite, il faut qu'il crée lui-même toutes les classes permettant de gérer ces nouvelles données dans son programme, cela lui prend donc beaucoup de temps.
Pour remédier à ces inconvénients, il existe le « Data Binding » également appelé en français : association de données. En java, Sun a réalisé une API nommée JAXB (Java Architecture for XML Binding) pour simplifier les processus de transformation d’objets Java en fichier XML, et de fichier XML en objets Java.
JAXB est une spécification qui permet de faire correspondre un document XML à un ensemble de classes et vice et versa via des opérations de sérialisation/désérialisation nommée marshaling/unmarshaling.
JAXB permet aux développeurs de manipuler un document XML sans à avoir connaître XML ou la façon dont un document XML est traitée comme cela est le cas avec SAX, DOM ou STAX.
La manipulation du document XML se fait en utilisant des objets précédemment générés à partir d'une DTD pour JAXB 1.0 et d'un schéma XML du document à traiter pour JAXB 2.0.
Le Data Binding est une technologie permettant d'automatiser la transformation d'un modèle de données en un modèle de données objets dans un langage de programmation. Autrement dit, il permet par exemple de convertir les fichiers XML en instances de classes Java.
Pour réaliser cela, il y a trois étapes :
Le schéma suivant résume assez bien le principe : un document XML suit les règles de grammaire du « schema », ce dernier une fois compilé permet de créer une classe correspondante. Cette dernière permettra de créer une instance d'objet correspondant :
JAXB est l'acronyme de Java Architecture for XML Binding. Le but de l'API et des spécifications JAXB est de faciliter la manipulation d'un document XML en générant un ensemble de classes qui fournissent un niveau d'abstraction plus élevé que l'utilisation de JAXP (SAX ou DOM).
Avec ces deux API, SAX et DOM, toute la logique de traitements des données contenues dans le document est à écrire.
§
JAXB au contraire fournit un outil qui analyse un schéma XML et génère à partir de ce dernier un ensemble de classes qui vont encapsuler les traitements de manipulation du document. Le grand avantage est de fournir au développeur un moyen de manipuler un document XML sans connaître XML ou les technologies d'analyse. Toutes les manipulations se font au travers d'objets java. Ces classes sont utilisées pour faire correspondre le document XML dans des instances de ces classes et vice et versa : ces opérations se nomment respectivement unmarshalling et marshalling.
JAXB 2.0 a été développé sous la JSR 222 et elle est incorporée dans Java EE 5 et dans Java SE 6. Les fonctionnalités de JAXB 2.0 par rapport à JAXB 1.0 sont :
En plus de son utilité principale, JAXB 2.0 propose d'atteindre plusieurs objectifs :
L'utilisation de JAXB implique généralement deux étapes :
La sérialisation d'un graphe d'objets Java est effectué par une opération dite de mashalling. L'opération inverse est dite d'unmashalling. Lors de ces deux opérations, le document XML peut être validé.
L'API JAXB propose un framework composé de classes regroupées dans trois packages :
JAXB 2.0 utilise de nombreuses annotations définies dans le package javax.xml.bin.annotation essentiellement pour préciser le mode de fonctionnement lors des opérations de marshaling/unmarshaling.
Ces annotations précisent le mapping entre les classes Java et le document XML. La plupart de ces annotations ont des valeurs par défaut ce qui réduit l'obligation de leur utilisation si la valeur par défaut correspond au besoin.
JAXB 2.0 permet aussi de réaliser dynamiquement à l'exécution une transformation d'un graphe d'objets en document XML et vice et versa. C'est cette fonctionnalité qui est largement utilisée dans les services web via l'API JAX−WS 2.0. La classe abstraite JAXBContext fournie par l'API JAXB permet de gérer la transformation d'objets Java en XML et vice et versa.
JAXB 2.0 propose plusieurs outils pour faciliter sa mise en oeuvre :
L'API JAXB est contenue dans la package javax.xml.bind.
§
La mise en oeuvre de JAXB requiert pour un usage standard plusieurs étapes :
L'utilisation de JAXB se fait donc en deux phases :
L'utilisation de JAXB met en oeuvre plusieurs éléments :
Les avantages d'utiliser JAXB sont nombreux :
Si vous devez créer des classes directement à partir d'un document XML, vous devez impérativement posséder le Schéma XML correspondant (avec l'extension xsd). Si vous n'avez pas le Schéma XML, cela peut poser quelques difficultés. C'est l'inconvénient de cette technologie.
Toutefois, il existe des logiciels libres, comme Liquid XML Studio, qui savent créer un Schéma directement à partir du document XML correspondant. Ils proposent alors des valeurs par défaut que vous pouvez par la suite modifier au travers des propriétés proposées dans l'éditeur intégré.
Pour permettre l'utilisation et la manipulation d'un document XML, JAXB propose de générer un ensemble de classes à partir du schema XML du document.
Chaque implémentation de JAXB doit fournir un outil (binding compiler) qui permet la génération de classes et interfaces à partir d'un schema (binding a schema).
Je vous propose, pour la suite de cette étude, de prendre un exemple concret, et surtout très simple. J'aimerais pouvoir mettre en oeuvre un logiciel qui trace des formes, des carrés ou des cercles, dans la partie principale d'une application graphique fenêtrée.
Il doit être possible de récupérer l'ensemble des formes à tracer au moyen, par exemple, du document XML ci-dessous :
En utilisant le logiciel Liquid XML Studio, et en faisant les réglages nécessaires, voici le Schéma XML correspondant :
Comme nous l'avons vu, l'implémentation de référence fournit l'outil xjc pour générer les classes à partir d'un schéma XML. L'utilisation la plus simple de l'outil xjc est de lui fournir simplement le fichier qui contient le schéma XML du document à utiliser.
xjc Formes.xsd
L'outil xjc possède plusieurs options dont voici les principales :
Option | Rôle |
---|---|
-p nom_package | Précise le package qui va contenir les classes générées |
-d répertoire | Précise le répertoire qui va contenir les classes générées |
-nv | Inhibe la validation du schéma |
-b fichier | Précise un fichier de configuration |
-classpath chemin | Précise le classpath |
Le compilateur génère des classes en correspondance avec le Schéma XML fourni à l'outil.
Les classes générées sont dépendantes de l'implémentation de JAXB utilisée : il est préférable d'utiliser les classes générées par une implémentation avec cet outil. Par défaut, JAXB utilise des règles pour définir chaque entité incluse dans le schema (element et complexType défini dans le schéma).
package jaxb; //---------------------------------------------------------------------------------------------------------- Ligne rajoutée import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.*; @XmlAccessorType(XmlAccessType.FIELD) //------------------------------------------------------------ Element racine @XmlType(name = , propOrder = { }) @XmlRootElement(name = ) public class Formes { @XmlElement(name = ) protected List<Formes.Forme> forme; public List<Formes.Forme> getForme() { if (forme == null) { forme = new ArrayList<Formes.Forme>(); } return this.forme; } @XmlAccessorType(XmlAccessType.FIELD) //----------------------------------------------------- Element Forme @XmlType(name = , propOrder = { , }) public static class Forme { protected Formes.Forme.Centre centre; @XmlSchemaType(name = ) protected Long largeur; @XmlAttribute(required = true) protected String type; public Formes.Forme.Centre getCentre() { return centre; } public void setCentre(Formes.Forme.Centre value) { this.centre = value; } public Long getLargeur() { return largeur; } public void setLargeur(Long value) { this.largeur = value; } public String getType() { return type; } public void setType(String value) { this.type = value; } @XmlAccessorType(XmlAccessType.FIELD) //--------------------------------------------------- Element Centre @XmlType(name = ) public static class Centre { @XmlAttribute(required = true) protected int x; @XmlAttribute(required = true) protected int y; public int getX() { return x; } public void setX(int value) { this.x = value; } public int getY() { return y; } public void setY(int value) { this.y = value; } } } }
package jaxb; //---------------------------------------------------------------------------------------------------------- Ligne rajoutée import javax.xml.bind.annotation.XmlRegistry; @XmlRegistry public class ObjectFactory { public ObjectFactory() {} public Formes.Forme createFormesForme() { return new Formes.Forme(); } public Formes createFormes() { return new Formes(); } public Formes.Forme.Centre createFormesFormeCentre() { return new Formes.Forme.Centre(); } }
JAXB fournie une API qui permet à l'exécution d'effectuer les opérations de transformation d'un document XML en un graphe d'objets utilisant les classes générées et vice et versa (unmashalling/marshalling) ainsi que des opérations de validation.
JAXBContext contexte = JAXBContext.newInstance("jaxb");
L'API JAXB propose de transformer un document XML en un ensemble d'objets qui vont encapsuler les données et la hiérarchie du document. Ces objets sont des instances des classes générées à partir du schéma XML.
Unmarshaller mapperXMLObjet = contexte.createUnmarshaller();
Formes formes = (Formes) mapperXMLObjet.unmarshal(new File("Formes.xml"));
Je vous propose de finaliser ces concepts au travers de l'application fenêtrée qui trace les formes stockées dans le document XML, dont voici le résultat :
package jaxb; import java.awt.*; import java.io.File; import javax.swing.*; import javax.xml.bind.*; public class Principal extends JFrame { private Tracé dessins = new Tracé(); public Principal() { super( ); add(dessins); setSize(400, 300); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Principal(); } private class Tracé extends JComponent { private Formes formes; public Tracé() { try { JAXBContext contexte = JAXBContext.newInstance( ); Unmarshaller mapperXMLObjet = contexte.createUnmarshaller(); formes = (Formes) mapperXMLObjet.unmarshal(new File( )); } catch (JAXBException ex) { setTitle( ); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D surface = (Graphics2D) g; surface.setStroke(new BasicStroke(5)); surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (formes!=null) for (Formes.Forme forme : formes.getForme()) { long largeurLong = forme.getLargeur(); int largeur = (int) largeurLong; int x = forme.getCentre().getX() - largeur/2; int y = forme.getCentre().getY() - largeur/2; String type = forme.getType(); if (type.equals( )) surface.drawRect(x, y, largeur, largeur); else surface.drawOval(x, y, largeur, largeur); } } } }
JAXB permet de créer un document XML à partir d'un graphe d'objets : cette opération est nommée marshalling. Une operation de mashalling est l'opération inverse de l'opération d'unmarshalling.
Ce graphe d'objets peut être issu d'une opération de type unmarshalling (construction à partir d'un document XML existant) ou issu d'une création de toutes pièces de l'ensemble des objets. Dans le premier cas cela correspond à une modification du document et dans le second cas à une création de document.
Marshaller mapperObjetsXML = contexte.createMarshaller(); mapperObjetsXML.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Comme nous l'avons découvert dans les chapitres précédents, une des classes générées à partir du schéma se nomme ObjectFactory : c'est une fabrique d'objets pour les classes générées qui encapsulent des données d'un document respectant le schéma.
Pour créer un document XML en utilisant ces classes, il faut suivre les étapes que nous avons évoqué dans la fin du chapitre précédent :
ObjectFactory fabrique = new ObjectFactory();
Formes formes = fabrique.createFormes(); Formes.Forme forme = fabrique.createFormesForme(); Formes.Forme.Centre centre = fabrique.createFormesFormeCentre();
Je vous propose de finaliser ces concepts au travers d'une application fenêtrée qui trace les formes directement sur la zone principale de la fenêtre et qui peut stocker par la suite tous les formes présentes dans le document XML correspondant :
package jaxb; import java.awt.*; import java.awt.event.*; import java.io.File; import javax.swing.*; import javax.xml.bind.*; public class Principal extends JFrame { private Tracé dessins = new Tracé(); private JRadioButton cercle = new JRadioButton( , true); private JRadioButton carré = new JRadioButton( ); private JFormattedTextField largeur = new JFormattedTextField(50L); private ButtonGroup groupe = new ButtonGroup(); private JPanel boutons = new JPanel(); private JToolBar barre = new JToolBar(); public Principal() { super( ); add(barre, BorderLayout.NORTH); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { dessins.effacer(); } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { dessins.enregistrer(); } }); dessins.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (cercle.isSelected()) dessins.ajoutForme( , e.getX(), e.getY()); else dessins.ajoutForme( , e.getX(), e.getY()); } }); add(dessins); largeur.setColumns(5); boutons.add(cercle); boutons.add(carré); boutons.add(largeur); groupe.add(cercle); groupe.add(carré); add(boutons, BorderLayout.SOUTH); setSize(400, 300); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Principal(); } private class Tracé extends JComponent { private Formes formes; private Marshaller mapperObjetsXML; private ObjectFactory fabrique = new ObjectFactory(); public Tracé() { try { JAXBContext contexte = JAXBContext.newInstance(Formes.class); mapperObjetsXML = contexte.createMarshaller(); mapperObjetsXML.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); formes = fabrique.createFormes(); } catch (JAXBException ex) { setTitle( ); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D surface = (Graphics2D) g; surface.setStroke(new BasicStroke(5)); surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (formes!=null) for (Formes.Forme forme : formes.getForme()) { long largeurLong = forme.getLargeur(); int largeur = (int) largeurLong; int x = forme.getCentre().getX() - largeur/2; int y = forme.getCentre().getY() - largeur/2; String type = forme.getType(); if (type.equals( )) surface.drawRect(x, y, largeur, largeur); else surface.drawOval(x, y, largeur, largeur); } } public void effacer() { formes = fabrique.createFormes(); revalidate(); repaint(); } public void ajoutForme(String type, int x, int y) { Formes.Forme forme = fabrique.createFormesForme(); forme.setType(type); forme.setLargeur((Long) largeur.getValue()); Formes.Forme.Centre centre = fabrique.createFormesFormeCentre(); centre.setX(x); centre.setY(y); forme.setCentre(centre); formes.getForme().add(forme); repaint(); } private void enregistrer() { try { mapperObjetsXML.marshal(formes, new File( )); } catch (JAXBException ex) { setTitle( ); } } } }
JAXB permet de mapper un document XML vers une ou plusieurs classes annotées, ou inversement, sans être obligé d'utiliser un schéma XML. Dans ce cas, le développeur a la charge d'écrire la ou les classes annotées requises.
Nous avons quelquefois besoin de pouvoir stocker ces informations dans un fichier afin de les retrouver par la suite. Se pose alors la question du format de stockage. Nous avons vu que le document XML est une bonne alternative puisqu'il est facile de le lire avec un simple éditeur de texte.
Dans ce contexte, il serait judicieux de pouvoir fabriquer ces documents XML directement par l'intermédiaire des classes déjà existantes (mapping Objets/XML), sans passer par un Schéma XML (puisqu'encore aucun document n'existe). C'est dans ce cadre là que nous pouvons rajouter des annotations spécifiques directement sur les classes concernées.
Il n'est donc plus nécessaire d'utiliser des classes générées par le compilateur de schéma mais dans ce cas, la ou les classes doivent être créées manuellement en utilisant les annotations adéquates. La classe qui encapsule la racine du document doit être annotée avec l'annotation @XmlRootElement. Une exception de type javax.xml.bind.MarshalException est levée par JAXB si la classe racine ne possède pas cette annotation.
JAXB utilise des comportements par défaut qui ne nécessite de la part du développeur que de définir des comportements particuliers si la valeur par défaut ne convient pas. Ainsi, nous pouvons limiter le nombre d'annotations requises.
Comme nous l'avons découvert dans les chapitres précédents, le mapping entre un document XML et les objets correspondants, se feront par l'intermédiaires des classes JAXBContext, Marshaller et Unmarshaller.
La configuration de la transformation d'un document XML en objets Java est réalisée grâce à l'utilisation d'annotations dédiées dans les classes Java. De nombreuses annotations sont définies par JAXB 2.0. Toutes ces annotations sont définies dans le package javax.xml.bind.annotation.
Annotation | Description |
---|---|
XmlAccessorOrder | Contrôler l'ordre des attributs et des propriétés dans la classe. |
XmlAccessorType | Contrôler si l'attribut ou la propriété de la classe est sérialisé par défaut. |
XmlAnyAttribute | Convertir une propriété de la classe en un ensemble d'attributs de type jocker dans le document XML. |
XmlAnyElement | Convertir une propriété de la classe en une description representant l'élément JAXB dans le document XML. |
XmlAttachmentRef | Marquer un attribut ou une propriété de la classe comme une URI que fait référence à une type MIME particulier. |
XmlAttribute | Convertir une propriété de la classe en un attribut dans le document XML. |
XmlElement | Convertir une propriété de la classe en un élément dans le document XML. |
XmlElementDecl | Associer une fabrique à un element XML. |
XmlElementRef | Convertir une propriété de la classe en un élément dans le document XML qui est associé à un nom de propriété particulier. |
XmlElementRefs | Marquer une propriété de la classe qui fait référence aux classes qui possèdente XmlElement ou JAXBElement. |
XmlElements | Créer un conteneur pour de multiples annotations XmlElement, qui précise ainsi le choix possible parmi celles qui sont proposées. |
XmlElementWrapper | Créer un élément père dans le document XML pour des collections d'éléments. |
XmlEnum | Convertir une énumération en une représentation équivalente dans le document XML. |
XmlEnumValue | Convertir un énumérateur en une représentation équivalente dans le document XML. |
XmlID | Convertir une propriété de la classe en un ID XML. |
XmlIDREF | Convertir une propriété de la classe en un IDREF XML. |
XmlInlineBinaryData | Encoder en base64 dans le document XML. |
XmlList | Utilisé pour convertir une propriété de la classe vers une liste de type simple. |
XmlMimeType | Associer un type MIME qui contrôle la représentation XML en rapport avec la propriété de la classe. |
XmlMixed | Annoter une propriété de la classe qui peut supporter plusieurs types de valeur avec un contenu mixte. |
XmlNs | Associer un prefix d'un espace de nommage à une URI. |
XmlRegistry | Marquer une classe comme possédant une ou des méthodes annotées avec XmlElementDecl. |
XmlRootElement | Associer une classe ou une énumération à un element XML. Très souvent utilisé pour spécifier la racine du document XML. |
XmlSchema | Associer un espace de nommage à un paquetage. |
XmlSchemaType | Associer un type Java ou une énumeration à un type défini dans un schéma. |
XmlSchemaTypes | Conteneur pour plusieurs propriétés annotées par XmlSchemaType. |
XmlTransient | Marquer une entité pour qu'elle ne soit pas mappée dans le document XML. |
XmlType | Convertir une classe ou une énumération vers le type spécifié dans Shéma XML correspondant. |
XmlValue | Mapper une classe vers le type complexe dans le Schéma XML ou vers le type simple suivant le cas. |
Nous allons toute de suite élaborer un premier exemple qui permet de faire du tracé de formes, comme précédemment, avec la possiblité de sauvegarder le tracé réalisé sur l'IHM dans un document XML et de pouvoir ensuite le restituer à loisir. Tout ceci se fait sans document XML préalable, et à plus forte raison, sans Schéma XML équivalent.
Dans ce premier exemple, nous ne proprosons pas d'attributs dans les balises. Du coup, il devient nécessaire de prévoir des balises spécifiques pour les coordonnées. Remarquez au passage que notre document Formes, représenté par l'élément racine <formes> peut aussi bien accueillir des éléments <cercle> que des éléments <carré>, ce qui offre une structure un petit peu plus compliquée que précédemment.
package jaxb; import java.awt.Graphics2D; import java.util.ArrayList; import javax.xml.bind.annotation.*; // Définition de l'élément racine du document et de ses sous-éléments @XmlRootElement public class Formes { @XmlElements({ @XmlElement(name = , type = Carré.class), @XmlElement(name = , type = Cercle.class) }) private ArrayList<Forme> formes = new ArrayList<Forme>(); public ArrayList<Forme> getFormes() { return formes; } public void ajoutForme(Forme forme) { formes.add(forme); } public void supprimerFormes() { formes.clear(); } } // hiérarchie des classes représentant l'ensemble des formes possibles abstract class Forme { protected int x, y; public Forme() {} public Forme(int x, int y) { this.x = x; this.y=y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } abstract void dessine(Graphics2D surface); } class Cercle extends Forme { private int rayon = 50; public Cercle() {} public Cercle(int x, int y, int rayon) { super(x, y); this.rayon = rayon; } public int getRayon() { return rayon; } public void setRayon(int rayon) { this.rayon = rayon; } @Override void dessine(Graphics2D surface) { surface.drawOval(x-rayon, y-rayon, 2*rayon, 2*rayon); } } class Carré extends Forme { private int côté = 100; public Carré() {} public Carré(int x, int y, int côté) { super(x, y); this.côté = côté; } public int getCôté() { return côté; } public void setCôté(int côté) { this.côté = côté; } @Override void dessine(Graphics2D surface) { surface.drawRect(x-côté/2, y-côté/2, côté, côté); } }
Dans ce source, nous découvrons deux parties essentielles :
package jaxb; import java.awt.*; import java.awt.event.*; import java.io.File; import javax.swing.*; import javax.xml.bind.*; import jaxb.Formes.*; public class Principal extends JFrame { private Tracé dessins = new Tracé(); private JRadioButton cercle = new JRadioButton( , true); private JRadioButton carré = new JRadioButton( ); private JFormattedTextField largeur = new JFormattedTextField(50); private ButtonGroup groupe = new ButtonGroup(); private JPanel boutons = new JPanel(); private JToolBar barre = new JToolBar(); public Principal() { super( ); add(barre, BorderLayout.NORTH); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { dessins.effacer(); } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { dessins.ouvrir(); } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { dessins.enregistrer(); } }); dessins.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { int larg = (Integer)largeur.getValue(); if (cercle.isSelected()) dessins.ajoutForme(new Cercle(e.getX(), e.getY(), larg/2)); else dessins.ajoutForme(new Carré(e.getX(), e.getY(), larg)); } }); add(dessins); largeur.setColumns(3); boutons.add(cercle); boutons.add(carré); boutons.add(largeur); groupe.add(cercle); groupe.add(carré); add(boutons, BorderLayout.SOUTH); setSize(400, 300); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } public static void main(String[] args) { new Principal(); } private class Tracé extends JComponent { private Formes formes = new Formes(); private Marshaller mapperObjetsXML; private Unmarshaller mapperXMLObjets; public Tracé() { try { JAXBContext contexte = JAXBContext.newInstance(Formes.class); mapperObjetsXML = contexte.createMarshaller(); mapperObjetsXML.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); mapperXMLObjets = contexte.createUnmarshaller(); } catch (JAXBException ex) { setTitle( ); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D surface = (Graphics2D) g; surface.setStroke(new BasicStroke(5)); surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (formes!=null) for (Forme forme : formes.getFormes()) forme.dessine(surface); } public void effacer() { formes.supprimerFormes(); revalidate(); repaint(); } public void ajoutForme(Forme forme) { formes.ajoutForme(forme); repaint(); } private void enregistrer() { try { mapperObjetsXML.marshal(formes, new File( )); } catch (JAXBException ex) { setTitle( ); } } private void ouvrir() { try { formes = (Formes) mapperXMLObjets.unmarshal(new File( )); repaint(); } catch (JAXBException ex) { setTitle( ); } } } }
Vous remarquez ici, par rapport aux chapitres précédents, que la gestion des formes est beaucoup plus simple, puisqu'il s'agit de classes des plus normales sans annotations particulières. Le fait d'avoir d'ailleurs une classe abstraite commune permet de simplifier encore plus le problème.
L'utilisation des annotations sans passer par une Schéma XML me paraît finalement plus facile à mettre en oeuvre et surtout plus intuitif. L'intérêt du Schéma XML est surtout intéressant lorsque nous devons faire une application qui tient compte de documents XML déjà existants.
Le tableau ci-dessous reprend quelques-unes des annotations pour bien préciser leur particularité et leur utilité.
Annotation | Considérations |
---|---|
XmlRootElement | Peut être utilisée sur une classe pour préciser que cette classe sera l'élément racine du document XML. Chaque attribut de la classe devient ainsi un élément fils dans le document XML. L'attribut namespace de l'annotation @XmlRootElement permet de préciser l'espace de nommage. Codage de la classe Personne qui va servir de référenceimport java.util.Date; import javax.xml.bind.annotation.*; @XmlRootElement public class Personne { private String nom; private String prenom; private int taille; private Date dateNaissance; public Personne() { } public Personne(String nom, String prenom, int taille, Date dateNaissance) { super(); this.nom = nom; this.prenom = prenom; this.taille = taille; this.dateNaissance = dateNaissance; } public Date getDateNaissance() { return dateNaissance; } public void setDateNaissance(Date dateNaissance) { this.dateNaissance = dateNaissance; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String getPrenom() { return prenom; } public void setPrenom(String prenom) { this.prenom = prenom; } public int getTaille() { return taille; } public void setTaille(int taille) { this.taille = taille; } } Résultat<?xml version= encoding= standalone= ?> <personne> <nom>REMY</nom> <prenom>Emmanuel</prenom> <taille>171</taille> <dateNaissance>2007-01-16T17:03:31.213+01:00</dateNaissance> </personne> |
XmlTransient | Permet d'ignorer une propriété/entité dans la mapping entre l'objet et l'élément XML correspondant : Exempleimport java.util.Date; import javax.xml.bind.annotation.*; @XmlRootElement public class Personne { ... @XmlTransient public String getNom() { return nom; } ... } Résultat<?xml version= encoding= standalone= ?> <personne> <prenom>Emmanuel</prenom> <taille>171</taille> <dateNaissance>2007-01-16T17:03:31.213+01:00</dateNaissance> </personne> |
XmlAttribute | Permet de mapper une propriété sous la forme d'un attribut et fournir des précisions sur la configuration de cet attribut Exempleimport java.util.Date; import javax.xml.bind.annotation.*; @XmlRootElement public class Personne { ... @XmlAttribute public String getNom() { return nom; } ... } Résultat<?xml version= encoding= standalone= ?> <personne nom="REMY"> <prenom>Emmanuel</prenom> <taille>171</taille> <dateNaissance>2007-01-16T17:03:31.213+01:00</dateNaissance> </personne> |
XmlElementWrapper | Pour les collections, il est possible d'utiliser l'annotation @XmlElementWrapper pour définir un élement père qui encapsule les occurrences de la collection et d'utiliser l'annotation @XmlElement pour préciser le nom de chaque élément de la collection.Exempleimport java.util.Date; import javax.xml.bind.annotation.*; @XmlRootElement public class Personne { private String nom; private String prenom; private int taille; private Date dateNaissance; @XmlElementWrapper(name = "Residence") Résultat<?xml version= encoding= standalone= ?> <personne> <nom>REMY</nom> <prenom>Emmanuel</prenom> <taille>171</taille> <dateNaissance>2007-01-16T17:03:31.213+01:00</dateNaissance> <Residence> <Adresse> <rue>Nom de la rue</rue> <codePostal>78895</codePostal> <ville>Une ville</ville> <pays>France</pays> </Adresse> </Residence> </personne> |
XmlType | Il est possible de configurer l'ordre des éléments au travers de cette annotation Exempleimport java.util.Date; import javax.xml.bind.annotation.*; @XmlRootElement @XmlType(propOrder = {"dateNaissance", "adresses", "nom", "prenom", "taille"}) public class Personne { private String nom; private String prenom; private int taille; private Date dateNaissance; @XmlElementWrapper(name = "Residence") Résultat<?xml version= encoding= standalone= ?> <personne> <dateNaissance>2007-01-16T17:03:31.213+01:00</dateNaissance> <Residence> <Adresse> <rue>Nom de la rue</rue> <codePostal>78895</codePostal> <ville>Une ville</ville> <pays>France</pays> </Adresse> </Residence> <nom>REMY</nom> <prenom>Emmanuel</prenom> <taille>171</taille> </personne> |
XmlJavaTypeAdapter | Il est possible de définir des classes de type Adapter qui permettent de personnaliser la façon dont un objet est serialisé/desérialisé dans le document XML. Ces classes Adapter héritent de la classe javax.xml.bind.annotation.adapters.XmlAdapter. Il suffit de redéfinir les méthodes marshal() et unmashal() afin de permettre un formatage de l'information adapté au visuel que nous désirons faire apparaître côté document XML. Ces méthodes seront automatiquement utilisées à l'exécution par l'API JAXB lors des transformations.
L'annotation @XmlJavaTypeAdapter permet de préciser la classe de type Adapter qui sera à utiliser plutôt que d'utiliser la conversion par défautExempleimport java.util.Date; import java.text.*; import javax.xml.bind.annotation.*; import javax.xml.bind.annotation.adapters.*; @XmlRootElement @XmlType(propOrder = {"dateNaissance", "adresses", "nom", "prenom", "taille"}) public class Personne { private String nom; private String prenom; private int taille; @XmlJavaTypeAdapter(DateAdapter.class) private Date dateNaissance; @XmlElementWrapper(name = "Residence") Résultat<?xml version= encoding= standalone= ?> <personne> <dateNaissance>1 octobre 1959</dateNaissance> <Residence> <Adresse> <rue>Nom de la rue</rue> <codePostal>78895</codePostal> <ville>Une ville</ville> <pays>France</pays> </Adresse> </Residence> <nom>REMY</nom> <prenom>Emmanuel</prenom> <taille>171</taille> </personne> |
Nous allons reprendre l'application précédente qui permet de sauvegarder, et donc de restituer un ensemble de formes, dans un document XML. Ce document XML est légèrement différent que le précédent. Pour chacune des formes, nous envisageons, cette fois-ci, de prendre une nouvelle balise nommée <centre> à l'intérieur de laquelle nous stipulons les coordonnées du centre de la forme.
Cette fois-ci nous devons proposer des attributs, respectivement x et y, qui vont représenter les coordonnées dela balise <centre>.
package jaxb; import java.awt.Graphics2D; import java.util.ArrayList; import javax.xml.bind.annotation.*; // Définition de l'élément racine du document et de ses sous-éléments @XmlRootElement public class Formes { @XmlElements({ @XmlElement(name = , type = Carré.class), @XmlElement(name = , type = Cercle.class) }) private ArrayList<Forme> formes = new ArrayList<Forme>(); public ArrayList<Forme> getFormes() { return formes; } public void ajoutForme(Forme forme) { formes.add(forme); } public void supprimerFormes() { formes.clear(); } } // La classe Forme est modifiée pour accueillir un centre à la place des coordonnées x et y abstract class Forme { protected Centre centre; public Forme() { centre = new Centre(); } public Forme(int x, int y) { centre = new Centre(x, y); } public Centre getCentre() { return centre; } public void setCentre(Centre centre) { this.centre = centre; } abstract void dessine(Graphics2D surface); } // Nouvel élément représenant le <centre> qui possède deux attributs x et y class Centre { @XmlAttribute private int y; @XmlAttribute private int x; public Centre() {} public Centre(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } class Cercle extends Forme { private int rayon = 50; public Cercle() {} public Cercle(int x, int y, int rayon) { super(x, y); this.rayon = rayon; } public int getRayon() { return rayon; } public void setRayon(int rayon) { this.rayon = rayon; } @Override void dessine(Graphics2D surface) { int x = centre.getX(); int y = centre.getY(); surface.drawOval(x-rayon, y-rayon, 2*rayon, 2*rayon); } } class Carré extends Forme { private int côté = 100; public Carré() {} public Carré(int x, int y, int côté) { super(x, y); this.côté = côté; } public int getCôté() { return côté; } public void setCôté(int côté) { this.côté = côté; } @Override void dessine(Graphics2D surface) { int x = centre.getX(); int y = centre.getY(); surface.drawRect(x-côté/2, y-côté/2, côté, côté); } }
Dans ce source, nous pouvons faire un certain nombre de remarques :
Cette fois-ci, nous changeons de sujet. Nous proposons une application qui permet de stocker un petit répertoire téléphonique de portable associé juste à quelques prénoms connus. Ce répertoire devra toutefois être dans l'ordre alphabétique des prénoms.
package carte; import java.awt.*; import java.awt.event.ActionEvent; import java.io.File; import java.text.ParseException; import javax.swing.*; import java.util.*; import javax.swing.text.MaskFormatter; import javax.xml.bind.*; import javax.xml.bind.annotation.*; public class GestionRépertoire extends JFrame { private JTextField prénom = new JTextField( , 18); private JFormattedTextField téléphone; private JPanel panneau = new JPanel(); private JToolBar barre = new JToolBar(); private Marshaller mapperObjetsXML; private Unmarshaller mapperXMLObjets; private Répertoire répertoire = new Répertoire(); private JComboBox liste = new JComboBox(); public GestionRépertoire() throws ParseException { super( ); documentXML(); liste.setForeground(Color.RED); téléphone = new JFormattedTextField(new MaskFormatter( )); téléphone.setValue( ); barre.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { répertoire.ajout(prénom.getText(), (String) téléphone.getValue()); répertoire.miseAJour(liste); } }); barre.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { répertoire.suppression(prénom.getText()); répertoire.miseAJour(liste); } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { try { répertoire = (Répertoire) mapperXMLObjets.unmarshal(new File( )); répertoire.miseAJour(liste); } catch (JAXBException ex) { setTitle( ); } } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { try { mapperObjetsXML.marshal(répertoire, new File( )); } catch (JAXBException ex) { setTitle( ); } } }); add(barre, BorderLayout.NORTH); panneau.add(new JLabel( )); panneau.add(prénom); panneau.add(new JLabel( )); panneau.add(téléphone); add(panneau); add(liste, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void documentXML() { try { JAXBContext contexte = JAXBContext.newInstance(Répertoire.class); mapperObjetsXML = contexte.createMarshaller(); mapperObjetsXML.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); mapperXMLObjets = contexte.createUnmarshaller(); } catch (JAXBException ex) { setTitle( ); } } public static void main(String[] args) throws ParseException { new GestionRépertoire(); } } @XmlRootElement class Répertoire { private TreeMap<String, String> répertoire = new TreeMap<String, String>(); public TreeMap<String, String> getRépertoire() { return répertoire; } public void setRépertoire(TreeMap<String, String> répertoire) { this.répertoire = répertoire; } public void ajout(String prénom, String numéro) { répertoire.put(prénom, numéro); } public void suppression(String prénom) { répertoire.remove(prénom); } public void miseAJour(JComboBox liste) { liste.removeAllItems(); for (Map.Entry<String, String> élément : répertoire.entrySet()) liste.addItem(élément); } }
Faisons un certain nombre de remarques :
Nous reprenons le même sujet que précédemment. L'objectif ici est de proposer une meilleure mise en forme du document XML pour une lecture plus adaptée à la situation.
... @XmlRootElement class Répertoire { @XmlElement(name= ) @XmlJavaTypeAdapter(MapAdapter.class) private TreeMap<String, String> répertoire = new TreeMap<String, String>(); public TreeMap<String, String> getRépertoire() { return répertoire; } public void ajout(String prénom, String numéro) { répertoire.put(prénom, numéro); } public void suppression(String prénom) { répertoire.remove(prénom); } public void miseAJour(JComboBox liste) { liste.removeAllItems(); for (Map.Entry<String, String> élément : répertoire.entrySet()) liste.addItem(élément); } } class MapAdapter extends XmlAdapter<Téléphones, TreeMap<String, String>> { @Override public TreeMap<String, String> unmarshal(Téléphones téléphones) throws Exception { TreeMap<String, String> cartes = new TreeMap<String, String>(); for (Téléphone téléphone : téléphones.getTéléphones()) cartes.put(téléphone.getPrénom(), téléphone.getNuméro()); return cartes; } @Override public Téléphones marshal(TreeMap<String, String> valeurs) throws Exception { Téléphones téléphones = new Téléphones(); for (Map.Entry<String, String> carte : valeurs.entrySet()) téléphones.getTéléphones().add(new Téléphone(carte.getKey(), carte.getValue())); return téléphones; } } class Téléphones { @XmlElement(name= ) private ArrayList<Téléphone> téléphones = new ArrayList<Téléphone>(); public ArrayList<Téléphone> getTéléphones() { return téléphones; } } class Téléphone { @XmlAttribute private String numéro; @XmlAttribute private String prénom; public String getNuméro() { return numéro; } public String getPrénom() { return prénom; } public Téléphone(String prénom, String numéro) { this.prénom = prénom; this.numéro = numéro; } public Téléphone() {} }
Faisons un certain nombre de remarques :
Nous reprenons de nouveau la même application. Chaque utilisateur peut cette fois-ci avoir plusieurs téléphones : le mobile, le téléphone du domicile ou le téléphone du travail.
package carte; import java.awt.*; import java.awt.event.ActionEvent; import java.io.File; import java.text.ParseException; import javax.swing.*; import java.util.*; import javax.swing.text.MaskFormatter; import javax.xml.bind.*; import javax.xml.bind.annotation.*; import javax.xml.bind.annotation.adapters.*; public class GestionRépertoire extends JFrame { private JTextField prénom = new JTextField( , 18); private JFormattedTextField téléphone; private JPanel panneau = new JPanel(); private JToolBar barre = new JToolBar(); private Marshaller mapperObjetsXML; private Unmarshaller mapperXMLObjets; private Répertoire répertoire = new Répertoire(); private JComboBox typeTéléphone = new JComboBox(TypeTéléphone.values()); private JComboBox liste = new JComboBox(); public GestionRépertoire() throws ParseException { super( ); documentXML(); liste.setForeground(Color.RED); téléphone = new JFormattedTextField(new MaskFormatter( )); téléphone.setValue( ); barre.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { répertoire.ajout(prénom.getText(), typeTéléphone.getSelectedItem(), (String) téléphone.getValue()); répertoire.miseAJour(liste); } }); barre.add(new AbstractAction( ) { public void actionPerformed(ActionEvent e) { répertoire.suppression(prénom.getText()); répertoire.miseAJour(liste); } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { try { répertoire = (Répertoire) mapperXMLObjets.unmarshal(new File( )); répertoire.miseAJour(liste); } catch (JAXBException ex) { setTitle( ); } } }); barre.add(new AbstractAction( , new ImageIcon( )) { public void actionPerformed(ActionEvent e) { try { mapperObjetsXML.marshal(répertoire, new File( )); } catch (JAXBException ex) { setTitle( ); } } }); add(barre, BorderLayout.NORTH); panneau.add(new JLabel( )); panneau.add(prénom); panneau.add(new JLabel( )); panneau.add(typeTéléphone); panneau.add(téléphone); add(panneau); add(liste, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } private void documentXML() { try { JAXBContext contexte = JAXBContext.newInstance(Répertoire.class); mapperObjetsXML = contexte.createMarshaller(); mapperObjetsXML.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); mapperXMLObjets = contexte.createUnmarshaller(); } catch (JAXBException ex) { setTitle( ); } } public static void main(String[] args) throws ParseException { new GestionRépertoire(); } } enum TypeTéléphone {Mobile, Domicile, Travail}; @XmlRootElement class Répertoire { @XmlElement(name= ) @XmlJavaTypeAdapter(MapAdapter.class) private TreeMap<String, EnumMap<TypeTéléphone, String>> répertoire = new TreeMap<String, EnumMap<TypeTéléphone, String>>(); public TreeMap<String, EnumMap<TypeTéléphone, String>> getRépertoire() { return répertoire; } public void suppression(String prénom) { répertoire.remove(prénom); } public void ajout(String prénom, Object type, String numéro) { EnumMap<TypeTéléphone, String> téléphones; if (répertoire.containsKey(prénom)) { téléphones = répertoire.get(prénom); téléphones.put((TypeTéléphone)type, numéro); } else { téléphones = new EnumMap<TypeTéléphone, String>(TypeTéléphone.class); téléphones.put((TypeTéléphone)type, numéro); répertoire.put(prénom, téléphones); } } public void miseAJour(JComboBox liste) { liste.removeAllItems(); for (Map.Entry<String, EnumMap<TypeTéléphone, String>> carte : répertoire.entrySet()) liste.addItem(carte); } } class MapAdapter extends XmlAdapter<Téléphones, TreeMap<String, EnumMap<TypeTéléphone, String>>> { @Override public TreeMap<String, EnumMap<TypeTéléphone, String>> unmarshal(Téléphones téléphones) throws Exception { TreeMap<String, EnumMap<TypeTéléphone, String>> cartes = new TreeMap<String, EnumMap<TypeTéléphone, String>>(); for (Téléphone téléphone : téléphones.getTéléphones()) { EnumMap<TypeTéléphone, String> liste= new EnumMap<TypeTéléphone, String>(TypeTéléphone.class); for (NuméroParType npt : téléphone.getTéléphone()) liste.put(npt.getType(), npt.getNuméro()); cartes.put(téléphone.getPrénom(), liste); } return cartes; } @Override public Téléphones marshal(TreeMap<String, EnumMap<TypeTéléphone, String>> valeurs) throws Exception { Téléphones téléphones = new Téléphones(); for (Map.Entry<String, EnumMap<TypeTéléphone, String>> carte : valeurs.entrySet()) { Téléphone personne = new Téléphone(carte.getKey()); for (Map.Entry<TypeTéléphone, String> téléphone : carte.getValue().entrySet()) personne.ajoutTéléphone(téléphone.getKey(), téléphone.getValue()); téléphones.getTéléphones().add(personne); } return téléphones; } } class Téléphones { @XmlElement(name= ) private ArrayList<Téléphone> téléphones = new ArrayList<Téléphone>(); public ArrayList<Téléphone> getTéléphones() { return téléphones; } } class Téléphone { @XmlAttribute private String prénom; @XmlElement private ArrayList<NuméroParType> téléphone = new ArrayList<NuméroParType>(); public Téléphone(String prénom) { this.prénom = prénom; } public Téléphone() {} public String getPrénom() { return prénom; } public ArrayList<NuméroParType> getTéléphone() { return téléphone; } public void ajoutTéléphone(TypeTéléphone type, String numéro) { téléphone.add(new NuméroParType(type, numéro)); } } class NuméroParType { @XmlAttribute private String numéro; @XmlAttribute private TypeTéléphone type; public NuméroParType() {} public NuméroParType(TypeTéléphone type, String numéro) { this.type = type; this.numéro = numéro; } public String getNuméro() { return numéro; } public TypeTéléphone getType() { return type; } }
Il existe une annotation @XmlEnum, mais vous remarquez qu'il n'a pas été nécessaire de la mettre en oeuvre. JAXB se débrouille très bien sans et propose les énumérateurs sous forme de chaînes de caractères, ceci automatiquement.
L'outil schemagen fourni avec l'implémentation par défaut permet de générer un schéma XML à partir de classes annotées compilées.
Nous allons reprendre l'application du tracé de formes qui permet de sauvegarder, et donc de restituer un ensemble de formes, dans un document XML.
Cette fois-ci nous devons proposer des attributs, respectivement x et y, qui vont représenter les coordonnées dela balise <centre>.
package jaxb; import java.awt.Graphics2D; import java.util.ArrayList; import javax.xml.bind.annotation.*; // Définition de l'élément racine du document et de ses sous-éléments @XmlRootElement public class Formes { @XmlElements({ @XmlElement(name = , type = Carré.class), @XmlElement(name = , type = Cercle.class) }) private ArrayList<Forme> formes = new ArrayList<Forme>(); public ArrayList<Forme> getFormes() { return formes; } public void ajoutForme(Forme forme) { formes.add(forme); } public void supprimerFormes() { formes.clear(); } } // La classe Forme est modifiée pour accueillir un centre à la place des coordonnées x et y abstract class Forme { protected Centre centre; public Forme() { centre = new Centre(); } public Forme(int x, int y) { centre = new Centre(x, y); } public Centre getCentre() { return centre; } public void setCentre(Centre centre) { this.centre = centre; } abstract void dessine(Graphics2D surface); } // Nouvel élément représenant le <centre> qui possède deux attributs x et y class Centre { @XmlAttribute private int y; @XmlAttribute private int x; public Centre() {} public Centre(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } class Cercle extends Forme { private int rayon = 50; public Cercle() {} public Cercle(int x, int y, int rayon) { super(x, y); this.rayon = rayon; } public int getRayon() { return rayon; } public void setRayon(int rayon) { this.rayon = rayon; } @Override void dessine(Graphics2D surface) { int x = centre.getX(); int y = centre.getY(); surface.drawOval(x-rayon, y-rayon, 2*rayon, 2*rayon); } } class Carré extends Forme { private int côté = 100; public Carré() {} public Carré(int x, int y, int côté) { super(x, y); this.côté = côté; } public int getCôté() { return côté; } public void setCôté(int côté) { this.côté = côté; } @Override void dessine(Graphics2D surface) { int x = centre.getX(); int y = centre.getY(); surface.drawRect(x-côté/2, y-côté/2, côté, côté); } }
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="formes" type="formes"/> <xs:complexType name="formes"> <xs:sequence> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="carré" type="carré"/> <xs:element name="cercle" type="cercle"/> </xs:choice> </xs:sequence> </xs:complexType> <xs:complexType name="carré"> <xs:complexContent> <xs:extension base="forme"> <xs:sequence> <xs:element name="côté" type="xs:int"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> <xs:complexType name="forme" abstract="true"> <xs:sequence> <xs:element name="centre" type="centre" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="centre"> <xs:sequence/> <xs:attribute name="x" type="xs:int" use="required"/> <xs:attribute name="y" type="xs:int" use="required"/> </xs:complexType> <xs:complexType name="cercle"> <xs:complexContent> <xs:extension base="forme"> <xs:sequence> <xs:element name="rayon" type="xs:int"/> </xs:sequence> </xs:extension> </xs:complexContent> </xs:complexType> </xs:schema>