EXSLT/XML/JSON complications

Bruce D'Arcus commented on my entry "Creating JSON from XML using XSLT 1.0 + EXSLT", and following up on his reply put me on a bit of a journey. Enough so that the twists merit an entry of their own.

Bruce pointed out that libxslt2 does not support the str:replace function. This recently came up in the EXSLT mailing list, but I'd forgotten. I went through this thread. Using Jim's suggestion for listing libxslt2 supported extensions (we should implement something like that in 4XSLT) I discovered that it doesn't support regex:replace either. This is a serious pain, and I hope the libxslt guys can be persuaded to add implementations of these two very useful functions (and others I noticed missing).

That same thread led me to a workaround, though. EXSLT provides a bootstrap implementation of str:replace, as it does for many functions. Since libxslt2 does support the EXSLT functions module, it's pretty easy to alter the EXSLT bootstrap implementation to take advantage of this, and I did so, creating an updated replace.xsl for processors that support the Functions module and exsl:node-set. Therefore a version of the JSON converter that does work in libxslt2 (I checked) is:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:func="http://exslt.org/functions"
    xmlns:str="http://exslt.org/strings"
    xmlns:js="http://muttmansion.com"
    extension-element-prefixes="func">

  <xsl:import href="http://copia.ogbuji.net/files/code/replace.xsl"/>
  <xsl:output method="text"/>

  <func:function name="js:escape">
    <xsl:param name="text"/>
    <func:result select='str:replace($text, "&apos;", "\&apos;")'/>
  </func:function>

  <xsl:template match="/">
var g_books = [
<xsl:apply-templates/>
];
  </xsl:template>

  <xsl:template match="book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)"/>',
first: '<xsl:value-of select="js:escape(author/first)"/>',
last: '<xsl:value-of select="js:escape(author/last)"/>',
publisher: '<xsl:value-of select="js:escape(publisher)"/>'
}
  </xsl:template>

</xsl:transform>

One more thing I wanted to mention is that there was actually a bug in 4XSLT's str:replace implementation. I missed that fact because I had actually tested a variation of the posted code that uses regex:replace. Just before I posted the entry I decided that the Regex module was overkill since the String module version would do the trick just fine. I just neglected to test that final version. I have since fixed the bug in 4Suite CVS, and you can now use either str:replace or regex:replace just fine. Just for completeness, the following is a version of the code using the latter function:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:func="http://exslt.org/functions"
    xmlns:regex="http://exslt.org/regular-expressions"
    xmlns:js="http://muttmansion.com"
    extension-element-prefixes="func">

  <xsl:output method="text"/>

  <func:function name="js:escape">
    <xsl:param name="text"/>
    <func:result select='regex:replace($text, "&apos;", "g", "\&apos;")'/>
  </func:function>

  <xsl:template match="/">
var g_books = [
<xsl:apply-templates/>
];
  </xsl:template>

  <xsl:template match="book">
<xsl:if test="position() > 1">,</xsl:if> {
id: <xsl:value-of select="@id" />,
name: '<xsl:value-of select="js:escape(title)"/>',
first: '<xsl:value-of select="js:escape(author/first)"/>',
last: '<xsl:value-of select="js:escape(author/last)"/>',
publisher: '<xsl:value-of select="js:escape(publisher)"/>'
}
  </xsl:template>

</xsl:transform>

[Uche Ogbuji]

via Copia