Search code examples
xmlc++builderdecimal-pointfiremonkey-fm3c++builder-xe5

How to set XML parser's Decimal point character?


I'm reading attribute fields in an XML document where the decimal point always is '.' and the computer local may differ from this (in my own case it is ',').

I tried to set the global FormatSettings.DecimalSeparator to '.' but it has no effect on the XML parser. This is a very compressed version of the problem.

_di_IXMLDocument Document;
_di_IXMLNode     Node;
float            Value;

Document = LoadXMLDocument("Test.xml");
Node = Document->DocumentElement;
FormatSettings.DecimalSeparator = '.';
Value = Node->GetAttribute("scale");

Assume this XML file.

<?xml version="1.0" encoding="utf-8"?>
<myroot scale="1.234">
</myroot>

After reading the attribute scale I always get the result striped on the '.' character resulting in Value = 1234 instead of 1.234.

The number of decimals are not constant, they can be 1 or 4 or anything in between. This also goes for the whole part, so dividing by 100 or 1000 will not solve the problem.

I would preferable have the OLEVariant to accept the '.' as a decimal point (my local is ',').

I had a look at SetLocalInfo() but this will set the format for ALL applications. The getlocale() function manipulates the current thread but I have not found a way to explicitly specify the character to use. It seems like it is only possible to select a code page or localization, as in a country.

EDIT
I tried to use setlocal() and select English-US as localization. Even if US are using '.' as the decimal separator, the XML Parsers seems to ignore this.

If I manually change the '.' to a ',' in the XML file, it works fine. But the XML file is a third party file, which I have no control of. So I really need to read it as it is with a '.' decimal separator


Solution

  • This is a well-known problem with the way the IXMLNode.NodeValue property handles floating-point numbers. It has nothing to do with the underlying XML engine (MSXML, etc).

    The NodeValue property getter returns an OleVariant that contains the attribute value as a String, not as a float. You are then assigning that OleVariant to a float. The RTL performs a conversion using OS locale settings, not RTL locate settings, which is why FormatSettings has no effect.

    The NodeValue property setter receives an OleVariant as input. Passing a float directly to it performs a conversion to a String when inserting the value into the XML DOM, and that conversion is also not tied to FormatSettings.

    NodeValue is locale-sensitive, but XML is not. The XML standard specifically outlines exactly how floating-point numbers must be formatted, which IXMLNode does not take into account. So you will have to read/write floating-point values as String values so that you can handle conversions yourself, eg:

    TFormatSettings fmt = TFormatSettings::Create();
    fmt.DecimalSeparator = '.';
    fmt.ThousandSeparator = 0;
    Value = StrToFloat(Node->Attributes["scale"], fmt);
    

    TFormatSettings fmt = TFormatSettings::Create();
    fmt.DecimalSeparator = '.';
    fmt.ThousandSeparator = 0;
    Node->Attributes["scale"] = FloatToStr(Value, fmt);