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


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) :