2
votes

I have consumed my prepared XSD with the XML Binding Wizard.

<?xml version="1.0" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
 <xs:element name="examples" type="examples"/>
 <xs:complexType name="examples">
  <xs:sequence>
   <xs:element name="example" type="example" minOccurs="1" maxOccurs="unbounded"/>
  </xs:sequence>
 </xs:complexType>
 <xs:complexType name="example">
  <xs:sequence>
   <xs:element name="doublevalue" type="xs:double"/>
   <xs:element name="decimalvalue" type="xs:decimal"/>
  </xs:sequence>
 </xs:complexType>
</xs:schema>

First of all, why are decimal and double treated differently by default? XML Data Binding Wizard Double enter image description here

XML simple type Double gets translated to Delphi native type Double and XML simple type Decimal gets translated to Delphi native type UnicodeString by default.

I have the same issue with both data types: locale conflicts

I'm German. That means the DecimalSeparator is a , and the ThousandSeparator is a . by (Windows) default.

When I read my example XML as follows, then the 0.08 double becomes an 8 integer.

XML

<?xml version="1.0" encoding="UTF-8"?>
<examples>
  <example>
    <doublevalue>0.08</doublevalue>
    <decimalvalue>1001.015</decimalvalue>
  </example>
</examples>

Code

var
  xmldoc: IXMLDocument;
  examples: IXMLExamples;
  i: Integer;
  d: Double;
begin
  xmldoc := TXMLDocument.Create(nil) as IXMLDocument;
  try
    xmldoc.LoadFromFile('C:\temp\example.xml');
    examples := Getexamples(xmldoc); // Getexamples() is part of the unit generated by the Binding Wizard 
    for i := 0 to examples.Count - 1 do
      d := examples[i].Doublevalue;
  finally
    examples := nil;
    xmldoc := nil;
  end;
end;

Snapshot Runtime Debug Snapshot

Right now I change the XML data type Double to a Delphi native type UnicodeString and work with a method like this:

function XMLStringToDouble(const str: string): double;
var
  fs: TFormatSettings;
begin
  fs := FormatSettings;
  fs.DecimalSeparator := '.';
  fs.ThousandSeparator := #0;
  result := StrToFloat(str, fs);
end;

There is another issue when creating an XML

Code

var
  xmldoc: TXMLDocument;
  examples: IXMLExamples;
  example: IXMLExample;
begin
  xmldoc := TXMLDocument.Create(nil);
  try
    xmldoc.DOMVendor := MSXML_DOM;
    xmldoc.Options := [doNodeAutoCreate, doNodeAutoIndent, doAttrNull, doAutoPrefix, doNamespaceDecl];
    xmldoc.Active := true;
    xmldoc.Version := '1.0';
    xmldoc.Encoding := 'UTF-8';
    examples := xmldoc.GetDocBinding('examples', TXMLExamples, '') as IXMLExamples;
    example := examples.Add;
    example.Doublevalue := 0.08;
    example.Decimalvalue := '1001.015';
    xmldoc.SaveToFile('C:\temp\example.xml');
  finally
    xmldoc.Free
  end;
end;

I end up getting an XML with a , as DecimalSeparator.

<?xml version="1.0" encoding="UTF-8"?>
<examples>
  <example>
    <doublevalue>0,08</doublevalue>
    <decimalvalue>1001.015</decimalvalue>
  </example>
</examples>

1. Is there a way to treat Double in a simpler/proper fashion?

2. Can I somehow pass a TFormatSettings to XMLDocument or solve it in a completely different way?

3. How do you do it?

1
It seems changing the global FormatSettings variable isn't a good choice as well. 1. deprecated 2. not thread-safe stackoverflow.com/questions/17442325/…complete_stranger
cannot reproduce, which delphi version are you using. Tokyo forces a '.' as decimal separator for xml documents.whosrdaddy
Embarcadero® RAD Studio 10 Seattle Version 23.0.21418.4207 Delphi 10 Seattle and C++Builder 10 Seattle Subscription Update 1complete_stranger
check the source code for XmlStrToFloatExt in unit xml.xmlutil...whosrdaddy
C:\Program Files (x86)\Embarcadero\Studio\17.0\source\xml\Xml.xmlutil.pas does NOT contain XmlStrToFloatExtcomplete_stranger

1 Answers

0
votes

I can not reproduce your problem (using Delphi Tokyo 10.2.3).

I made a small MCVE to prove this, using below code with german regional settings will still read/write a '.' as decimal separator:

program SO52863558;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Windows,
  ActiveX,
  XmlDoc,
  XmlIntf,
  xmltest in 'xmltest.pas';

var
  Examples: IXMLExamples;
  Example: IXMLExample;
  i: Integer;
  d: Double;

begin
 try
  CoInitialize(nil);
  try
   Examples := Loadexamples('test.xml');
   for i := 0 to examples.Count - 1 do
    begin
     Writeln(examples[i].Doublevalue:0:2);
     Writeln(examples[i].Decimalvalue);
    end;
   Examples := nil;
   Examples := NewExamples;
   Example := Examples.Add;
   Example.Doublevalue := 0.012;
   Example.Decimalvalue := '12.51515';
   Examples.OwnerDocument.SaveToFile('test1.xml');
  finally
   Example := nil;
   Examples := nil;
   CoUninitialize;
  end;
 except
  on E: Exception do
   Writeln(E.Message);
 end;
 Readln;
end.

Unit generated from your XSD file by the binding:

unit xmltest;

interface

uses Xml.xmldom, Xml.XMLDoc, Xml.XMLIntf;

type

{ Forward Decls }

  IXMLExamples = interface;
  IXMLExample = interface;

{ IXMLExamples }

  IXMLExamples = interface(IXMLNodeCollection)
    ['{E86B6745-A58E-439F-A73F-B92F446B146B}']
    { Property Accessors }
    function Get_Example(Index: Integer): IXMLExample;
    { Methods & Properties }
    function Add: IXMLExample;
    function Insert(const Index: Integer): IXMLExample;
    property Example[Index: Integer]: IXMLExample read Get_Example; default;
  end;

{ IXMLExample }

  IXMLExample = interface(IXMLNode)
    ['{41DB1169-948C-4B28-BFB0-F63ACEAC14DB}']
    { Property Accessors }
    function Get_Doublevalue: Double;
    function Get_Decimalvalue: UnicodeString;
    procedure Set_Doublevalue(Value: Double);
    procedure Set_Decimalvalue(Value: UnicodeString);
    { Methods & Properties }
    property Doublevalue: Double read Get_Doublevalue write Set_Doublevalue;
    property Decimalvalue: UnicodeString read Get_Decimalvalue write Set_Decimalvalue;
  end;

{ Forward Decls }

  TXMLExamples = class;
  TXMLExample = class;

{ TXMLExamples }

  TXMLExamples = class(TXMLNodeCollection, IXMLExamples)
  protected
    { IXMLExamples }
    function Get_Example(Index: Integer): IXMLExample;
    function Add: IXMLExample;
    function Insert(const Index: Integer): IXMLExample;
  public
    procedure AfterConstruction; override;
  end;

{ TXMLExample }

  TXMLExample = class(TXMLNode, IXMLExample)
  protected
    { IXMLExample }
    function Get_Doublevalue: Double;
    function Get_Decimalvalue: UnicodeString;
    procedure Set_Doublevalue(Value: Double);
    procedure Set_Decimalvalue(Value: UnicodeString);
  end;

{ Global Functions }

function Getexamples(Doc: IXMLDocument): IXMLExamples;
function Loadexamples(const FileName: string): IXMLExamples;
function Newexamples: IXMLExamples;

const
  TargetNamespace = '';

implementation

uses Xml.xmlutil;

{ Global Functions }

function Getexamples(Doc: IXMLDocument): IXMLExamples;
begin
  Result := Doc.GetDocBinding('examples', TXMLExamples, TargetNamespace) as IXMLExamples;
end;

function Loadexamples(const FileName: string): IXMLExamples;
begin
  Result := LoadXMLDocument(FileName).GetDocBinding('examples', TXMLExamples, TargetNamespace) as IXMLExamples;
end;

function Newexamples: IXMLExamples;
begin
  Result := NewXMLDocument.GetDocBinding('examples', TXMLExamples, TargetNamespace) as IXMLExamples;
end;

{ TXMLExamples }

procedure TXMLExamples.AfterConstruction;
begin
  RegisterChildNode('example', TXMLExample);
  ItemTag := 'example';
  ItemInterface := IXMLExample;
  inherited;
end;

function TXMLExamples.Get_Example(Index: Integer): IXMLExample;
begin
  Result := List[Index] as IXMLExample;
end;

function TXMLExamples.Add: IXMLExample;
begin
  Result := AddItem(-1) as IXMLExample;
end;

function TXMLExamples.Insert(const Index: Integer): IXMLExample;
begin
  Result := AddItem(Index) as IXMLExample;
end;

{ TXMLExample }

function TXMLExample.Get_Doublevalue: Double;
begin
  Result := XmlStrToFloatExt(ChildNodes['doublevalue'].Text);
end;

procedure TXMLExample.Set_Doublevalue(Value: Double);
begin
  ChildNodes['doublevalue'].NodeValue := Value;
end;

function TXMLExample.Get_Decimalvalue: UnicodeString;
begin
  Result := ChildNodes['decimalvalue'].Text;
end;

procedure TXMLExample.Set_Decimalvalue(Value: UnicodeString);
begin
  ChildNodes['decimalvalue'].NodeValue := Value;
end;    
end.

As you can see from the databinding generated code, it uses XmlStrToFloatExt to convert the double to a string. When looking at the code behind this function:

type
  PFormatSettings = ^TFormatSettings;

var
  FormatSettings : TFormatSettings;

function GetFormatSettings: PFormatSettings;
begin
  if FormatSettings.DecimalSeparator <> XmlDecimalSeparator then
  begin
    FormatSettings := TFormatSettings.Create('');
    FormatSettings.DecimalSeparator := XmlDecimalSeparator;
  end;
  Result := @FormatSettings;
end;

function XmlFloatToStr(const Value: Extended): string;
begin
  Result := FloatToStr(Value, GetFormatSettings^);
end;

function XmlStrToFloat(const Value: string): Extended;
begin
  Result := StrToFloat(Value, GetFormatSettings^);
end;

function XmlStrToFloatExt(const Value: string): Extended;
var
  s: string;
begin
  s := Trim(Value);
  if s = '' then
    Result := 0.0
  else if SameText(Value, 'NaN') then
    Result := Nan
  else if SameText(Value, 'INF') then
    Result := Infinity
  else if SameText(Value, '-INF') then
    Result := NegInfinity
  else
    Result := XmlStrToFloat(Value);
end;

you can see it will always force XmlDecimalSeparator (defined as '.') regardless of what your regional settings are.