Search code examples
xmldelphidelphi-7delphi-xe4

Workaround for & and CRLF issue in Delphi XE4 with TXMLTransformProvider component


I know its going to be a repeating question but still asking again as no workaround worked for this problem. Let me explain the problem statement first and then I will also mention what I tried.

XML:

<Order><XMLversion>2</XMLversion><Info>*-first line
*-second line</Info></Order>

CRLF is there after "*-first line" statement.

I created XTR file corresponding to this XML using Delphi XE4 RAD Studio Tools --> XML Mapper utility. Following XTR file I got:

<XmlTransformation Version="1.0"><Transform Direction="ToCds"><SelectEach dest="DATAPACKET\ROWDATA\ROW" from="\Order"><Select dest="@XMLversion" from="\XMLversion"/><Select dest="@Info" from="\Info"/></SelectEach></Transform><XmlSchema RootName="Order"><![CDATA[<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <element xmlns="http://www.w3.org/2001/XMLSchema" name="Order" type="OrderType"/>
  <complexType xmlns="http://www.w3.org/2001/XMLSchema" name="OrderType">
    <sequence>
      <element name="XMLversion" type="XMLversionType"/>
      <element name="Info" type="InfoType"/>
    </sequence>
  </complexType>
  <element xmlns="http://www.w3.org/2001/XMLSchema" name="XMLversion" type="XMLversionType"/>
  <simpleType xmlns="http://www.w3.org/2001/XMLSchema" name="XMLversionType">
    <restriction base="xs:string"/>
  </simpleType>
  <element xmlns="http://www.w3.org/2001/XMLSchema" name="Info" type="InfoType"/>
  <simpleType xmlns="http://www.w3.org/2001/XMLSchema" name="InfoType">
    <restriction base="xs:string"/>
  </simpleType>
</xs:schema>]]></XmlSchema><CdsSkeleton/><XslTransform/><Skeleton><![CDATA[<?xml version="1.0"?><DATAPACKET Version="2.0"><METADATA><FIELDS><FIELD attrname="XMLversion" fieldtype="string" WIDTH="1"/><FIELD attrname="Info" fieldtype="bin.hex" SUBTYPE="Text"/></FIELDS><PARAMS/></METADATA><ROWDATA/><METADATA><FIELDS><FIELD attrname="XMLversion" fieldtype="string" WIDTH="1"/><FIELD attrname="Info" fieldtype="bin.hex" SUBTYPE="Text"/></FIELDS><PARAMS/></METADATA><ROWDATA/></DATAPACKET>
]]></Skeleton></XmlTransformation>

Problem Statement:

In my dfm file, I put TXMLTransformProvider and Clientdataset. In TXMLTransformProvider, I have provided the path of XML and XTR files. ProviderName of TClientdataset is set to TXMLTransformProvider.

Following is my code to read "info" node from the XML.

procedure TfrmAmpersand.Issue;
var
  InfoNode : Variant;
begin
  try
    InfoNode := ClientDataset1.FieldByName('Info').AsVariant;
    ShowMessage(InfoNode);
  except on e:exception do
    begin
      ShowMessage(e.Message);
    end;
  end;
end;

There are two fields in XML: XMLVersion and Info. In Clientdataset, XMLVersion is TStringField while Info is of TMemoField.

ShowMessage(InfoNode), shows the message like this

*-first line&*-second line

While in case of Delphi 7, it works fine and shows message like this:

*-first line
*-second line

Following is my dropbox link where I have uploaded my sample project for this problem:

https://www.dropbox.com/s/foi7o3wf7wlx9lh/AmpersandIssue.zip

Please note, I have put hardcoded path for XML and XTR file in TXMLTransformProvider. So keep this project in D: drive or just change the path in TXMLTransformProvider.

Following is EDN link where I have asked same question but got no reply:

https://forums.embarcadero.com/thread.jspa?messageID=667134&#667134

Workarounds I tried:

  1. Replace & with #13#10 after getting that node value.

    InfoNode := StringReplace(InfoNode, '&', #13#10, [rfReplaceAll]);

But this is not the right solution as info node can actually contain the & in database.

  1. I tried all Remy's solutions which he provided in my last question:

I hardcoded my XML as:

<Order><XMLversion>2</XMLversion><Info>*-first line&#13;&#10;*-second line</Info></Order>

and

<Order><XMLversion>2</XMLversion><Info>*-first line&#xD;&#xA;*-second line</Info></Order>

and also

<Order><XMLversion>2</XMLversion><Info><![CDATA[*-first line
*-second line]]></Info></Order>

Nothing worked. Everytime I am getting the same result which I mentioned above. The ampersand sign.

  1. Ken White's answer is also not working for me which he provided me in last question.

  2. @TLama - You suggested me in my previous question (sorry, but I have deleted that for some reason) that this is bug in TXMLTransformProvider and I should carry with Remy's solution. But now nothing is working. So what next steps should I take to solve this straight forward problem? Have you raised this bug with Embarcadero?

Please suggest me any workaround which can solve this issue?


Solution

  • I need to preface this answer by saying that unlike Remy or Ken, I am absolutely no expert at Unicode, and I'm not even sure that it's involved in your problem.

    However I've looked at the on-disk bytes that your XMLTransformProvider generates and compared it with what gets written to disk when you do a CDS.SaveToFile('Test.Xml', dfXML).

    It's evident that whereas a CDS saves a newline embedded in a DB memo field as:

    &#013;&#010;

    what the XMLTransformProvider produces for a similar newline is:

    &#xA;

    So, my simple-minded suggested work-around is to wire in to your code where you open the XML datafile something like this, to load the CDS with XML in its own format:

    const 
      TransformNewLine = '&#xA;';
      CDSNewLine = '&#013;&#010;';
    
    function FixCdsXml(Input : String) : String;
    begin
      Result:= StringReplace(Input, TransformNewLine, CDSNewLine, [rfReplaceAll]);
    end;
    
    procedure TfrmAmpersand.TestFix;
    var
      S : String;
      SS : TStringStream;
    begin
      S := XMLTransformProvider1.TransformRead.Data;
      S := FixCdsXml(S);
      SS := TStringStream.Create(S);
      try
        SS.Position := 0;
        ClientDataSet1.LoadFromStream(SS);
      finally
        SS.Free;
      end;
    end;
    

    There don't seem to be any convenient events in TXMLTransformerProvider or its TXMLTransform sub-components to use as an opportunity to call TestFix automatically. However, it does seem to work ok if you call it in the CDS's AfterOpen event, provided you add a "FixingXML" boolean to your form to prevent re-entrancy and code the event like this:

    procedure TfrmAmpersand.ClientDataSet1AfterOpen(DataSet: TDataSet);
    begin
      if FixingXML then exit;
      FixingXML := True;
      try
        TestFix;
      finally
        FixingXML := False;
      end;
    end;
    

    I'm using XE6, btw.