5

I've got a .NET library that uses an XSLT file for transforming beer xml files into json for a web app.

The XSLT file looks a lot like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes" />
<xsl:template match="RECIPES">
{
    {
      "description": {
      "name": "<xsl:value-of select="NAME"/>",
      "style": "<xsl:value-of select="STYLE/NAME"/>",
      ...

And I'm converting using this piece of code in c#:

using(var writer = new StringWriter()){
     _xsltCompiler.Transform(_document, null, writer);
     json = writer.ToString();
}

Now, the problem is that curly braces and whitespace is missing from the output. And it used to work. From the source control history I can see no aparent changes lately. Any suggestions on how to fix this?

Anders Nygaard
  • 5,433
  • 2
  • 21
  • 30
  • 5
    XSLT is generally the wrong tool to produce JSON. It cannot reliably do this, it has no notion of how JSON works. This is the wrong approach, you should use a proper JSON serializer. .NET has quite a few, pick one. Consider this approach, it will save you more than one headache: http://stackoverflow.com/questions/12037085/convert-xml-to-json-using-c-linq – Tomalak Mar 25 '14 at 20:07
  • 1
    @Tomalak not sure why you think XSLT is a poor choice for this? OP wants to transform XML into a string, albeit a formatted string. Why is XSLT not a good choice for this? –  Mar 26 '14 at 01:17
  • Can you give an example output of what you are seeing after the transformation? –  Mar 26 '14 at 01:18
  • 4
    @LegoStormtroopr, if nothing else (and there may be nothing else), proper JSON-escaping of strings would be very inconvenient in XSLT, as it requires a single-to-multi-character representation over a large map of values (including most non-ASCII unicode points). While I tend to give XSLT the benefit of the doubt whenever humanly possible, I think Tomolak is right. – harpo Mar 26 '14 at 04:41
  • @harpo Good point, I hadn't thought of string escaping. My questions are answered and I stand corrected! Thanks :) –  Mar 26 '14 at 05:05
  • I think XSLT is a clean enough way of transforming XML to JSON given a format like BeerXml that never changes (http://www.beerxml.com/). After spending a few hours looking in all the wrong places, it turned out that the XSLT fell through because it's looking for an element called RECIPES in plural. In my context, that doesn't exists. The output from the .NET XslCompiledTransform class just contains all the inner text from all the nodes in the xml. Strange behviour indeed. – Anders Nygaard Mar 26 '14 at 07:24
  • 2
    @AndersNygaard That's [default behavior for XSLT](http://www.dpawson.co.uk/xsl/sect2/defaultrule.html). Listen to my advice. XSLT is not the right tool for this. It cannot do proper character escaping and will easily produce syntactically wrong JSON without a way for you to notice - and it's pretty hard to fix that. Use LINQ to XML to produce the object graph you need right in .NET and serialize that graph directly to JSON. This will get the job done safely, correctly, faster and very probably in less lines of code at that. – Tomalak Mar 26 '14 at 07:59
  • 1
    @LegoStormtroopr The OP doesn't want to transform XML to a string. He wants to transform it to JSON, which is *not a string*. JSON is an object graph in serialized form, with specialized and pretty complex syntax rules, just like XML itself. JSON output is not built into XSLT therefore it cannot reliably obey the formal semantics for JSON. At this point it becomes a game of hit-and-miss, an XSL stylesheet that produces correct JSON will be pretty complex (at least a lot more complex than the OP's attempt). Plus: There already are JSON serializers for .NET, why build yet another, inferior one? – Tomalak Mar 26 '14 at 08:07
  • 1
    @Tomalak I'll take your advice with converting XML to an object structure and then to JSON into consideration. You see, we did try a few frameworks for converting from XML to JSON directly and that wasn't the way to go. – Anders Nygaard Mar 26 '14 at 08:40
  • Good decision. With LINQ it should be pretty straightforward in the end. – Tomalak Mar 26 '14 at 10:50

1 Answers1

1

I would recommend transforming it initially to xml and storing it into a variable then applying a standard/general template to transform that to JSON. I would this this slightly different using XSLT 2.0 or 3.0 and implement xml-to-json().

This is my solution to the example above:

<xsl:stylesheet version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="text" omit-xml-declaration="yes" />
      <xsl:template match="RECIPES">
            <xsl:variable name="xml">
                  <description>
                        <xsl:element name="name">
                              <xsl:value-of select="NAME"/>
                        </xsl:element>
                        <xsl:element name="style">
                              <xsl:value-of select="STYLE/NAME"/>
                        </xsl:element>
                  </description>
            </xsl:variable>

            {<xsl:apply-templates select="$xml" mode="xml-to-json"/>}

      </xsl:template>



      <!-- Object or Element Property-->
      <xsl:template match="*" mode="xml-to-json">
            "<xsl:value-of select="name()"/>" :
            <xsl:call-template name="Properties">
                  <xsl:with-param name="parent" select="'Yes'"></xsl:with-param>
            </xsl:call-template>
      </xsl:template>

      <!-- Array Element -->
      <xsl:template match="*" mode="ArrayElement">
            <xsl:call-template name="Properties"/>
      </xsl:template>

      <!-- Object Properties -->
      <xsl:template name="Properties">
            <xsl:param name="parent"></xsl:param>
            <xsl:variable name="childName" select="name(*[1])"/>
            <xsl:choose>
                  <xsl:when test="not(*|@*)">
                        <xsl:choose>
                              <xsl:when test="$parent='Yes'">
                                    <xsl:text>&quot;</xsl:text>
                                    <xsl:value-of select="."/>
                                    <xsl:text>&quot;</xsl:text>
                              </xsl:when>
                              <xsl:otherwise>"<xsl:value-of select="name()"/>":"<xsl:value-of select="."/>"</xsl:otherwise>
                        </xsl:choose>
                  </xsl:when>
                  <xsl:when test="count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
                  <xsl:otherwise>{<xsl:apply-templates select="@*" mode="xml-to-json"/><xsl:apply-templates select="*" mode="xml-to-json"/>}</xsl:otherwise>
            </xsl:choose>
            <xsl:if test="following-sibling::*">,</xsl:if>
      </xsl:template>
</xsl:stylesheet>
codedawi
  • 314
  • 2
  • 11
  • This answer brinds atention to a bad question with bad comments. Please consider to contribute to https://stackoverflow.com/questions/13007280/how-to-convert-json-to-xml-using-xslt – Alejandro Mar 11 '19 at 19:25