Réaliser une map en full XSLT

Florian CAILLAUD
Publié par Florian CAILLAUD
Catégorie : BizTalk
25/11/2018

 

Un des artefacts fourni par BizTalk Server est la map. Cette map permet de transformer un message XML Source en message XML Destination. Pour cela, il existe concrètement 3 approches.

 

Le Microsoft BizTalk Mapper

 

Le mapper BizTalk est un outil intégré à Visual Studio permettant graphiquement de spécifier son schéma XSD en entrée et son schéma XSD en sortie de la transformation. Une fois que l’opération est réalisée, il est très rapide de décrire des transformations simples comme l’écriture d’un champ Source vers un champ Destination. Pour des choses plus complexes, BizTalk met à disposition une ToolBox remplie de fonctoïds. Ces éléments permettent de réaliser divers traitements (opérations sur les chaînes de caractères, logique, accès BDD, etc.).

 

mapper BizTalk

 

Néanmoins, on se rend rapidement compte que certaines transformations deviennent trop laborieuses, voire impossibles, à réaliser avec les fonctoïds fournis.

 

Le fonctoïd Scripting

 

Pour ces cas, BizTalk propose un fonctoïd Scripting. Ce dernier permet d’intégrer des scripts Inline (C#, JScript, VB, XSLT) ou même des méthodes d’assemblies externes à votre transformation.

 

scripting functoid

 

Cette approche permet, techniquement, de réaliser l’ensemble des transformations, même complexes. Cependant, pour certains traitements, cela peut rapidement devenir indigeste, difficile à lire et donc à maintenir.

 

Solution full XSLT (eXtensible Stylesheet Language Transformations)

 

Pour éviter cela, il est possible de ne pas utiliser de fonctoïd et de construire sa transformation directement en XSLT. Cette option est moins graphique. Certains diront qu’elle est également moins facile à maintenir tout au long du projet. Le fait est que cela permet souvent de réaliser des traitements complexes relativement rapidement. C’est cette approche que nous allons détailler ici.

Nous allons mettre en place une solution XSLT afin d’adresser le problème suivant :
à partir d’une liste brute de personnes en entrée de la map, nous souhaitons obtenir une liste de personnes uniques (avec un nom complet en lettres majuscules).

Pour cela, il faut créer une map (.btm) vide en spécifiant le schéma Source et le schéma Destination. Ensuite, en cliquant sur la grille, on accède aux propriétés de la map :

 

configurer chemin custom-xslt

 

La propriété « Custom XSLT Path » permet de donner le chemin vers un fichier .xsl qui décrira la transformation entre le message Source et le messageDestination.

En créant le fichier ci-dessous (et en ajoutant son URI dans la propriété« Custom XSLT Path »), il est possible de répondre à notre problématique.


<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>

<xsl:template match="/">
  <xsl:apply-templates select="/s0:Persons" />
</xsl:template>

<xsl:template match="/s0:Persons">
   <ns0:Persons>
      <xsl:for-each select="key('uniqueName', Person/Name)">
         <xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
            <xsl:variable name="fullUpperName" select="userCSharp:GetUpperCaseFullName(Name/text(), FirstName/text())"/>
            <Person>
               <FullName>
                  <xsl:value-of select="$fullUpperName" />
               </FullName>
            </Person>
         </xsl:if>
      </xsl:for-each>
   </ns0:Persons>
</xsl:template>

<msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
   public string GetUpperCaseFullName(string pName, string pFirstName)
   {
      string fullName = pName + " " + pFirstName;
      return fullName.ToUpper();
   }
]]>
</msxsl:script>
</xsl:stylesheet>

 

Ce code réalise une transformation qu’il serait impossible de mettre en place avec de simples fonctoïds, dans le mapper. En outre, on note que cela est visiblement plus simple que d’utiliser une méthode externe, au travers d’un fonctoïd Scripting.

Il faut également rappeler que BizTalk ne gère que la version 1.0 de XSL. Cependant, pour pallier à ce handicap, il est très facile de compenser en utilisant facilement des scripts C#, en les déclarant dans la balise « Script », puis en les appelant, dans le XSL, avec le préfixe mentionné dans l’attribut « implements-prefix ».

 

Astuce

 

Si la création from scratch du fichier XSL semble trop complexe ou si, simplement, la map est déjà existante, avec un certain nombre de liens et/ou fonctoïds, il est possible de générer ce fichier. Pour cela, il faut cliquer droit sur la map en question puis cliquer « Validate Map ».

De plus, il est également possible, comme avec les fonctoïds Scripting, dans le mapper, de faire appel àune méthode externe (dans une autre assembly), après l’avoir référencée. Pour cela, il faut au préalable créer un fichier d’extension (.xml) contenant les informations de chaque assembly en question :

 

<ExtensionObjects>
   <ExtensionObject Namespace="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="XSLTransform.Helper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=dbf4b132923889f0" ClassName="XSLTransform.Helper" />
</ExtensionObjects>

 

L’URI de ce fichier doit être renseignée dans la propriété « Custom Extension XML » de la map. Il est alors possible de modifier le fichier XSL comme suit :


<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp" xmlns:ScriptNS0="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>

<xsl:template match="/">
   <xsl:apply-templates select="/s0:Persons" />
</xsl:template>

<xsl:template match="/s0:Persons">
   <ns0:Persons>
      <xsl:for-each select="key('uniqueName', Person/Name)">
         <xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
            <xsl:variable name="fullUpperName" select="concat(ScriptNS0:ToUpper(Name/text()), ' ', ScriptNS0:ToUpper(FirstName/text()))"/>
            <Person>
               <FullName>
                  <xsl:value-of select="$fullUpperName" />
               </FullName>
            </Person>
         </xsl:if>
      </xsl:for-each>
   </ns0:Persons>
</xsl:template>

</xsl:stylesheet>