XSLT et modification de namespace

Cet article a pour but d'éclaircir l'utilisation des namespaces au sein des feuilles de transformations XSL.

Imaginons qu'il faille changer le namespace global du xml suivant :

<?xml version="1.0" encoding="utf-8"?>
<pef:OPS xmlns:pef="http://www.fooldomain.fr">
  <pef:DAT TM="CU">
    ...
  </pef:DAT>
</pef:OPS>

En celui-ci :

<?xml version="1.0" encoding="utf-8"?>
<pef:OPS xmlns:pef="http://developpef.blogspot.com">
  <pef:DAT TM="CU">
    ...
  </pef:DAT>
</pef:OPS>

A première vue, le template suivant devrait faire l'affaire :

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:pef="http://www.fooldomain.fr"
  version="1.0">
  
 <xsl:template match="pef:OPS">
  <pef:OPS xmlns:pef="http://developpef.blogspot.com">
   <pef:DAT TM="CU">
   ...
   </pef:DAT>
  </pef:OPS>
 </xsl:template>
  
</xsl:stylesheet>

Or, voici ce qui sera produit :

<?xml version="1.0" encoding="utf-8"?>
<pef:OPS xmlns:pef="http://developpef.blogspot.com">
  <pef:DAT xmlns:pef="http://www.fooldomain.fr" TM="CU">
    ...
  </pef:DAT>
</pef:OPS>

Le processeur XSL ajoutera l'ancien namespace au premier élément qui n'en porte pas... Pourquoi donc? Tout d'abord, la déclaration du namespace telle que faite sur l'élément OPS n'affecte en aucun cas les namespaces utilisés par le processeur. Lui ne connait que ceux qui sont déclarés en en-tête du document. Il va donc naturellement rajouter le namespace qu'il utilise au premier élément considéré. En réalité, dans ce cas de figure, il faut explicitement indiquer au processeur les deux namespace à utiliser : le premier lui permettant de lire le XML entrant et le second décrivant le XML sortant. Voici donc à quoi doit ressemble notre XSLT :

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:peffool="http://www.fooldomain.fr"
  xmlns:pef="http://developpef.blogspot.com"
  exclude-result-prefixes="peffool"
  version="1.0">
  
 <xsl:template match="peffool:OPS">
  <pef:OPS>
   <pef:DAT TM="CU">
   ...
   </pef:DAT>
  </pef:OPS>
 </xsl:template>
  
</xsl:stylesheet>

Ainsi, le template travaillera bien sur les éléments basés sur l'ancien namespace peffool:OPS pour produire le nouveau XML basé sur le bon namespace xmlns:pef="http://developpef.blogspot.com"

Hope this helps! :)


Fichier(s) joint(s) :

XSLT et transcodage

Lors de la transformation d'un fichier XML, il peut être nécessaire d'effectuer un transcodage sur certains éléments (migration d'une ancienne valeur vers une nouvelle). Les choses se compliquent si en plus ceci doit se faire à partir d'un référentiel externe contenant le mapping des données à utiliser.

Concrètement, voici un exemple de fichier XML initial :

<?xml version="1.0"?>
<OPS AD="FRA" FR="FRA">
      <LOG RC="FHZW" MA="VIAUD">
          <SPE SN="ANE" WT="5000.00">
            <PRO FF="A" PS="FRE" PR="WHL" TY="BOX" CF="1.00"></PRO>
            <RAS SR="23E6"></RAS>
          </SPE>
          <SPE SN="ANE" WT="5000.00">
            <PRO FF="A" PS="FRE" PR="WHL" TY="BOX" CF="1.00"></PRO>
            <RAS SR="24F8"></RAS>
         </SPE>
     </LOG>
</OPS>

Qui doit être convertit pour obtenir ceci (on change la valeur de l'attribut SR de l'élement RAS) :

<?xml version="1.0"?>
<OPS AD="FRA" FR="FRA">
      <LOG RC="FHZW" MA="VIAUD">
          <SPE SN="ANE" WT="5000.00">
            <PRO FF="A" PS="FRE" PR="WHL" TY="BOX" CF="1.00"></PRO>
            <RAS SR="FR23E6"></RAS>
          </SPE>
          <SPE SN="ANE" WT="5000.00">
            <PRO FF="A" PS="FRE" PR="WHL" TY="BOX" CF="1.00"></PRO>
            <RAS SR="FR24F8"></RAS>
         </SPE>
     </LOG>
</OPS>

La table de transcodage utilisée est située dans le document 'transcodage.xml' :

<mapping>
    <data>
        <item v1="23E6" v3="FR23E6" />
        <item v1="24F8" v3="FR24F8" />
    </data>
</mapping>

Pour arriver à nos fins, XSLT met à notre disposition l'élément xsl:key qui permet de définir une clé de recherche dans un document. Voici comment l'utiliser :

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<xsl:variable name="map" select="document('transcodage.xml')"/>
<xsl:key name="mapPorts" match="/mapping/data/item" use="@v1"/>
 
<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>
 
<xsl:template match="@SR[parent::RAS]">
    <xsl:variable name="codeV1" select="."/>
    <xsl:for-each select="$map">
        <xsl:attribute name="SR">
            <xsl:value-of select="key('mapPorts', $codeV1)/@v3"/>
        </xsl:attribute>
    </xsl:for-each>
</xsl:template>
 
</xsl:stylesheet>

Voici en détail le déroulement des opérations :

  • La variable map contient la table de transcodage.
  • La clé mapPorts permet d'indiquer : "je recherche tous les éléments correspondant à /mapping/data/item et je teste leur attribut v1
  • Dans le template gérant l'attribut SR du noeud RAS, on parcourt les éléments de la table de transcodage (en changeant temporairement de noeud racine grâce à xsl:for-each) afin de récupérer la valeur de l'attribut v3 de l'élément dont l'attribut v1 porte la valeur actuelle. En effet, l'appel à la fonction key() retourne l'élément qui a répondu aux conditions passées en paramètres (chemin décrit par la clé et valeur d'attribut)

Ainsi, chaque ancienne valeur sera remplacée par la valeur correspondante issue du document de mapping. Une autre syntaxe est possible, sans utiliser de clé :

<xsl:template match="@SR[parent::RAS]">
    <xsl:attribute name="SR">
       <xsl:value-of select="$map/mapping/data/item[@v1 = current()]/@v3"/>
    </xsl:attribute>
</xsl:template>

Cette solution est tout de même à modérer dans le cas de volume important de données.

Mon prochain article sera dédié au changement de namespace lors d'une transformation XSL.

Bon courage!


Fichier(s) joint(s) :

Exporter un document Calc en XML

Encore une fois, les choses ne sont pas si simples qu'il n'y paraît...

Ici le besoin consiste à exporter un classeur OpenOffice Calc en XML, selon une structure personnalisée. Le logiciel offre déjà la possibilité d'exporter sous forme d'XML à la sauce Micro$oft : autant dire que le résultat n'est pas glorieux :

Le document Calc :

Le XML produit (Fichier > Enregistrer sous... > Microsoft Excel 2003 XML) :

<?xml version="1.0" encoding="utf-8"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x2="http://schemas.microsoft.com/office/excel/2003/xml" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel">
  <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
    <Colors>
      <Color>
        <Index>3</Index>
        <RGB>#c0c0c0</RGB>
      </Color>
      <Color>
        <Index>4</Index>
        <RGB>#ff0000</RGB>
      </Color>
    </Colors>
  </OfficeDocumentSettings>
  <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
    <WindowHeight>9000</WindowHeight>
    <WindowWidth>13860</WindowWidth>
    <WindowTopX>240</WindowTopX>
    <WindowTopY>75</WindowTopY>
    <ProtectStructure>False</ProtectStructure>
    <ProtectWindows>False</ProtectWindows>
  </ExcelWorkbook>
  <Styles>
    <Style ss:ID="Default" ss:Name="Default" />
    <Style ss:ID="Result" ss:Name="Result">
      <Font ss:Bold="1" ss:Italic="1" ss:Underline="Single" />
    </Style>
    <Style ss:ID="Result2" ss:Name="Result2">
      <Font ss:Bold="1" ss:Italic="1" ss:Underline="Single" />
      <NumberFormat ss:Format="Euro Currency" />
    </Style>
    <Style ss:ID="Heading" ss:Name="Heading">
      <Alignment ss:Horizontal="Center" />
      <Font ss:Bold="1" ss:Italic="1" ss:Size="16" />
    </Style>
    <Style ss:ID="Heading1" ss:Name="Heading1">
      <Alignment ss:Horizontal="Center" ss:Rotate="90" />
      <Font ss:Bold="1" ss:Italic="1" ss:Size="16" />
    </Style>
    <Style ss:ID="co1" />
    <Style ss:ID="ta1" />
    <Style ss:ID="ta_extref" />
  </Styles>
  <ss:Worksheet ss:Name="Exemple1">
    <Table ss:StyleID="ta1">
      <Column ss:Span="1" ss:Width="64.2614" />
      <Row ss:Height="12.8409">
        <Cell>
          <Data ss:Type="String">Donnée 1</Data>
        </Cell>
        <Cell>
          <Data ss:Type="String">Donnée 1.1</Data>
        </Cell>
      </Row>
      <Row ss:Height="12.8409">
        <Cell>
          <Data ss:Type="String">Donnée 2</Data>
        </Cell>
        <Cell>
          <Data ss:Type="String">Donnée 1.2</Data>
        </Cell>
      </Row>
    </Table>
    <x:WorksheetOptions />
  </ss:Worksheet>
  <ss:Worksheet ss:Name="Exemple2">
    <Table ss:StyleID="ta1">
      <Column ss:Span="1" ss:Width="64.2614" />
      <Row ss:Height="12.8409">
        <Cell>
          <Data ss:Type="String">Feuille 2 test</Data>
        </Cell>
        <Cell>
          <Data ss:Type="String">Autre valeur</Data>
        </Cell>
      </Row>
    </Table>
    <x:WorksheetOptions />
  </ss:Worksheet>
</Workbook>

Pas très lisible tout ça...

Avant de continuer, il faut savoir que les fichiers Calc (.ods) ne sont en fait que des fichiers zip contenant des XML. La structure de base du classeur, tel que lu par OpenOffice est la suivante :

  • content.xml
  • meta.xml
  • mimetype.xml
  • settings.xml
  • styles.xml

Tous ces fichiers décrivent le contenu, la présentation et les métas informations constituant le classeur. Le plus intéressant est le premier. En voici un extrait :

  <office:body>
    <office:spreadsheet>
      <table:table table:name="Exemple1" table:style-name="ta1" table:print="false">
        <office:forms form:automatic-focus="false" form:apply-design-mode="false" />
        <table:table-column table:style-name="co1" table:number-columns-repeated="2" table:default-cell-style-name="Default" />
        <table:table-row table:style-name="ro1">
          <table:table-cell office:value-type="string">
            <text:p>Donnée 1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string">
            <text:p>Donnée 1.1</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row table:style-name="ro1">
          <table:table-cell office:value-type="string">
            <text:p>Donnée 2</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string">
            <text:p>Donnée 1.2</text:p>
          </table:table-cell>
        </table:table-row>
      </table:table>
      <table:table table:name="Exemple2" table:style-name="ta1" table:print="false">
        <table:table-column table:style-name="co1" table:number-columns-repeated="2" table:default-cell-style-name="Default" />
        <table:table-row table:style-name="ro1">
          <table:table-cell office:value-type="string">
            <text:p>Feuille 2 test</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string">
            <text:p>Autre valeur</text:p>
          </table:table-cell>
        </table:table-row>
      </table:table>
    </office:spreadsheet>
  </office:body>

On retrouve ici les informations contenues dans les cellules de chaque feuille.

Ainsi, puisque la structure des documents Calc est basée sur du XML, si l'on désire l'exporter sous un autre format, il faudra passer par... XSLT!

Admettons que nous voulions exporter le document présenté plus haut sous la forme :

<mapping>
 <Exemple1>
  <data col1="Donnée 1" col2="Donnée 1.1" />
  <data col1="Donnée 2" col2="Donnée 1.2" />
 </Exemple1>
 <Exemple2>
 ...
</mapping>

Il faudra donc trouver la feuille de transformation XSL convenable. Par exemple, celle-ci (le code est perfectible mais est présenté à titre d'exemple) :

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
 xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
 xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
 exclude-result-prefixes="office table">
 
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes"/>
 
<!-- To avoid annoying header data -->
<xsl:template match="office:meta" />
<xsl:template match="office:settings" />
<xsl:template match="office:styles" />
<xsl:template match="office:master-styles" />
<xsl:template match="office:automatic-styles" />
<xsl:template match="office:font-face-decls" />
<xsl:template match="office:scripts" />
<xsl:template match="office:font-face-decls" />
 
<!-- parse document -->
<xsl:template match="office:spreadsheet">
 <xsl:element name="mapping">
  <xsl:for-each select="table:table">
   <!-- take only sheets that have real data (multiple columns) -->
   <xsl:if test="table:table-column[@table:number-columns-repeated]">
    <xsl:variable name="tablename" select="@table:name" />
    <xsl:element name="{$tablename}">
     <xsl:for-each select="table:table-row">
      <xsl:element name="item">
       <xsl:for-each select="table:table-cell">
        <xsl:variable name="attrNameV1" select="'col1'" />
        <xsl:variable name="attrNameV3" select="'col2'" />
        <xsl:if test="position() = 1">
         <xsl:attribute name="{$attrNameV1}">
          <xsl:value-of select="text:p" />
         </xsl:attribute>
        </xsl:if>
        <xsl:if test="position() = 2">
         <xsl:attribute name="{$attrNameV3}">
          <xsl:value-of select="text:p" />
         </xsl:attribute>
        </xsl:if>
       </xsl:for-each>
      </xsl:element>
     </xsl:for-each>
    </xsl:element>
   </xsl:if>
  </xsl:for-each>
 </xsl:element>
</xsl:template>
</xsl:stylesheet>

En détails, voici comment elle fonctionne :

  • Les namespaces déclarés correspondent à ceux attendus par OpenOffice pour présenter ses propres balises.
  • Le premier bloc de templates supprime le traitement de tout ce qui est méta-infos, styles et autres scripts éventuellement contenus dans le classeur.
  • L'élement table:table décrit chaque feuille du classeur
  • Le test xsl:if permet de ne pas traiter les feuilles ne contenant aucune donnée (chaque feuille contenant par défault toujours au moins une cellule vide)
  • Enfin, on parcour chaque ligne (table:table-row) et chaque cellule (table:table-cell) pour en extraire le contenu (text:p)

Pour rendre cet export disponible dans OpenOffice, il faut le configurer. Pour ce faire : Outils > Paramétrages du filtre XML... > Nouveau... Et renseigner le chemin vers notre XSLT dans la section "XSLT pour export"

Notre export sera finalement réalisable depuis le menu Fichier > Exporter...

Pourquoi faire simple quand on peut faire compliqué!


Fichier(s) joint(s) :



Pourquoi Scala ne m'a pas séduit...

Il y a quelques temps, j'écrivais une série d'articles sur Scala et la programmation fonctionnelle, en tant que débutant et pour partager ce que j'ai pu découvrir de ces concepts.

Même si l'aspect fonctionnel est intéressant, après plusieurs tentatives de m'y remettre, je dois avouer que je n'ai pas vraiment accroché. Et pour expliquer les raisons de mon changement d'avis, je m'en remets à ce post de Stephen Colebourne qui exprime plutôt clairement ce que j'ai ressenti. Si je devais en extraire les points les plus marquants :

By the way, if you're looking at Scala, you may come across conference presentations, blog posts and forums that show small snippets of code in Java and Scala and show how much less code you have to write in Scala, and how much simpler it appears to be. This is a clever trick. The real complexity occurs when you start mixing each of the seemingly simple features together in a large codebase, on a big team, over a period of time, where code is read far more than it is written. That is when you start to appreciate that some of Java's restrictions are there for a reason.
The language is a well-meaning attempt to create something with a higher abstraction level. But what got created is a language that has huge inherent complexity and doesn't really address the true issues that developers face today, yet is being pitched as being a suitable replacement for Java, something which I find to be bonkers.

Tout ca pour dire qu'au final, même si Scala m'avais semblé être une alternative séduisante à Java, le plongeon rapide dans un niveau de complexité élevée m'a quelque peu rebuté... Mon but n'était pas d'apprendre un nouveau langage, mais plutôt de trouver quelque chose de complémentaire à Java.

Comme "alternative/complément" à Java, je préfère Groovy. Comme "Java sugar", je préfère Xtend.


Fichier(s) joint(s) :

Déployer Java dans le cloud avec Jelastic

Depuis quelques temps, les systèmes de gestion d'applications Cloud (PaaS - Plateforme as a Service) en tout genre se multiplient. Mais certains à mon sens sortent vraiment du lot en proposant de réels "services". C'est le cas notamment de Jelastic qui permet de créer des environnements de déploiement d'applications Java très facilement.

Voici un graphique résumant les fonctionnalités offertes :

Avec seulement un compte sur ce site, il est possible de récupérer le code source depuis un serveur de gestion de configuration, packager le tout grâce à Maven, déployer sur un serveur Tomcat et gérer une base de données. Le tout éventuellement de manière sécurisée (SSL) et ajustable selon le niveau de performances requis (load balancing). Qui dit mieux??!!

Le but étant de faciliter la mise en place de multiples processus de déploiements d'un application sur différents environnements (développement, test, recette...) sans avoir à se soucier de l'aspect matériel (technique). En effet, la liste des outils mis à disposition est déjà assez impressionnante :

Java developers have access to support for any JVM-based application including pure Java 6 or 7, JRuby, Scala and Groovy. In addition, Jelastic provides support for SQL databases that include MariaDB, MySQL, and PostgreSQL. Non-SQL database support is provided for MongoDB and CouchDB. Supported application servers include Tomcat 6 and 7, GlassFish and Jetty. Load balancing and caching is done though integrated NGINX, and developer tools integration via Maven and Ant plug-ins.

L'idée est ingénieuse, la mise en oeuvre semble bien aboutit puisque le site est très facile à utiliser, reste donc à savoir si cela séduira les développeurs et managers. La plateforme est encore en version beta mais commence à faire ses preuves comme on peut le voir au travers de son blog et sa communauté semble déjà bien active.

Un outil de plus à suivre de près!


Fichier(s) joint(s) :



Des outils pour maîtriser contenu XML, schémas et transformations

La problématique à l'origine de cet article est la suivante : quels sont les outils (simples et visuels) existants pour déduire la feuille de transformation (XSL) nécessaire à la conversion de fichiers XML entre deux versions de schémas (XSD).

De prime abord, il existe beaucoup de logiciels sur le web répondant à ce besoin. Or il se trouve qu'aucun ne possède réellement l'ensemble des fonctionnalités désirées. Je vais donc ici compiler les petites astuces pour se munir de tout le nécessaire afin de se faciliter la vie.

La première étape est de réussir à comparer les schémas XSD pour essayer de lier les correspondances existantes entre les deux versions. Pour ce faire, le logiciel DiffDog de la société Altova, leader dans le domaine de la manipulation de fichiers XML, me semble de loin le plus simple et précis. Voici par exemple ce qu'il est capable de faire en ouvrant simplement les deux schémas :

Rien de plus aisé donc que de mapper les éléments, même dans des schémas longs et complexes, grâce à son interface accessible. Puis, à partir de cette construction, il est possible de générer le fichier XSL nécessaire à la transformation entre les deux versions.

Petit bémol cependant, comme vous pouvez le voir sur l'aperçu, DiffDog ne tient pas compte des éléments non mappés : ceux-ci n'apparaitront donc jamais dans le XSL généré... Plutôt gênant s'il s'agit d'attributs obligatoires! Dans le même principe, les contraintes spécifiées (format des données, caractère obligatoire...) ne sont pas gérées.

Il faut donc prévoir dans ce scénario de modifier à la main la feuille de transformation. Comment donc consulter le contenu du schéma de manière la plus simple possible afin de retrouver ces informations?

Le projet WTP de la fondation Eclipse dispose d'un éditeur de schémas XSD très complet permettant notamment d'en visualiser le contenu sous la forme de diagramme de classe :

Or là encore, pas moyen de visualiser rapidement l'arborescence des éléments ainsi que les contraintes... L'idéal serait une présentation sous forme de graphe. Altova propose le logiciel XMLSpy qui dispose d'une fonction de visualisation sous cette forme. Il est très complet et contient une palette d'outils très intéressante mais petit détail : il est payant...

En cherchant un peu sur le net, on peut voir qu'il existe des alternatives : WSVT, l'ancêtre de WTP. Mais là encore le bât blesse : la visualisation en graphe n'est plus disponible depuis la version 1.5 (juillet 2006 intégré dans la version 3.1 d'Eclipse!) comme l'indique ce bug!!!

Heureusement, la distribution en question est toujours téléchargeable à l'adresse suivante. Avec ceci, il est enfin possible de lire le contenu d'un schéma sous forme de graphe de manière claire, concise et complète :

Ici apparaissent bien plus explicitement les contraintes et types de relations entre les éléments.

Je ne sais pas trop pourquoi ce formalisme a été abandonné par WTP (peut-être à cause du lobbying des éditeurs de logiciels spécialisés?).

Dernier outil utile : la génération d'un XML basique à partir d'un schéma. Cette fois-ci, la dernière version de WTP contient la fonction "generate > XML file" disponible sur les fichiers XSD. Elle permet d'obtenir un fichier type répondant à l'ensemble des exigences du schéma. L'outil d'édition s'appuie ensuite sur ses spécifications pour aider la saisie du contenu :

Pour terminer, voici un petit bout de code Java permettant d'exécuter la transformation des fichiers XML :

import java.io.File;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class Test {

 public static void main(String[] args) {
  try {
   File xmlFile = new File("OOB20110921007705.xml");
   File xsltFile = new File("ttt.xsl");
   File htmlFile = new File("result.xml");
   Source xmlSource = new StreamSource(xmlFile);
   Source xsltSource = new StreamSource(xsltFile);
   Result xmlResult = new StreamResult(htmlFile);
   TransformerFactory transFact = TransformerFactory.newInstance();
   Transformer trans = transFact.newTransformer(xsltSource);
   trans.transform(xmlSource, xmlResult);
  } catch (TransformerException e) {
   e.printStackTrace();
  }

 }

}

Voici donc une compilation des outils les plus simples (et si possibles gratuits) pour gérer du contenu XML. J'espère vous avoir fait économiser quelques heures de Googling!


Fichier(s) joint(s) :