Affichage des articles dont le libellé est avc-binding-dom. Afficher tous les articles
Affichage des articles dont le libellé est avc-binding-dom. Afficher tous les articles

16 octobre 2016

avc-binding-dom sur Maven Central

Ma petite bibliothèque open source de binding Java/XML est disponible sur Maven Central :
<dependency>
 <groupId>net.avcompris.commons</groupId>
 <artifactId>avc-binding-dom</artifactId>    
 <version>0.1.10</version>
</dependency>
Et le source est toujours sur BitBucket :
https://bitbucket.org/avantage-compris/avc-binding-dom
Rappel de son utilisation par un exemple :

Soit un document XML de la forme suivante :
<aaaa>
 <bbbb width="34" height="58">
  <cccc weight="19k">one</cccc>
  <cccc weight="812m">two</cccc>
  <cccc weight="7x8">three</ccc>
 </bbbb>
</aaaa>
Vous écrivez une interface Java de binding sous la forme suivante :
interface Foo { 

    @XPath("bbbb/@width")    
    int getWidth();
    
    @XPath("bbbb/@height")    
    int getHeight();
    
    @XPath("cccc")    
    Bar[] getBars();
    
    interface Bar {
        
        @XPath("@weight")        
        String getWeight();                
        
        @XPath(".")        
        String getContent();    
    }
    
    @XPath("cccc[@weight = $arg0]")    
    Bar getBarByWeight(String x);
}
Et vous pouvez alors écrire le code Java suivant, qui va directement puiser les valeurs dans le DOM  :
Foo foo = DomBinderUtils.xmlContentToJava(..., Foo.class); 

assertEquals(34, foo.getWidth()); 

assertEquals(3, foo.getBars().length); 

assertEquals("19k", foo.getBars()[0].getWeight()); 

assertEquals("two", foo.getBarByWeight("812m").getContent());

Voir sur ce blog d’autres articles sur le même sujet :

21 janvier 2014

avc-binding-dom et polymorphisme

On a des cas où une structure XML a évolué et où l’on aimerait pouvoir accéder aux contenus des différentes versions à travers une unique interface Java. Pour cela, avc-binding-dom permet de déclarer des sous-interfaces et suit un polymorphisme.

Prenons un exemple, avec une interface Java candidate à récupérer des informations d’un fichier xib (Apple iOS) :
public interface Xib { boolean hasUseAutoLayout(); }
Les deux versions du XML qu’on veut savoir traiter renseignent l’information « useAutoLayout » de deux façons différentes.
Ici avec Xcode 5.0.2 :
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="4514" systemVersion="12F45" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES"> ... </document>
Ici avec Xcode 4.6.3 :
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00"> <data> ... <int key="IBDocument.defaultPropertyAccessControl">3</int> <bool key="IBDocument.UseAutolayout">YES</bool> <string key="IBCocoaTouchPluginVersion">2083</string> </data> </archive>
Écrivons des interfaces de binding pour ces deux versions :
@XPath("/document[@version = '3.0']") public interface Xib3 extends Xib { @Override @XPath("@useAutolayout = 'YES'") boolean hasUseAutoLayout(); }
et :
@XPath("/archive[@version = '8.00']") public interface Xib8 extends Xib { @Override @XPath( "data/bool[@key = 'IBDocument.UseAutolayout'] = 'YES'") boolean hasUseAutoLayout(); }


Pour lire des documents XML de versions différentes, on utilise les sous-interfaces pour le binding effectif, en ne déclarant pour l’accès aux données qu’une seule variable du type de la super-interface :
Xib xib; xib = DomBinderUtils.xmlContentToJava(new File( "MyViewController_iPhone_3_0.xib"), Xib3.class); assertTrue(xib.hasUseAutoLayout()); xib = DomBinderUtils.xmlContentToJava(new File( "MyViewController_iPhone_8_0.xib"), Xib8.class); assertTrue(xib.hasUseAutoLayout());
Maintenant, ajoutons des méthodes pour lire des sous-structures contenues dans le fichier xib :
public interface Xib { boolean hasUseAutoLayout(); IBUIView getIBUIViewByTag(int tag); // e.g. 1104 interface IBUIView { String getFrame(); // e.g. "{{82, 77}, {43, 43}}" String getUserLabel(); // e.g. "LButton 20" } }

Évidemment, les expressions XPath ne sont pas du tout les mêmes pour les deux structures XML.

Pour Xcode 5.0.2 :
@XPath("/document[@version = '3.0']") public interface Xib3 extends Xib { @Override @XPath("@useAutolayout = 'YES'") boolean hasUseAutoLayout(); @Override @XPath("//*[@tag = $arg0]") IBUIView3 getIBUIViewByTag(int tag); interface IBUIView3 extends Xib.IBUIView { @Override @XPath("concat('{{', rect[@key = 'frame']/@x, ', '" + ", rect[@key = 'frame']/@y, '}, {'" + ", rect[@key = 'frame']/@width, ', '" + ", rect[@key = 'frame']/@height, '}}')") String getFrame(); // e.g. "{{82, 77}, {43, 43}}" @Override @XPath("@userLabel") String getUserLabel(); // e.g. "LButton 20" } }
Et pour Xcode 4.6.3 :
@XPath("/archive[@version = '8.00']") public interface Xib8 extends Xib { @Override @XPath( "data/bool[@key = 'IBDocument.UseAutolayout'] = 'YES'") boolean hasUseAutoLayout(); @Override @XPath("//object[int[@key = 'IBUITag'] = $arg0] ") IBUIView8 getIBUIViewByTag(int tag); interface IBUIView8 extends Xib.IBUIView { @Override @XPath("string[@key = 'NSFrame']") String getFrame(); // e.g. "{{82, 77}, {43, 43}}" @Override @XPath("//object[@class = 'IBObjectRecord'" + " and reference[@key = 'object']/" + "@ref = $this/@id]/" + "string[@key = 'objectName']") String getUserLabel(); // e.g. "LButton 20" } }
Quel que soit le binding initial de l’objet « xib », on pourra valider le code suivant grâce au polymorphisme :
final IBUIView view = xib.getIBUIViewByTag(1104); assertEquals("{{130, 100}, {128, 128}}", view.getFrame()); assertEquals("Bouton OK", view.getUserLabel());

Passons maintenant à de l’héritage qui ajoute des fonctionnalités :


Dans notre exemple, les interfaces IBUIButton dérivent des interfaces IBUIView, en leur ajoutant la méthode « getNormalTitle() », dont l’annotation @XPath dépend encore une fois de la version du document XML.

avc-binding-dom permet alors d’écrire ceci :
final IBUIView view = xib.getIBUIViewByTag(1104); final IBUIButton button = BinderUtils.rebind( view, IBUIButton8.class); assertEquals("OK", button.getNormalTitle());
Eh oui, les relations d’héritage dans notre diagramme ne permettent pas de remonter à IBUIButton8 à partir de « Xib8.getIBUIViewByTag(int): IBUIView8 ». Il faut faire une sorte de transtypage grâce à la méthode rebind().
Noter que d’autres possibilités de transtypage existent, par exemple la méthode self(Class<?>) quand l’interface de binding dérive explicitement de Binding<Node>, mais rebind() est une solution qui fonctionne dans tous les cas.

Pour que le transtypage fonctionne de façon transparente avec des interfaces génériques, c’est-à-dire si on voulait pouvoir écrire « rebind(view, IBUIButton.class) » au lieu de « rebind(view, IBUIButton8.class) », il faudrait plutôt que « Xib8.getIBUIViewByTag(int) » renvoie directement un IBUIButton8 et non un IBUIView8.
Le diagramme devient alors :


Et le code :
final IBUIView view = xib.getIBUIViewByTag(1104); // ni rebind(), ni IBUIButton8, ni IBUIButton3 final IBUIButton button = (IBUIButton) view; assertEquals("OK", button.getNormalTitle());

Voilà :-)

Edit : pour le dernier exemple, je montre qu’on peut utiliser un cast, plus simple qu’un rebind().


Note : les bouts de code et les exemples de cet article sont inclus dans les tests unitaires de avc-binding-dom.

11 juin 2013

Une API de binding Java/XML : avc-binding-dom

Si vous avez un besoin d’extraire en Java des données d’un document XML, je vous présente avc-binding-dom, une magnifique petite API.

Le principe est simple : on déclare le chemin XPath de la donnée qu’on veut lire à l’aide d’une annotation Java.

Exemple :
interface Annuaire {
@XPath("/annuaire/departement/@id")  
  String getDepartementId(); 
  @XPath("/annuaire/@annee") 
  int getAnnee(); 
} 
... 
// static import net.avcompris.binding.dom.helper.DomBinderUtils.*;
File file = ... ; // un fichier XML 
Annuaire annuaire = xmlContentToJava(file, Annuaire.class);
... annuaire.getAnnee(); // hop, ça marche

L’API est en 0.1.1, et, si elle est prometteuse, elle n’a été testée que sur des petits projets – en particulier, il doit lui manquer quelques types primitifs, la gestion des dates doit être brute de décoffrage, des trucs comme ça.

L’API permet d’ores et déjà de faire des requêtes paramétrées :
@XPath("//person[email = $arg0]/name") 
String getPersonNameByEmail(String email);

À partir d’un document XML, elle peut récupérer des tableaux, des couples clés / valeurs, divers types Java, des sous-structures, elle peut vérifier qu’un élément est présent, compter le nombre de éléments vérifiant une condition, faire des calculs à la volée…
Je vous renvoie à la page Return Types de la documentation.

Pour ceux qu’intéresse la mécanique interne, l’API attache l’interface Java passée en paramètre au nœud DOM. Cela signifie en particulier que si le document DOM est modifié, les méthodes Java getXxx() annotées avec des chemins XPath renverront les données mises à jour.

L’écosystème de cette API est le suivant :

Pour la petite histoire : j’avais initialisé une première version de cette API chez Capgemini pour un projet Java à l’automne 2010. Capgemini a ensuite validé le fait de passer ce développement open source, qui a continué de vivre sa vie sous la forme de XmlField, maintenu notamment par Nicolas Richeton, Jean-Pierre Grillon et Mabrouk Belhout.

Liens pour XmlField :
Blog de Nicolas Richeton : http://blog.richeton.com/
L’API XmlField a notamment la capacité de modifier les nœuds DOM depuis des méthodes Java de type setXxx() en suivant la même logique que les getXxx(), ce qui est très pratique pour de la manipulation de données XML.

J’ai ouvert le projet avc-binding-dom pour des besoins spécifiques, en réécrivant tout à la base, et en me concentrant sur mes besoins immédiats. Les méthodes setXxx() ne sont ainsi quasiment pas gérées dans avc-binding-dom.