1

Problem: I have a given JSON output and need to transform this into a xml with a given format to use it for another input.

The JSON-document: report.json

 {
  "Diagnostic Cycle" : "2019-02-13T08:19:44ZZ",
    "01 Tester" : {
      "01 Name"              : "IPTester",
      "02 Operating System"  : "Linux",
      "03 Node Name"         : "tester15",
      "04 Release Level"     : "5.4.7",
      "06 Machine"           : "i686",
      "07 Domain Name"       : "(none)" 
    } 
,
    "02 Device Name" : 
{

      "SampleECU":
      {
      "01 Diagnostic"        : "OK",
      "02 CAN Id"            : "(none)",
      "02 DoIP Id"           : "00FFh 124Ah 85B1h",
      "03 VIN original"      : "BZ7282399843",
      "04 VIN current"       : "ERROR 11",
      "05 HW Part No"        : "887895414",
      "06 DTC Status 01"     : 1,
      "10 Hardware Year"     : 2020,
      "11 Hardware Week"     : 08,
      "12 Hardware Patch"    : 0,
      "20 Software Year"     : 2020,
      "21 Software Week"     : 08,
      "22 Software Patch"    : 0,
      "30 Bootware"          : "ERROR 11"
    }
}

How the XML-output should look:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="./My_Stylesheet.xsl"?>
<VehicleReport xsi:schemaLocation="http://www.w3.org/xsd/vdx30 VDX.3.2.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VDXVersion="3.2.1" xmlns="http://www.w3.org/xsd/vdx30">
    <ServiceTool>
        <Name>IPTester</Name>
        <Version>5.4.7</Version>
        <UserID>tester15</UserID>
        <ExecutionTime>2019-02-13T08:19:44ZZ</ExecutionTime>
    </ServiceTool>
    <VehicleInformation>
        <IdentificationNumberValue>BZ7282399843</IdentificationNumberValue>
    </VehicleInformation>
    <ComponentList>
        <Component>
            <ECUShortName>SampleECU</ECUShortName>
            <DiagnosticInfo>
                <DiagnosticInfoValue>1</DiagnosticInfoValue>
            </DiagnosticInfo>
            <SWHWInformation>
                <Software>
                    <Version>
                        <VersionValue>20/08/00</VersionValue>
                    </Version>
                </Software>
                <Hardware>
                    <PartNumber>
                        <PartNumberValue>887895414</PartNumberValue>
                    </PartNumber>
                    <Version>
                        <VersionValue>20/08/00</VersionValue>
                    </Version>
                </Hardware>
            </SWHWInformation>
        </Component>
      </ComponentList>
</VehicleReport>

I heard I should use the Saxon XSLT Processor for a XSLT-transformation, but I dont know how (no XSLT experience).

The two ways I can think of:

  1. First use json-to-xml() then transform the xml with XSLT
  2. Populate the XML directly with JSON-values (preferred because simple)

But I dont know how to do both - a tutorial for XSLT would be appreciated.

  • 1
    As for your "JSON", I don't think a value like `08` passes JSON validation at https://jsonlint.com/ for instance or when you would feed it to the `json-doc` function in XSLT 3 so that input is not going to work. The use of `json-to-xml` is shown in the XSLT 3 spec and the XPath 3.1 function spec as well as here in various examples. If you don't know XSLT at all, then try https://cranesoftwrights.github.io/books/ptux/index.htm. – Martin Honnen Feb 13 '20 at 09:05
  • Thank you. The JSON is valid from jsonlint.com. – imperialcode Feb 13 '20 at 12:34
  • when I test the sample you have shown in the question at jsonlint.com it gives errors: "Error: Parse error on line 22: ..."11 Hardware Week": 08, "12 Hardware Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '[', got 'undefined'" – Martin Honnen Feb 13 '20 at 12:38

2 Answers2

1

Both approaches are possible. Martin has given you an indication of the first approach (use json-doc() or parse-json() to convert the JSON to maps and arrays, then populate the XML document by selecting into those maps and arrays. The other approach is to convert to "generic" XML and then transform the generic XML using template rules.

Transformations (whether XML-to-XML or JSON-to-XML) are generally either input-driven or output-driven. In an example like yours where the structure of the output bears little relation to the structure of the input, you need to be output-driven: that is, the stylesheet will take the form "Generate XXX by fetching /a/b/c from the input, then generate YYY by fetching /p/q/r from the input". That's a strong indicator to use the style of solution Martin has put forward.

The other style, where you convert JSON to generic XML and then apply templates to the generic XML to produce your specific XML, is probably more suited to "input-driven" transformations, which take the typical form "look to see what comes next in the input, and depending on what you find, generate X or Y or Z in the output".

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Then I think it should be "Input-driven" because it is possible to have several of these "SampleECU" blocks, and you could have different ones. With the example of Martin, I can't query for a specific key like "SampleECU". Thanks for your help! – imperialcode Feb 13 '20 at 12:37
0

To give you one example on how to operate with XPath 3.1 on JSON values which are represented in the XPath 3.1 data model as maps and arrays which are functions you can call with a property name (for maps/objects) or the index (for arrays) as the argument here is a simple XQuery 3.1 code that directly populates your XML (I have only put in the XPath expressions for a few values, you should be able to fill in the rest):

<VehicleReport xsi:schemaLocation="http://www.w3.org/xsd/vdx30 VDX.3.2.1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" VDXVersion="3.2.1" xmlns="http://www.w3.org/xsd/vdx30">
    <ServiceTool>
        <Name>{.("01 Tester")("01 Name")}</Name>
        <Version>{.("01 Tester")("04 Release Level")}</Version>
        <UserID>{.("01 Tester")("03 Node Name")}</UserID>
        <ExecutionTime>{.("Diagnostic Cycle")}</ExecutionTime>
    </ServiceTool>
    <VehicleInformation>
        <IdentificationNumberValue>{.("02 Device Name")("SampleECU")("03 VIN original")}</IdentificationNumberValue>
    </VehicleInformation>
    <ComponentList>
        <Component>
            <ECUShortName>SampleECU</ECUShortName>
            <DiagnosticInfo>
                <DiagnosticInfoValue>1</DiagnosticInfoValue>
            </DiagnosticInfo>
            <SWHWInformation>
                <Software>
                    <Version>
                        <VersionValue>20/08/00</VersionValue>
                    </Version>
                </Software>
                <Hardware>
                    <PartNumber>
                        <PartNumberValue>887895414</PartNumberValue>
                    </PartNumber>
                    <Version>
                        <VersionValue>20/08/00</VersionValue>
                    </Version>
                </Hardware>
            </SWHWInformation>
        </Component>
      </ComponentList>
</VehicleReport>

This assumes the JSON is valid JSON like

{
  "Diagnostic Cycle" : "2019-02-13T08:19:44ZZ",
    "01 Tester" : {
      "01 Name"              : "IPTester",
      "02 Operating System"  : "Linux",
      "03 Node Name"         : "tester15",
      "04 Release Level"     : "5.4.7",
      "06 Machine"           : "i686",
      "07 Domain Name"       : "(none)" 
    } 
,
    "02 Device Name" : 
{

      "SampleECU":
      {
      "01 Diagnostic"        : "OK",
      "02 CAN Id"            : "(none)",
      "02 DoIP Id"           : "00FFh 124Ah 85B1h",
      "03 VIN original"      : "BZ7282399843",
      "04 VIN current"       : "ERROR 11",
      "05 HW Part No"        : "887895414",
      "06 DTC Status 01"     : 1,
      "10 Hardware Year"     : 2020,
      "11 Hardware Week"     : 8,
      "12 Hardware Patch"    : 0,
      "20 Software Year"     : 2020,
      "21 Software Week"     : 8,
      "22 Software Patch"    : 0,
      "30 Bootware"          : "ERROR 11"
    }
}
}

and is provided in XQuery as the context item e.g. with declare context item := json-doc('file.json');.

Saxon 9 supports XQuery 3.1 as well as XSLT 3, therefore, as long as you have a simple XML document you want to populate, it seems a bit easier to use XQuery than XSLT as there is no boilerplate for templates.

Online example is at https://xqueryfiddle.liberty-development.net/bFDbxkV.

So the main issues to learn is to understand that a JSON object { "foo" : 1, "bar": "value" } is represented as a map which also is a function you can call with a property name (e.g. .("foo") or .("bar")) to select a property. For your JSON property names containing spaces this seems the way to go, for the simple example I have shown the shortfix ?foo or ?bar would suffice.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thank you! Is there also the possibility to mix XQuery with XSLT? – imperialcode Feb 14 '20 at 07:13
  • @imperialcode, in XQuery 3.1 as specified you can apply XSLT 3 by using the `fn:transform` function from the XPath 3.1 spec, to "call" XSLT from XQuery. Saxon (9.8 and higher) supports that, other XQuery processors like BaseX or eXist I think only have their pre-exsiting, proprietary functions to allow the use of XSLT from XQuery. The other way, using XQuery from XSLT, also has its way in the spec https://www.w3.org/TR/xpath-functions/#func-load-xquery-module but support for that is only in Saxon PE or EE. – Martin Honnen Feb 14 '20 at 11:18
  • Then there is XProc of course, XProc 3 to deal with XQuery 3.1 and XSLT 3 is not quite ready, at least there are no public implementations, unless the current XMLPrague conference is about to change that. The existing XProc 1 implementations nevertheless might allow the use of XQuery 3 and XSLT 3 in a pipeline, probably as long as XML is the intermediary result between pipelines. – Martin Honnen Feb 14 '20 at 11:21