Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

2012-07-14

Matching Classes In XSLT

A while ago (well 2 years) I posted an entry about the differences between CSS selectors and Xpath expressions. An simple ".classname" in CSS is a noisy "contains(concat(' ', normalize-space(@class), ' '), ' first ')" in Xpath.

In XSLT this adds a lot of overhead. But if your XSLT processor supports EXSLT you are able to encapsulate it into a function.

I put this into a file "contains-token.xsl".


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:func="http://exslt.org/functions"
  extension-element-prefixes="func"
>
<func:function name="func:contains-token">
  <xsl:param name="haystack">
  <xsl:param name="needle">
  <xsl:variable name="normalizedHaystack" select="concat(' ', normalize-space($haystack), ' ')"/>
  <xsl:variable name="normalizedNeedle" select="concat(' ', normalize-space($needle), ' ')"/>
  <func:result select="$needle != '' and contains($haystack, $needle)"/>
</func:function>

</xsl:stylesheet>


You need to declare the func namespace and define it as an extension prefix. The function definition is not unlike an normal template definition. You just have to use the "func:function" and "func:result" elements. Function always need to be inside an namespace. If in doubt, just use the "func" namespace itself.

In this case I normalize both parameters first and just validate if the haystack contains the needle.

Now it is possible to import the function and use it.


...
<xsl:import href="../functions/contains-token.xsl"/>

<xsl:template match="/">
  ...
  <xsl:value-of select=".//div[func:contains-token(@class, 'classname')]"/>
  ...
</xsl:template>
...


It is still longer then the CSS version but it is a lot more readable.


2009-02-05

Multi Language XSLT: Language Texts

Currently I am refactoring the default templates for the upcoming papaya CMS 5 release. I will show you some of the concepts in this blog. As you probably know papaya CMS uses XSLT for its templates, which is imho a perfect choice for web applications.

You get a strict split between application logic and layout. But XSLT can do more. How about translating layout texts, like the caption of a more link, format numbers and dates? Sounds nice, doesn't it?

In the first step you need to separate the layouts texts from the xslt and create language files for easier management.

The template for this is quite small:

<xsl:template name="language-text">
  <xsl:param name="text"></xsl:param>
  <xsl:choose>
    <xsl:when
      test="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]">
      <xsl:value-of
        select="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$text"/>
    <xsl:otherwise>    
  </xsl:choose>
</xsl:template>

This will check for an element text in the variable $LANGUAGE_TEXTS_CURRENT with an attribute ident that has the same value like the parameter $text and output it's content.

To fill up the variable, create a xml file with your texts.

<texts>
  <text ident="SAMPLE">Sample text</text>
</texts>

At the top of the XSL file define a global parameter and load a xml file into it. The Xpath function document() loads XML data from an URI. By default the URI is relative to the current xsl file.

<xsl:param name="LANGUAGE_TEXTS_CURRENT"
  select="document('./de-DE.xml')/texts"/>

Of course this whould be only a single fixed language file. So you have to use a variable for the file name. Xpath concat() supports a dynamic count of parameters - no need for nesting.

<xsl:param name="LANGUAGE_TEXTS_CURRENT"
  select="document(concat('./', $PAGE_LANGUAGE, '.xml'))/texts"/>

Now you can call the template to get the language specific text.

<xsl:call-template name="language-text">
  <xsl:with-param name="text">SAMPLE</text>
</xsl:call-template>

This is still a little noisy, but in standard XSLT you can not help it. However if your processor supports the EXSLT extension you can. With EXSLT you can convert a template into a function. The result whould look like this:

<xsl:value-of select="language:text('SAMPLE')" />

Less source and easier to read. You could use it in an output tag, too.

<img src="sample.png" alt="{language:text('SAMPLE')}" />

The PHP 5 ext/xsl using the libxslt library supports EXSLT. To convert the template to a function you change the declaration from "xsl:template" to "func:function" after you did import the EXSLT function namespace:

<func:function name="language:text">
  <xsl:param name="text"/>
  <func:result>
    <xsl:choose>
      <xsl:when
        test="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]">
        <xsl:value-of
          select="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      <xsl:otherwise>    
    </xsl:choose>
  </func:result>
</func:function>

Here's still one little problem, if you haven't translated the language xml file to the current language or you missed a phrase the result is the text identifier. It whould be nice to fall back to a default language. So declare an additional parameter and add a condition to the template.

<xsl:param name="LANGUAGE_TEXTS_FALLBACK"
  select="document('./en-US.xml')/texts"/>
<func:function name="language:text">
  <xsl:param name="text"/>
  <func:result>
    <xsl:choose>
      <xsl:when
        test="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]">
        <xsl:value-of
          select="$LANGUAGE_TEXTS_CURRENT/text[@ident = $text]"/>
      </xsl:when>
      <xsl:when
        test="$LANGUAGE_TEXTS_FALLBACK/text[@ident = $text]">
        <xsl:value-of
          select="$LANGUAGE_TEXTS_FALLBACK/text[@ident = $text]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      <xsl:otherwise>    
    </xsl:choose>
  </func:result>
</func:function>

You can extend this idea and have default and project specific language files.

Have fun experimenting.

2007-08-14

FrOSCon Talk

I will talk about generating PDF with XSLT and FPDF on the FrOSCon in Sankt Augustin next week. It is the first talk of the day in the PHP Room.

2007-08-09

Problem with libxslt

If you use the current PHP 5.2 with libxslt, libxml will add CDATA sections to your XHTML script tags. The W3C suggests the use of a CDATA section (less escaping for special chars needed), but the most browsers will not support this. Firefox supports it, if you send the mimetype application/xhtml+xml. To avoid the problem make your script section a html comment. Here a sample:
<xsl:template match="script">
<script type="{@type}"><xsl:comment>
  <xsl:copy-of select="text()"/>
//</xsl:comment></script>
</xsl:template>
This is a generic match for all script elements in the xml tree. But will take care of xslt generated script elements, too.
<script type="text/javascript"><xsl:comment>
myLittleJSCall();
//</xsl:comment></script>
Here is a bug reported for libxml. But it is closed. :-(

2007-07-31

International PHP Conference

At the International PHP Conference in Frankfurt (Main), Germany I will talk about generating PDF with XSLT and FPDF.

It is not a talk about papaya CMS but the concept and source code of the PDF Output Filter.

Views in papaya CMS

Unlike other cms a view in papaya CMS is not just a template. It is the full configuration for the output.

You create several output filters like html, rss, pdf or print. Each of them can use a different filter module like the xslt filter module or the pdf filter module. The name of the output filter is later used as an extension in the filename of the page urls.

After you defined all the output filters for your project you create a view for a page or box module and link them to the output filters. Each of the links can hold a configuration depending on the selected filter module. For the two existing module you can select a xsl file (the template) and a switch for fullpage mode.

The setting of the fullpage switch is given to the page module, so it can split large content into several outputs in one mode (html) and return a single output in another mode (pdf, print). This needs to be implemented in the page module or it will have no effect at all.

Take a look at the following little graphic:


Editing a page or a box you only need the select a view, because the views knows of its page/box module.
x