0

I have XML where the values are saved in scientific notation and I need to preform some comparisons on that value . But the scientific notation numbers are not working as expected.

Below is the code I have:

XmlDocument doc = new XmlDocument();
doc.LoadXml("<MainLevel><SubLevel Name=\"test\"  Amount=\"0.0E0\"></SubLevel></MainLevel>");
    
XmlNodeList? xmlLevelsList1 = doc.SelectNodes("MainLevel/SubLevel[@Amount<=275.00]");// this does not return anything because 0.0E0 is in scientific notation                
doc.LoadXml("<MainLevel><SubLevel Name=\"test\"  Amount=\"0.00\"></SubLevel></MainLevel>");

XmlNodeList? xmlLevelsList2 = doc.SelectNodes("MainLevel/SubLevel[@Amount<=275.00]"); this does not return anything because 0.00 is a good format

I want xmlLevelsList1 to return 1 node instead of 0. It is returning 0 because

"0.0E0" is not a valid number.

Helen Araya
  • 1,886
  • 3
  • 28
  • 54
  • Maybe this [SO question](https://stackoverflow.com/q/4367737/1305969) will help you. In short: *"XSLT 1.0 does not have support for scientific notation."* – zx485 Jun 28 '23 at 22:45
  • @zx485 number(@Amount) does not give me the desired result. Do you know how the syntax may be for the attribute to be converted to a number. – Helen Araya Jun 28 '23 at 22:51
  • Yes and No. You have to use XSLT-2.0. As mentioned above, XSLT-1.0 does not support scientific notation. The `fn:number(...)` function only works in XSLT-2.0 with exponential numbers. The question/answer mentioned above describes a workaround for XSLT-1.0. – zx485 Jun 28 '23 at 22:55
  • @zx485 I am using 2.0 but not sure how to do it in my case. – Helen Araya Jun 28 '23 at 23:00
  • I tested your expressions with XSLT-2.0 and they work with scientific numbers! You tagged your question with [tag:xquery] which supports XPath-2.0, but your code looks like [tag:c#] which only supports XPath-1.0. Which variant is true? – zx485 Jun 28 '23 at 23:31
  • The C#/.NET program, is that a .NET framework program (for Windows only) or a cross platform .NET (6/7, previously called .NET Core) program? For .NET framework you can use Saxon.NET HE from Saxonica https://www.nuget.org/packages/Saxon-HE, its DocumentBuilder class can build or wrap from an XmlDocument https://www.saxonica.com/html/documentation10/dotnetdoc/Saxon/Api/DocumentBuilder.html#Wrap(XmlDocument) to then use XPath 3.1 against the Microsoft DOM implementation. – Martin Honnen Jun 29 '23 at 09:35
  • For .NET Core there is https://www.nuget.org/packages/SaxonHE10Net31Api and https://www.nuget.org/packages/SaxonHE10Net31 which is basically Saxon.NET HE 10 but recompiled for .NET Core instead of .NET framework. Of course, if you are working in a commercial environment, for .NET 6/7 (Core) the best option is the commercial SaxonCS package from Saxonica: https://www.nuget.org/packages/SaxonCS – Martin Honnen Jun 29 '23 at 09:39

3 Answers3

1

It is better to use LINQ to XML API. It is available in the .Net Framework since 2007.

As @zx485 already pointed out: "XSLT 1.0 does not have support for scientific notation."

So, we cannot use an XPath expression.

Converting to a double data type on the .Net side does the job.

c#

void Main()
{
    XDocument doc = XDocument.Parse(@"<MainLevel>
            <SubLevel Name='test' Amount='0.0E0'></SubLevel>
            <SubLevel Name='test1'  Amount='10.0E1'></SubLevel>
            <SubLevel Name='test1'  Amount='4.0E5'></SubLevel>
            <SubLevel Name='test1'  Amount='2.75E2'></SubLevel>
            <SubLevel Name='test1'  Amount='2.76E2'></SubLevel>
        </MainLevel>");
    
    var xmlLevelsList1 = doc.Descendants("SubLevel")
        .Where(d => double.Parse(d.Attribute("Amount").Value) <= 275.00);
    Console.WriteLine(xmlLevelsList1);
}

Output

<SubLevel Name="test" Amount="0.0E0"></SubLevel>
<SubLevel Name="test1" Amount="10.0E1"></SubLevel>
<SubLevel Name="test1" Amount="2.75E2"></SubLevel>
Yitzhak Khabinsky
  • 18,471
  • 2
  • 15
  • 21
0

The number() function can be used in XPath 1.0

/MainLevel/SubLevel[number(@Amount) <= 275.00]

Given

<?xml version="1.0" encoding="utf-8" ?>
<MainLevel>
    <SubLevel Name="test"  Amount="0.0E0"></SubLevel>
    <SubLevel Name="test1"  Amount="10.0E1"></SubLevel>
    <SubLevel Name="test1"  Amount="4.0E5"></SubLevel>
    <SubLevel Name="test1"  Amount="2.75E2"></SubLevel>
    <SubLevel Name="test1"  Amount="2.76E2"></SubLevel>
</MainLevel>

Will return

<SubLevel Name="test" Amount="0.0E0"/>
<SubLevel Name="test1" Amount="10.0E1"/>
<SubLevel Name="test1" Amount="2.75E2"/>

Tested with xmllint that uses libxml2

 xmllint --xpath '/MainLevel/SubLevel[number(@Amount) <= 275.00]' tmp.xml
<SubLevel Name="test" Amount="0.0E0"/>
<SubLevel Name="test1" Amount="10.0E1"/>
<SubLevel Name="test1" Amount="2.75E2"/>

Testing with python lxml

from lxml import etree
tree = etree.parse("tmp.xml")
[e.get('Amount') for e in tree.xpath('/MainLevel/SubLevel[number(@Amount) <= 275.00]')]
['0.0E0', '10.0E1', '2.75E2']
LMC
  • 10,453
  • 2
  • 27
  • 52
  • this is not working for me for some reason? I am using a simple console C# app. – Helen Araya Jun 29 '23 at 01:19
  • I tried this method in c#. It is not working. – Yitzhak Khabinsky Jun 29 '23 at 01:20
  • It depends on the XPath implementation I believe. I tested it with `xmllint` that implements `libxml2`. – LMC Jun 29 '23 at 01:22
  • The .Net Framework is on XML technology stack v.1.0 (circa 1999-2001), across the board: XPath, XSLT, and XSD. No support for XQuery, just in MS SQL Server. – Yitzhak Khabinsky Jun 29 '23 at 01:24
  • Couldn't find any doc for available fucntions and stuff for .NET. You could try this simple XPath an see how it works `number("10.0E2")`. It should return `1000` – LMC Jun 29 '23 at 01:35
0

Using Saxon.NET:

using System.Xml;
using Saxon.Api;

XmlDocument doc = new XmlDocument();
doc.LoadXml("<MainLevel><SubLevel Name=\"test\"  Amount=\"0.0E0\"></SubLevel></MainLevel>");
    
XmlNodeList? xmlLevelsList1 = doc.SelectNodes("MainLevel/SubLevel[@Amount<=275.00]");// this does not return anything because 0.0E0 is in scientific notation                

Console.WriteLine(xmlLevelsList1.Count);

Processor processor = new Processor();

var docBuilder = processor.NewDocumentBuilder();

var xdmDoc = docBuilder.Wrap(doc);

var xpathCompiler = processor.NewXPathCompiler();

var xdmResult = xpathCompiler.Evaluate("MainLevel/SubLevel[@Amount<=275.00]", xdmDoc);

Console.WriteLine(xdmResult.Count);

Output:

0
1

Do note that, while you can run XPath 3.1 against the Microsoft DOM (i.e. XmlDocument/XmlNode) that Saxon has its own tree model and it is recommended to only use the Microsoft DOM if you have legacy code where you can't switch to Saxon's native tree model.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110