6

I have a class which validates the supplied XML document against the supplied XSD. In the class I call the XDocument.Validate method to perform validation, and getting the following error:

The 'http://www.example.com/enrollrequest1:requested-payment-date' element is invalid - The value '2015-05-28T00:00:00' is invalid according to its datatype 'http://www.w3.org/2001/XMLSchema:date' - The string '2015-05-28T00:00:00' is not a valid XsdDateTime value.

The value for element has been set from a .NET DateTime variable, which ultimately sets it with the time part included, since there is no equivalent of xs:date type in .NET.

The values for the elements are being set from a generic module, so I can't pick and choose elements and customize setting their values. The developer sends me value in a .NET DateTime type, which my program in turn calls the XElemet.SetValue(value) method to set it.

Also, the XSD file is out of my control. So modifying the XSD is not an option.

Is there a way to know what is the expected type of the XElement that caused the error? Once I know it, I can just typecast or customize my code accordingly. So for example in this case, if I know that the expected type is xs:date (and not xs:datetime), I can simply typecast the incoming value.

Here is my validator class, if this helps:

Option Strict On
Imports System.XML.Schema

Public Class XmlSchemaValidator
    Public ReadOnly Errors As New List(Of String)

    Private XDoc As XDocument
    Private Schemas As XmlSchemaSet

    Public Sub New(ByVal doc As XDocument, ByVal schemaUri As String, ByVal targetNamespace As String)
        Me.XDoc = doc
        Me.Schemas = New XmlSchemaSet
        Me.Schemas.Add(targetNamespace, schemaUri)
    End Sub

    Public Sub Validate()
        Errors.Clear()
        XDoc.Validate(Schemas, AddressOf XsdErrors)
    End Sub

    Private Sub XsdErrors(ByVal sender As Object, ByVal e As ValidationEventArgs)
        Errors.Add (e.Message)
    End Sub
End Class

Here is the function that is sets the xml node values.

Function SetValue(ByVal xmlDoc As XDocument, ByVal keyValues As Dictionary(Of String, Object)) As Boolean
    '' set values
    For Each kvp In keyValues
        Dim xe As XElement = xmlDoc.Root.XPathSelectElement(kvp.Key)

        ''-- this is buggy implementation for handling xs:date vs xs:datetime that needs to be corrected...
        'If TypeOf kvp.Value Is DateTime AndAlso DirectCast(kvp.Value, DateTime).TimeOfDay = TimeSpan.Zero Then
        '    xe.SetValue(DirectCast(kvp.Value, DateTime).ToString("yyyy-MM-dd"))
        'Else
        xe.SetValue(kvp.Value)
        'End If
    Next

    '' validate final document
    Dim schemaValidator As New XmlSchemaValidator(xmlDoc, schemaFile, "")
    schemaValidator.Validate()
    If schemaValidator.Errors.Count > 0 Then
        'Error Logging code goes here...
        Return False
    End If
    Return True
End Function
Pradeep Kumar
  • 6,836
  • 4
  • 21
  • 47
  • What would you typecast _to_? As you said, there is no `System.Date` class. – John Saunders Jun 05 '15 at 10:10
  • I want to know what the XSD is expecting for that element - `xs:date` or `xs:time` or `xs:datetime`, irrespective of what value is set for the element. The rest I can handle appropriately. – Pradeep Kumar Jun 05 '15 at 10:24
  • So for example if I know that the error was because I provided datetime instead of only date, I can simply do `XElement.SetValue(value.ToString("yyyy-MM-dd"))`. – Pradeep Kumar Jun 05 '15 at 10:28
  • See https://msdn.microsoft.com/en-us/library/xya3a402.aspx – John Saunders Jun 05 '15 at 10:47
  • hmm.. `http://www.w3.org/2001/XMLSchema:date` meaning that the expected type is exactly `xs:date`. So why don't you get the expected type from the validation error message? – har07 Jun 05 '15 at 10:52
  • You may be thinking too generically. Could you solve this particular problem by simply serializing this particular `DateTime` value correctly? Maybe use [`XmlConvert.ToString(dateTime, "yyyy-MM-dd")`](https://msdn.microsoft.com/en-us/library/xya3a402.aspx). – John Saunders Jun 05 '15 at 10:52
  • That's my point. Why do you need to know at runtime, dynamically? Why is the code that generates the XML that dynamic? Maybe show the part of the code that generates the XML. – John Saunders Jun 05 '15 at 10:53
  • So how do I know which one these `XmlConvert.ToString(dateTime, "yyyy-MM-dd")` or `XmlConvert.ToString(dateTime, "yyyy-MM-ddThh:mm:ss")` will not throw error when validated against the xsd file? – Pradeep Kumar Jun 05 '15 at 10:57
  • Also, I don't want to get into the mess of parsing the error message as a string (unless of-course there is no other option). I'm looking for some good way to handle this in a generic way. – Pradeep Kumar Jun 05 '15 at 10:59
  • I just updated my post and provided a trimmed down version of the validator class.. just in case it helps. – Pradeep Kumar Jun 05 '15 at 11:24
  • Edit #2.. added function that sets the element values and calls the validator. – Pradeep Kumar Jun 05 '15 at 11:44
  • Would it help if you actually had `System.Date` and `System.TimeOfDay` data types that used `xs:date` and `xs:time` appropriately, such as [these ones](https://github.com/mj1856/corefx-dateandtime)? – Matt Johnson-Pint Jun 05 '15 at 20:39
  • Matt, this was one of the ideas that came up during the meetings but the idea was rejected. The other suggestion that came up (and was rejected) was to pass me the date/datetime as string, and I could directly embed the value into the xml element. Both these approaches would need us to tell the developer implementing the client side code to specifically implement the custom class or pass us something which is not in a variable of its own type. We would rather like to contain the problem inside out code rather than relying on client code. – Pradeep Kumar Jun 07 '15 at 17:41

1 Answers1

0

You wrote in an earlier comment:

"I want to know what the XSD is expecting for that element"

Then, keep in mind that you can exploit the first parameter "sender" of your validation handlers, for instance, adapting this MSDN sample as, e.g.,

        string xsdMarkup =
 @"<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
   <xsd:element name='Root'>
    <xsd:complexType>
     <xsd:sequence>
      <xsd:element name='Child1' minOccurs='1' maxOccurs='1'/>
      <xsd:element name='Child2' minOccurs='1' maxOccurs='1'/>
     </xsd:sequence>
    </xsd:complexType>
   </xsd:element>
  </xsd:schema>";
        XmlSchemaSet schemas = new XmlSchemaSet();
        schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup)));

        // (just for debug spying)
        var schemata = new XmlSchema[1];
        schemas.CopyTo(schemata, 0);

        XDocument errorDoc = new XDocument(
            new XElement("Root",
                new XElement("Child1", "content1"),
                new XElement("Child2", "content2"),
                new XElement("Child2", "content3") // (must fail validation on maxOccurs)
            )
        );

        Console.WriteLine();
        Console.WriteLine("Validating errorDoc");
        errorDoc.Validate(schemas, (sender, args) =>
        {
            Console.WriteLine("{0}", args.Message); // (what you're already doing)
            Console.WriteLine();
            // but there's also:
            var xElement = sender as XElement;
            if (xElement != null)
            {
                Console.WriteLine("Element {0} invalid : {1}", xElement, e.Exception.Message);
            }
        });

        Console.ReadKey();

This can yield an output with enough information on the recognizable culprits within the document, hopefully:

Validating errorDoc
The element 'Root' has invalid child element 'Child2'.

Element <Child2>content3</Child2> invalid : The element 'Root' has invalid child element 'Child2'.

Anyway, once you know the culprit in the invalid document, then you have better chances to correlate more reliably with the corresponding definitions in the schema(s) used for this validation (than just relying on the schema validation error string).

(sorry for the answer in C# syntax, but I'd rather not write in incorrect VB.NET, I'm getting too rusty with, by now)

'Hope this helps.

YSharp
  • 1,066
  • 9
  • 8