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, "'", "\'")'/> </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, "'", "g", "\'")'/> </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>