2

I want to experiment (in Delphi code) with the XSLT 3.0 and its json-to-xml() function:

In XSLT 3.0, an inbound document can be in JSON, rather than XML. The processor can take that document, use the json-to-xml() function to convert it into a specific known XML format, process that through the templates, then convert the resulting output back into JSON (or can convert it into HTML 5 among other formats

But I'm stuck in two places:

  • How do I use a JSON string as the source for the transform? Trying to loading it into a TXMLDocument gives me (of course?) 'malformed' errors

  • How would I then apply the "json-to-xml() function". All the examples I find about using XSLT transforms in Delphi use the TransformNode function, as the below code. Things like lDoc.Node.json-to-xml do not compile.

.

var
  lDoc, lXSL, lRes: IXMLDocument;
  lUTF8Str        : UTF8String;
begin
  lDoc := LoadXMLData(AXMLString);
  lXSL := LoadXMLData(cRemoveNSTransform);
  lRes := NewXMLDocument;
  lDoc.Node.TransformNode(lXSL.Node,lRes);  // Param types IXMLNode, IXMLDocument
  lRes.SaveToXML(lUTF8Str);     

Can anyone point me in the right direction?

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
  • 1
    I doubt that there is any well-developed XSLT 3 API or binding for Delphi. Main protagonists for XSLT 3 are Saxon 9.8 and later (existing in a Java version, a .NET framework version and a C/C++ version) and Altova Raptor. I guess IXMLDocument suggests you are on Windows using Microsoft's MSXML which is an XSLT 1 processor. Raptor has a COM API, guess Delphi on Windows can connect to that. Not sure how easy it is bridging Delphi to Java or .NET so that you could use Saxon HE which is open-source. – Martin Honnen Apr 19 '19 at 15:20

1 Answers1

1

I'm going to write a 'guide' to my own question that does not use XSLT but instead uses the IP*Works! Delphi components that we have a subscription for.

This may at least give others an available option, or a rough idea how to 'roll your own'.

We use the IP*Works! TipwJSON and TipwXML components. The trick is to intercept the parsing of the JSON component and then write the detected data to the XML component. This is code from a test app showing how we did it (I have left logging code in):

TJSONTOXML = class(TIpwJSON)
            private
               FXML         : TipwXML;
               FLogLevel    : Integer;
               procedure ShowLogLine(AMsg: String);
               procedure InterceptJSONStartElement(Sender: TObject; const Element: string);
               procedure InterceptJSONEndElement(Sender: TObject; const Element: string);
               procedure InterceptCharacters(Sender: TObject; const Text: string);
               function GetXML: String;
            public
               property XML: String read GetXML;
               constructor Create(AOwner: TForm; ALogLevel: Integer); overload;  // For now testing on a Form
            end;

constructor TJSONTOXML.Create(AOwner: TForm; ALogLevel: Integer);
begin
   inherited Create(AOwner);
   FLogLevel := ALogLevel;
   Self.BuildDOM  := false;
   Self.OnStartElement := InterceptJSONStartElement;
   Self.OnEndElement   := InterceptJSONEndElement;
   Self.OnCharacters   := InterceptCharacters;
   FXML := TipwXML.Create(nil);
end;

procedure TJSONTOXML.InterceptJSONEndElement(Sender: TObject; const Element: string);
begin
   if Element = '' then  // End of array
   begin
      if FLogLevel > 2 then ShowLogLine('JSON parse EndElement - Array');
      FXML.EndElement;
   end
   else
   begin
      if FLogLevel > 2 then ShowLogLine('JSON parse EndElement - Element: ' + Element);
      FXML.EndElement;
   end;
end;

procedure TJSONTOXML.InterceptJSONStartElement(Sender: TObject; const Element: string);
begin
   if Element = '' then  // Start of array
   begin
      if FLogLevel > 2 then ShowLogLine('JSON parse StartElement - Array');
      FXML.StartElement('ARRAY','');
   end
   else
   begin
      if FLogLevel > 2 then ShowLogLine('JSON parse StartElement - Element: ' + Element);
      FXML.StartElement(Uppercase(Element),'');
   end;
end;

procedure TJSONTOXML.ShowLogLine(AMsg: String);
// Use WM_COPYDATA to send log info to form
var CopyDataStruct: TCopyDataStruct;
begin
  CopyDataStruct.dwData := 0;
  CopyDataStruct.cbData := 2 + 2 * Length(AMsg);
  CopyDataStruct.lpData := PChar(AMsg);
  SendMessage((Owner as TForm).Handle, WM_COPYDATA, (Owner as TForm).Handle, lParam(@CopyDataStruct));
end;

function TJSONTOXML.GetXML: String;
begin
   FXML.EndElement;
   Result := FXML.OutputData;
end;

procedure TJSONTOXML.InterceptCharacters(Sender: TObject; const Text: string);
var lText: String;
begin
   // Always surrounded by quotes, remove:
   lText := StripQuotes(Text);
   if FLogLevel > 2 then ShowLogLine('JSON parse characters: ' + lText);
   FXML.PutString(lText);
end;

With this you can

lJSONToXML := TJSONTOXML.Create(Self,FDataLogLvl);
// Get your JSON data from somewhere, e.g. a HTTP component. Then:
lJSONToXML.Inputdata := lData;
lJSONToXML.Parse;         // The Parse method initiates the parsing that was postponed by setting BuildDom := false
// The XML is now in the OutputData property of the TipwXML and can e.g. be retrieved by our:
lOutputData := lJSONToXML.XML;

Note that:

  • There is no namespace information in the XML
  • The JSON arrays when converted to XML are converted to nodes named ARRAY
  • All data is kept in memory
Jan Doggen
  • 8,799
  • 13
  • 70
  • 144