input.xml file -
<?xml version="1.0" encoding="utf-8"?>
<sg:EmployeeDetails xmlns:sg="http://sg.iaea.org/ssac-qs/qsSchema.xsd">
<sg:FirstName>Sagar</sg:FirstName>
<sg:MiddleName>H</sg:MiddleName>
<sg:LastName>Shinde</sg:LastName>
<sg:EmailId>sagarharidasshinde@gmail.com</sg:EmailId>
<sg:Mobile>9021063389</sg:Mobile>
<sg:Years>
<sg:Year>1954</sg:Year>
<sg:Year>1980</sg:Year>
<sg:Year>1954</sg:Year>
</sg:Years>
<sg:Address>135/214, Hindustan Chowk, Mulund Colony, Mulund (W) - 400082</sg:Address>
<sg:Cars>
<sg:Car id="car1" year="1980" condition="count(./ancestor::sg:EmployeeDetails/sg:Years/sg:Year[text() = //sg:Car[@id='car1']/@year]) >=0" message="Driver must be 100 yrs old.">BMW</sg:Car>
<sg:Car id="car2" year="1954" condition="count(./ancestor::sg:EmployeeDetails/sg:Years/sg:Year[text() = //sg:Car[@id='car1']/@year]) >=0" message="Driver must be 100 yrs old.">BMW</sg:Car>
</sg:Cars>
</sg:EmployeeDetails>
I am going to parse this xml file using .net 6. I will select all nodes with attribute 'condition' with xpath as -
//Car[@condition]
I will iterate thru' each node found with the captioned expression, and then evaluate if the expression in the value of the condition attribute is true. (I can not use xs:assert as .net6 does not support xsd1.1)
I would like to set the value of the condition attribute such that I can find if there is at least one ancestor node of 'EmployeeDetails' with more than one 'Year' child-node, having value that matches value of the 'year' attribute of the car node whose 'condition' attribute's value is being evaluated.
string xmlFilePath = "input.xml";
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add("http://sg.iaea.org/ssac-qs/qsSchema.xsd", "input.xsd");
settings.ValidationType = ValidationType.Schema;
ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationEventHandler);
using (XmlReader reader = XmlReader.Create(xmlFilePath, settings))
{
XmlDocument document = new XmlDocument();
document.Load(reader);
var nsmgr = new XmlNamespaceManager(document.NameTable);
nsmgr.AddNamespace("sg", "http://sg.iaea.org/ssac-qs/qsSchema.xsd");
document.Validate(eventHandler);
// Read the XML content and validate against the schema
var nav = document.CreateNavigator();
var list = nav.Select("//sg:Car[@condition]", nsmgr);
while(list.MoveNext())
{
var item = list.Current;
var condition = item.GetAttribute("condition", "");
var result = item.Evaluate(condition, nsmgr);
if (!(bool)result)
{
Console.WriteLine($"Validation error:Validation error");
}
}
}
This code works fine.
I would to remove reference of '//sg:Car[@id='car1']' from
count(./ancestor::sg:EmployeeDetails/sg:Years/sg:Year[text() = //sg:Car[@id='car1']/@year]) >=0
and make it something like
count(./ancestor::sg:EmployeeDetails/sg:Years/sg:Year[text() = @year]) >=0
This exact expression fails as '@year' is searched in the sg:Year node instead of sg:Car node.
Does any one has any idea how to do that?
For completeness the input.xsd file is as below -
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://sg.iaea.org/ssac-qs/qsSchema.xsd"
xmlns:sg="http://sg.iaea.org/ssac-qs/qsSchema.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="EmployeeDetails">
<xs:complexType>
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="MiddleName">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="LastName" type="xs:string" />
<xs:element name="EmailId" type="xs:string" />
<xs:element name="Mobile" type="xs:integer" minOccurs="1" maxOccurs="1"/>
<xs:element name="Years">
<xs:complexType>
<xs:sequence>
<xs:element name="Year" type="sg:Year" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Address">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="5"/>
<xs:maxLength value="100"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Cars" type="sg:Cars"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Cars">
<xs:sequence>
<xs:element name="Car" type="sg:Car" maxOccurs="unbounded">
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Car">
<xs:simpleContent>
<xs:extension base="sg:CarModel">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="year" type="sg:Year" use="required"/>
<xs:attribute name="condition" type="xs:string" use="optional" />
<xs:attribute name="message" type="xs:string" use="optional" fixed="Driver must be 100 yrs old."/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="CarModel">
<xs:restriction base="xs:string">
<xs:enumeration value="Audi"/>
<xs:enumeration value="Golf"/>
<xs:enumeration value="BMW"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="Year">
<xs:restriction base="xs:int">
<xs:minInclusive value="1900" />
<xs:maxInclusive value="2200" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
In terms of XPath 1.0, I think it should work to just check
@year = ancestor::sg:EmployeeDetails/sg:Years/sg:Year
in the context of the sg:Car
element. That condition is only true if there is a year match.
As for doing it with open-source software and XPath 3.1, you can use Saxon HE 10 cross-compiled with IKVM to .NET (Core) as follows:
using Saxon.Api;
using System.Xml;
string xmlFilePath = "input2.xml";
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add("http://sg.iaea.org/ssac-qs/qsSchema.xsd", "input.xsd");
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += (sender, e) =>
{
Console.WriteLine($"{e.Severity} {e.Message}");
};
using (XmlReader reader = XmlReader.Create(xmlFilePath, settings))
{
XmlDocument document = new XmlDocument();
document.Load(reader);
Processor processor = new Processor(false);
DocumentBuilder docBuilder = processor.NewDocumentBuilder();
XdmNode wrappedDoc = docBuilder.Wrap(document);
XPathCompiler xpathCompiler = processor.NewXPathCompiler();
xpathCompiler.DeclareNamespace("", "http://sg.iaea.org/ssac-qs/qsSchema.xsd");
xpathCompiler.DeclareNamespace("sg", "http://sg.iaea.org/ssac-qs/qsSchema.xsd");
foreach (XdmNode car in xpathCompiler.Evaluate("//Car[@condition]", wrappedDoc))
{
var conditionEvaluationResult = xpathCompiler.EvaluateSingle(car.GetAttributeValue("condition"), car) as XdmAtomicValue;
if (!conditionEvaluationResult.GetBooleanValue())
{
Console.WriteLine($"Validation failed for Car {car.GetAttributeValue("id")} with year {car.GetAttributeValue("year")}.");
}
}
}
Your .NET project would have e.g.
<ItemGroup>
<PackageReference Include="SaxonHE10Net31Api" Version="10.9.0.1" />
</ItemGroup>
your conditions would be (I think you want >
not >=
) e.g.
<sg:Car id="car3" year="1984" condition="let $car := . return count(ancestor::sg:EmployeeDetails/sg:Years/sg:Year[. = $car/@year]) >0" message="Driver must be 100 yrs old.">BMW</sg:Car>
Example .NET 6 console project is at https://github.com/martin-honnen/SaxonHE10Net6XPath31Example1.
Disclaimer: Saxon 10 HE is an open-source product from Saxonica that exists on NuGet from Saxonica itself, but only for .NET framework; the project SaxonHE10Net31Api used and referenced above is my recompilation/rebuild of that open source project for .NET (Core) aka .NET 3.1 and later.