blog.pagesd.info

Aller au contenu | Aller au menu | Aller à la recherche

vendredi 19 juin 2009

Une fonction XSLT avec des paramètres

Jusque là, je connaissais déjà les "templates" qui me permettaient grosso-modo de structurer mes traitements XSLT en séparant en quelque sorte le "layout" de la page des traitements pour afficher les différents contenus :

<!-- Disposition générale du document -->
...
<fo:table-row height="27mm" margin-top="15mm">
        <fo:table-cell>
                <!-- Logo client -->
                <xsl:call-template name="client-logo" />
        </fo:table-cell>
        <fo:table-cell>
                <!-- Adresse client -->
                <xsl:call-template name="client-adresse" />
        </fo:table-cell>
</fo:table-row>
...
<!-- Affichage du logo du client -->
<xsl:template name="client-logo">
        <fo:block>
                <fo:external-graphic src="url('logo_client.jpg')" />
        </fo:block>
</xsl:template>

<!-- Affichage de l'adresse du client -->
<xsl:template name="client-adresse">
        <fo:block>
                <xsl:value-of select="a:OrganizationName" />
        </fo:block>
        <xsl:for-each select="a:PostalAddress/a:DeliveryAddress/a:AddressLine">
                <fo:block>
                        <xsl:value-of select="." />
                </fo:block>
        </xsl:for-each>   
        <fo:block>
                <xsl:value-of select="a:PostalAddress/a:PostalCode" />
                &#32;<xsl:value-of select="a:PostalAddress/a:Municipality" />
        </fo:block>
</xsl:template>

Et hier soir, j'ai appris un nouveau truc. Comme j'avais besoin d'afficher plusieurs fois des dates, je me suis retrouvé à faire du copier / coller du morceau de code suivant :

<fo:block>
        <xsl:value-of select="concat(substring(a:DocumentInfo/a:DateRange/a:StartDate, 9, 2), 
                                '/', substring(a:DocumentInfo/a:DateRange/a:StartDate, 6, 2),
                                '/', substring(a:DocumentInfo/a:DateRange/a:StartDate, 1, 4))" />
</fo:block>

Puis à chaque fois je devais remplacer "a:DocumentInfo/a:DateRange/a:StartDate" par "a:DocumentInfo/a:DateRange/a:EndDate" ou "a:DocumentInfo/a:PartialRange/a:ActualDate" ... Non seulement ce n'était pas très intéressant à faire, mais c'était surtout la porte ouverte à toutes les erreurs de frappe ou de copier / coller.

Heureusement pour moi, comme XSLT est quand même bien conçu, il est aussi possible de définir des templates avec des variables paramètres :

<xsl:template name="format-date">
        <xsl:param name="d" select="0" />
        <xsl:value-of select="concat(substring($d, 9, 2), '/', substring($d, 6, 2), '/', substring($d, 1, 4))" />
</xsl:template>

Ensuite, on peut appeler cette "fonction" XSLT en lui passant le paramètre de notre choix :

<xsl:call-template name="format-date">
        <xsl:with-param name="d" select="a:DocumentInfo/a:DateRange/a:StartDate" />
</xsl:call-template>

C'est vrai que ça ne fait pas économiser beaucoup de saisie, mais c'est quand même un peu plus clair et beaucoup moins risqué.

D'autres exemples sur l'article Programmer avec XSLT de Victor Stinner.

mercredi 4 mars 2009

Limiter le nombre de lignes dans un fil Atom

La boite XmlFile d'Altrr-Press permet depuis quelque temps d'afficher le contenu d'un flux RSS, RDF ou Atom en indiquant l'url du fil de syndication et la feuille de style XSLT à lui appliquer :

Par défaut, ces feuilles de styles XSLT affichent le titre du billet suivi de son contenu. Mais dans Altrr-Press, il existe aussi une version qui affiche une simple liste <ul> / <li> en présentant uniquement le titre des billets.

Lorsque le fil d'origine contient beaucoup de billets, le résultat n'est pas toujours heureux car cela génère un assez long pavé . C'est par exemple le cas avec un Gandiblog (et aussi Dotclear ?) pour lequel le flux de syndication contient 20 billets par défaut.

Il fallait donc trouver une méthode pour ne faire apparaitre que les 5 premiers billets du fil (ce qui correspond donc aux 5 derniers billets publiés) :

  • contacter l'auteur du blogue et lui demander s'il vous plait s'il peut limiter son fil Atom à 5 billets, merci.
  • se débrouiller et trouver comment gérer ça de mon côté

Avant de faire le barbare avec un style "overflow: hidden;" pour cacher les billets que je ne saurais voir, j'ai fait chat et demandé à un spécialiste XSLT s'il existerait un truc pour faire ça. Et ben oui, et pour une fois c'est même carrément simple.

Au lieu d'appliquer bêtement le template aux différents items du fil Atom :

<ul>
        <xsl:apply-templates select="atom:entry"/>
</ul>

Il faut indiquer que le template ne doit s'appliquer que pour les 5 premières entrées :

<ul>
        <xsl:apply-templates select="atom:entry[position() &lt;= 5]"/>
</ul>

L'astuce, c'est que comme le fichier XSLT est lui-même un document XML, il faut penser à échapper le signe inférieur et écrire &lt; au lieu du caractère <.

vendredi 22 août 2008

Régime XML

Cet été, j'ai eu envie d'un Altrr-Press tout beau, tout propre pour qu'il soit encore plus simple à utiliser et aussi un peu plus prêt pour le grand jour du passage sous .Net 2.0 (ou plus) : moins de code = moins de problèmes lors de la conversion.

Pour commencer, je me suis enfin décidé à supprimer les sources d'un certain nombre de boites devenues inutiles mais dont les codes sources avaient été épargnés (voire un peu oubliés) en attendant que tous les sites existant soient mis à jour.

  • FlashFile : insérait une animation flash (remplacé par IncludeFile)
  • Image : insérait une image (remplacé par IncludeFile)
  • Redirect : redirige le client vers une autre URL (remplacé par la macro [%AP.Redirect:url%])
  • Sitemap : insérait le plan du site (remplacé par la macro [%AP.Site.Map%])
  • Sitemenu : menu ou sous-menu de navigation (remplacé par la macro [%AP.Site.Menu:level1-level2%])

Note : si vous connaissez des sites qui ont encore besoin de ces boites, 20071029_update.sql et 20071030_update.sql sont vos amis.

Pour continuer sur ma lancée, j'ai aussi cherché comment éliminer la boite SqlRepeat qui prend le résultat d'une requête sous forme de DataReader pour générer un tableau HTML à l'aide d'un système de template très primaire.

D'abord, il y a le template d'en-tête :

<table class='repertoire'>

Puis le template pour le détail des lignes :

<tr>
  <td class='col1'>{0}<br /></td>
  <td class='col2'>{1} {3} {2}<br />{4}</td>
  <td class='col3'>Tél:&amp;nbsp; {7}<br />Fax: {8}</td>
</tr>

Et pour finir celui pour la fin du tableau :

</table>

Et côté code, en simplifiant ça donne à peu près ça :

StringBuilder html = new StringBuilder();
html.Append(templateHead);
DataReader dr = Data.Base.ExecuteReader(sqlQuery);
while (dr.Read()) {
  html.Append(ToHtmlRow(dr, templateLoop));
}
dr.Close();
html.Append(templateFoot);

Pour chaque ligne du DataReader, la fonction ToHtmlRow() génère une ligne en remplaçant les marqueurs {#} par la colonne correspondante dans la ligne en cours DataReader.

Le but étant de supprimer une boite, il faut donc trouver par quoi la remplacer. Le plus simple est de passer par la boite à tout faire RawContent et d'y coller le tableau HTML final. Etant donné que cette boite n'est utilisée que sur le site de Saint-Privat, cela semble une solution raisonnable. Malgré tout, la boite RawContent est plutôt destinée aux petits bouts de codes de quelques lignes et là les tableaux HTML dépassent la toise.

Alors j'ai cherché s'il n'y aurait pas une autre méthode. Et TILT : XML. Ah ben ZUT alors!

Normalement, je préfère éviter d'utiliser le XML (et surtout le XSLT) parce que si c'est toujours assez facile quand on commence, on se retrouve vite avec des sacs de noeuds pour faire trois fois rien. (Ou alors il faut recourir à un collègue qui parle le XML couramment mais que quand il a fini j'ai pas toujours compris.) Mais là, l'envie de me débarrasser d'une boite quasiment inutilisée étant la plus forte, j'ai succombé. Et en fait, ça n'a pas été aussi compliqué que ce que je craignais.

Un premier bon point, c'est qu'il existe déjà une boite XmlFile qui prend un fichier XML pour générer du HTML en lui appliquant un fichier XSLT. C'est un truc qui existe depuis toujours dans IBuySpy et que j'ai adapté dans Altrr-Press. Dans la pratique, je ne m'en sers presque jamais, sauf de temps en temps pour afficher des fils RSS ou Atom ou bien des liens enregistrés dans Blogmarks.

Dans le cas présent, il fallait donc que je me débrouille pour prendre le résultat de ma requête comme source XML et surtout que je réussisse à créer un fichier XSLT qui transforme ce résultat en tableau HTML.

Pour la source XML, c'était vraiment trop facile : quelques minutes à passer dans l'aide et 2 lignes de codes ont suffit (merci le .Net) :

DataSet ds = Data.Base.ExecuteDataSet(sqlQuery);
string xml = ds.GetXml();

Ce qui donne :

<NewDataSet>
  <Table>
    <categories>Electricien</categories>
    <organization>Ets Martin</organization>
    <legalName />
    <givenName />
    <streetAddress>Le Pré-Vert</streetAddress>
    <postalCode>12345</postalCode>
    <placeName>Enville</placeName>
    <workPhone>01 02 03 04 05</workPhone>
    <faxPhone />
  </Table>
  ...
</NewDataSet>

Puis en partant de fichiers XSLT existants et après pas mal d'essais et de messages d'erreurs, j'ai réussi à obtenir un fichier XSLT qui reproduisait le résultat de mon futur ex-système de template :

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:template match="/">
    <table class='repertoire'>
    <xsl:for-each select='NewDataSet/Table'>
      <tr>
        <td class='col1'>
          <xsl:value-of select='categories'/>
          <br />
        </td>
        <td class='col2'>
          <xsl:value-of select='organization'/>
          <xsl:if test="givenName != ''">
            <xsl:if test="organization != ''">
              <xsl:text> - </xsl:text>
            </xsl:if>
            <xsl:value-of select='givenName'/>
            <xsl:text> </xsl:text>
            <xsl:value-of select='legalName'/>
          </xsl:if>
          <br />
          <xsl:value-of select='streetAddress'/>
        </td>
        <td class='col3'>
          Tél: <xsl:value-of select='workPhone'/>
          <br />
          <xsl:if test="faxPhone != ''">Fax: <xsl:value-of select='faxPhone'/></xsl:if>
        </td>
      </tr>
    </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

Et même que grâce à l'instruction <code><xsl:if test="faxPhone != ''"> ... </xsl:if></code>, le résultat obtenu est encore mieux que l'original. Si je ne me retenais pas, je passerais tout en XML.

Mais pour l'instant, je vais me contenter de supprimer la boite SqlRepeat et ses 3 fichiers sources devenus inutiles. C'est toujours ça de pris (et au final ça fait quand même 6 boites de moins !).

jeudi 2 mars 2006

Throwing from XSLT

Vu sur http://weblogs.asp.net/george_v_reilly/archive/2006/03/01/439402.aspx

I (George V. Reilly, not me) needed to add some declarative error checking to some XSLT templates recently. Specifically, I wanted to throw an error if my selects yielded an empty string, indicating that the input XML was wrong.

Unfortunately, there seems to be no easy way of doing this in XSLT, nor in XslTransform. The approved way is to validate against an XSD schema, but for various reasons, I didn't want to go to the hassle of creating one.

I found a partial solution using xsl:message with the terminate="yes" attribute. Under XslTransform.Transform() the following code throws an exception if the XPath expression is empty.

<xsl:if test="not(/some/xpath/expression)">
     <xsl:message terminate="yes">Missing expression</xsl:message>
</xsl:if>
<xsl:value-of select="/some/xpath/expression" />

It doesn't do anything, however, in XMLSpy.

The downside, of course, is that you have to maintain the expression in two places, and the template becomes littered with those annoying tests.

(publié à l’origine sur http://www.bigbold.com/snippets/pos...)

jeudi 19 janvier 2006

Blogmarks to Html conversion

Ce fichier permet de transformer le fil atom de Blogmarks (par exemple http://api.blogmarks.net/user/ms_michel) en code html afin de pouvoir l'intégrer à une page web.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
        xmlns:atom="http://www.w3.org/2005/Atom" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:dc="http://purl.org/dc/elements/1.1/">
  <xsl:output method="html" />
  <xsl:template match="/">
    <xsl:apply-templates select="/atom:feed/atom:head" mode="before" />
    <xsl:apply-templates select="/atom:feed/atom:entry" />
    <xsl:apply-templates select="/atom:feed/atom:head" mode="after" />
  </xsl:template>
  <xsl:template match="atom:feed/atom:head" mode="before" >
    <!-- <h3><xsl:value-of select="atom:title" /></h3> -->
  </xsl:template>
  <xsl:template match="atom:feed/atom:head" mode="after">
    <p><a href="{atom:link[@rel='alternate']/@href}"><img src="http://blogmarks.net/img/88x31_neg.png" alt="blogmarks.net" /></a></p>
  </xsl:template>
  <xsl:template match="atom:feed/atom:entry">
    <div>
      <xsl:choose>
        <xsl:when test="position() mod 2 = 1">
          <xsl:attribute name="class">bm_blogmarks bm_odd</xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="class">bm_blogmarks bm_even</xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <a href="{atom:link[@rel='related']/@href}"><img src="{atom:link[@rel='image']/@href}" alt="" /></a>
      <h4><a href="{atom:link[@rel='related']/@href}"><xsl:value-of select="atom:title" /></a></h4>
      <p><xsl:value-of select="atom:summary" disable-output-escaping="yes" /></p>
      <p class="blogmarks-tags">
        <xsl:value-of select="substring(atom:published, 0, 11)" />
        <xsl:if test="atom:category">
          <xsl:for-each select="atom:category">
            <xsl:text> - </xsl:text><a href="{@term}{@sheme}"><xsl:value-of select="@label" /></a>
          </xsl:for-each>
        </xsl:if>
      </p>
    </div>
  </xsl:template>
</xsl:stylesheet>

Le code html généré est inspiré par celui de Hot Links. Il peut ensuite être présenté grâce à la feuille de style CSS suivante :

.bm_blogmarks {
  margin: 10px auto;
  padding: 1%;
  background-color: #f5f5f5;
  border: 1px solid #d9d9d9;
  width: 97%;
  overflow:auto;
}
.bm_even {
  background-color: #fcfcfc;
}
.bm_blogmarks h4 {
  margin-top: 0;
}
.bm_blogmarks p.bm_tags {
  margin-bottom: 0;
  display: block;
  clear: left;
}
.bm_blogmarks img {
  margin: 0 0px 5px 10px;
  float: right;
  border: 0;
  clear: none;
  width: 144px;
  height: 107px;
}
.bm_even img {
  margin: 0 10px 5px 0px;
  float: left;
}

(publié à l'origine sur http://www.bigbold.com/snippets/pos...)

lundi 16 janvier 2006

RDF to Html conversion

Ce fichier permet de transformer un fil RDF en code html afin de pouvoir l'intégrer à une page web.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:foo="http://purl.org/rss/1.0/">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <xsl:apply-templates select="/rdf:RDF/foo:channel"/>
    </xsl:template>
    <xsl:template match="/rdf:RDF/foo:channel">
        <h3><xsl:value-of select="foo:title"/></h3>
        <p><xsl:value-of select="foo:description"/></p>
        <ul>
            <xsl:apply-templates select="/rdf:RDF/foo:item"/>
        </ul>
    </xsl:template>
    <xsl:template match="/rdf:RDF/foo:item">
        <li>
            <a href="{foo:link}" title="{substring(dc:date, 0, 11)}"><xsl:value-of select="foo:title"/></a>
            <p><xsl:value-of select="foo:description" disable-output-escaping="yes" /></p>
        </li>
    </xsl:template>
</xsl:stylesheet>

(publié à l'origine sur http://www.bigbold.com/snippets/pos...)

RSS to Html conversion

Ce fichier permet de transformer un fil RSS (version 0.9x ou 2.0) en code html afin de pouvoir l'intégrer à une page web.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:wfw="http://wellformedweb.org/CommentAPI/">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <xsl:apply-templates select="/rss/channel"/>
    </xsl:template>
    <xsl:template match="/rss/channel">
        <h3><xsl:value-of select="title"/></h3>
        <p><xsl:value-of select="description"/></p>
        <ul>
            <xsl:apply-templates select="item"/>
        </ul>
    </xsl:template>
    <xsl:template match="/rss/channel/item">
        <li>
            <a href="{link}" title="{substring(pubDate, 0, 11)}"><xsl:value-of select="title"/></a>
            <p><xsl:value-of select="description" disable-output-escaping="yes" /></p>
        </li>
    </xsl:template>
</xsl:stylesheet>

(publié à l'origine sur http://www.bigbold.com/snippets/pos...)

Atom to Html conversion

Ce fichier permet de transformer un fil atom en code html afin de pouvoir l'intégrer à une page web.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:dc="http://purl.org/dc/elements/1.1/">
    <xsl:output method="html"/>
    <xsl:template match="/">
    <xsl:apply-templates select="/atom:feed/atom:head"/>
        <xsl:apply-templates select="/atom:feed"/>
    </xsl:template>
    <xsl:template match="atom:feed/atom:head">
        <h3><xsl:value-of select="atom:title"/></h3>
        <xsl:if test="atom:tagline"><p><xsl:value-of select="atom:tagline"/></p></xsl:if>
        <xsl:if test="atom:subtitle"><p><xsl:value-of select="atom:subtitle"/></p></xsl:if>
    </xsl:template>
    <xsl:template match="/atom:feed">
        <h3><xsl:value-of select="atom:title"/></h3>
        <xsl:if test="atom:tagline"><p><xsl:value-of select="atom:tagline"/></p></xsl:if>
        <xsl:if test="atom:subtitle"><p><xsl:value-of select="atom:subtitle"/></p></xsl:if>
        <ul>
            <xsl:apply-templates select="atom:entry"/>
        </ul>
    </xsl:template>
    <xsl:template match="atom:entry">
        <li>
            <a href="{atom:link[@rel='related']/@href}" title="{substring(atom:published, 0, 11)}"><xsl:value-of select="atom:title"/></a>
            <xsl:choose>
                <xsl:when test="atom:content != ''">
                    <p><xsl:value-of select="atom:content" disable-output-escaping="yes" /></p>
                </xsl:when>
                <xsl:otherwise>
                    <p><xsl:value-of select="atom:summary" disable-output-escaping="yes" /></p>
                </xsl:otherwise>
            </xsl:choose>
        </li>
    </xsl:template>
</xsl:stylesheet>

(publié à l'origine sur http://www.bigbold.com/snippets/pos...)

dimanche 15 janvier 2006

Xmlfeed et Blogmarks

Amélioration en ce qui concerne la méthode utilisée pour gérer la transformation d'un document xml via xslt. Jusqu'à présent, c'était fait quasi-automatiquement à l'aide des propriétés DocumentSource et TransformSource du contrôle Xml. Après modification, la boite Xmlfeed fonctionne de la façon suivante :

  • la source est chargée dans un objet XmlDocument puis affectée à la propriété Document du contrôle Xml,
  • la fichier xsl est chargé dans un objet XslTransform qui est ensuite affecté à la propriété Transform du contrôle XML.

Cette solution permet d'avoir une meilleure gestion des erreurs, et aussi :

  • de reconnaitre le type de fil et donc de sélectionner automatiquement la feuille de style adéquate si besoin est,
  • de “bidouiller” le fil atom de Blogmarks car la mention "draft-ietf-atompub-format-05″ dans xlmns=”http://purl.org/atom/ns#…" (mais sans message d'erreur pour trouver comment gérer ça plus proprement).

En configuration, les types de fils xml sont passés de 6 (atom, sommaire atom, rss2, rss1, rdf et rss0.9) à 4 (atom, rdf, rss et automatique).

Par ailleurs, une feuille de style spéciale a été créée pour convertir le fil atom de Blogmarks en html, afin de gérer la miniature écran et la liste des tags.

Note : l'utilisation des propriétés Document et Transform en lieu et place de DocumentSource et TransformSource a été également mise en place au niveau de la boite Xmlfile.