0

I'm trying to convert JSON to a specific XML format, all in one XSLT. (It doesn't have to be in one step, but, you know,...)

I can convert the JSON to generic XML from here: How to use XPath/XSLT fn:json-to-xml

Converting the resultant generic XML to the XML I want is then simple.

But I can't work out how to combine the XSLTs so I can do it in one step, do JSON-to-XML and then the XML transformation. I've tried with variables, include, import, but can't get it to work.

I suspect it's straightforward! It needs to be in (just) XSLT.

So, from the question linked to above, I start with JSON (in XML tags)

<root>
    <data>{
        "desc"    : "Distances between several cities, in kilometers.",
        "updated" : "2014-02-04T18:50:45",
        "uptodate": true,
        "author"  : null,
        "cities"  : {
        "Brussels": [
        {"to": "London",    "distance": 322},
        {"to": "Paris",     "distance": 265},
        {"to": "Amsterdam", "distance": 173}
        ],...

and transform to

 <map xmlns="http://www.w3.org/2005/xpath-functions">
   <string key="desc">Distances between several cities, in kilometers.</string>
   <string key="updated">2014-02-04T18:50:45</string>
   <boolean key="uptodate">true</boolean>
   <null key="author"/>
   <map key="cities">
      <array key="Brussels">
         <map>
            <string key="to">London</string>
            <number key="distance">322</number>
         </map>
         <map>
            <string key="to">Paris</string>
            <number key="distance">265</number>
         </map>...

using

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" version="3.0">
    <xsl:output indent="yes"/>
    <xsl:template match="data">
        <xsl:copy-of select="json-to-xml(.)"/>
    </xsl:template>
</xsl:stylesheet>

Now I can apply this stylesheet to the 'intermediate' XML:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.w3.org/2005/xpath-functions">
<xsl:output indent="yes"/>
  <xsl:template match="/">
    <Distances>
      <xsl:for-each select="f:map/f:map/f:array">
        <Start>
          <StartPoint><xsl:value-of select="@key"/></StartPoint>
          <xsl:for-each select="f:map">
            <Distance>
              <xsl:attribute name="end"><xsl:value-of select="f:string"/></xsl:attribute>
              <xsl:attribute name="value"><xsl:value-of select="f:number"/></xsl:attribute>
            </Distance>
          </xsl:for-each>
        </Start>
      </xsl:for-each>
    </Distances>
  </xsl:template>
</xsl:stylesheet>

and get my desired structure:

<?xml version="1.0" encoding="UTF-8"?>
<Distances xmlns:f="http://www.w3.org/2005/xpath-functions">
   <Start>
      <StartPoint>Brussels</StartPoint>
      <Distance end="London" value="322"/>
      <Distance end="Paris" value="265"/>
      <Distance end="Amsterdam" value="173"/>
   </Start>...

So, is it possible to combine the JSON-to-XML and the XML transformation XSLs in one?

Cormac
  • 117
  • 1
  • 10
  • Why does your title say XSLT 2.0 when you're obviously using XSLT 3.0? -- P.S. Please post a [mcve], not snippets of code cut off in the middle. – michael.hor257k Jun 07 '22 at 14:56
  • Oh dear, am I? Which bits are 3.0? I should have said I'm not knowledgeable about XSLT! OK, I was trying to be brief - will expand the code. – Cormac Jun 07 '22 at 15:09
  • 1
    `json-to-xml()` requires an XSLT 3.0 processor. – michael.hor257k Jun 07 '22 at 15:12
  • 1
    Also as a general comment you can always process your data and store the result in a variable, then process the intermediate data contained in your variable. – Sebastien Jun 07 '22 at 15:45

2 Answers2

2

I am guessing you want to do:

XSLT 3.0

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:f="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="f">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/root">
    <Distances>
        <xsl:for-each select="json-to-xml(data)/f:map/f:map/f:array">
            <Start>
                <StartPoint>
                    <xsl:value-of select="@key"/>
                </StartPoint>
                <xsl:for-each select="f:map">
                    <Distance end="{f:string}" value="{f:number}"/>
                </xsl:for-each>
            </Start>
        </xsl:for-each>
    </Distances>
</xsl:template>
  
</xsl:stylesheet>

Untested, because no code suitable for testing was provided.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
1

To do it the way you were proposing, you can do

<xsl:template match="data">
   <xsl:apply-templates select="json-to-xml(.)"/>
</xsl:template>

and then add template rules to transform the generic XML produced by json-to-xml() to your application-specific XML.

But I think the approach suggested by @michael.hor257k is probably better.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164