The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,225 other subscribers

Validate XML with XSD in .NET and native MSXML – big difference in string maxLength validation with newlines (samples in C# and Delphi)

Posted by jpluimers on 2010/01/19

Recently, I had an issue while validating XML with XSD: validation in .NET using the built in classes in the System.XML namespace, and validation in native Windows using the COM objects exposed by  MSXML version 6 (which incidentally ships with the .NET 3.0 framework).

Some documents validating OK in .NET did not validate well with MSXML.

I’ll show my findings below, and try to explain the difference I found, together with my conclusions.
The main conclusion is that MSXML version 6 has a bug, but I wonder why I can’t find much more information on it.

Since there is not so much ready to use for validating XML by XSD in .NET and native, I’ll include complete source code of command-line validations applications for both platforms.
.NET source code is in C#.
Native source code is in Delphi.

So lets get started with the XSD:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
  <xs:element name="content">
	<xs:simpleType>
	  <xs:restriction base="xs:string">
		<xs:maxLength value="25"/>
	  </xs:restriction>
	</xs:simpleType>
  </xs:element>
</xs:schema>

An XML document like the one directly below will validate OK with the XSD, both in .NET and with MSXML version 6:

<?xml version="1.0" encoding="UTF-8"?>
<content>text</content>

The odd thing is, this XML file validates OK in .NET, but not in MSXML version 6:

<?xml version="1.0" encoding="UTF-8"?>
<content>123456789
123456789
12345</content>

Even stranger is that both .NET and MSXML agree on the length of the content element in the above XML file: 25 characters, just like with this XML:

<?xml version="1.0" encoding="UTF-8"?>
<content>1234567890123456789012345</content>

Just look at these two dumps of the above XML files from .NET:

NodeType=Element, NodeName=content
NodeType=Text, NodeName= Length=25 LF-Count=2 CR-Count=0
123456789
123456789
12345

NodeType=Element, NodeName=content
NodeType=Text, NodeName= Length=25 LF-Count=0 CR-Count=0
1234567890123456789012345

and from MSXML:

NodeType=1, NodeName=content
NodeType=3, NodeName=#text
NodeValue-Length=25, LF-Count=2, CR-Count=0
123456789
123456789
12345

NodeType=1, NodeName=content
NodeType=3, NodeName=#text
NodeValue-Length=25, LF-Count=0, CR-Count=0
1234567890123456789012345

So: for the XML, both .NET and MSXML agree on the length of content (both cases 25), and for the first case an LF count of 2, and a CR count of 0 (zero).
Even though the XML files here store their line endings as CRLF pairs (see the dumps below), they should be treated as one character.
Both the Microsoft documentation (end of the page) and the W3C documentation (section 2.11) state this.

000000: 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 <?xml version="1
000010: 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D 22 55 54 .0" encoding="UT
000020: 46 2D 38 22 3F 3E 0D 0A 3C 63 6F 6E 74 65 6E 74 F-8"?>..<content
000030: 3E 31 32 33 34 35 36 37 38 39 0D 0A 31 32 33 34 >123456789..1234
000040: 35 36 37 38 39 0D 0A 31 32 33 34 35 3C 2F 63 6F 56789..12345</co
000050: 6E 74 65 6E 74 3E 0D 0A 0D 0A ntent>….

000000: 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 <?xml version="1
000010: 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D 22 55 54 .0" encoding="UT
000020: 46 2D 38 22 3F 3E 0D 0A 3C 63 6F 6E 74 65 6E 74 F-8"?>..<content
000030: 3E 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 >123456789012345
000040: 36 37 38 39 30 31 32 33 34 35 3C 2F 63 6F 6E 74 6789012345</cont
000050: 65 6E 74 3E 0D 0A 0D 0A ent>….

So, I tried once more with this file which is LF separated:

000000: 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 <?xml version="1
000010: 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D 22 55 54 .0" encoding="UT
000020: 46 2D 38 22 3F 3E 0A 3C 63 6F 6E 74 65 6E 74 3E F-8"?>.<content>
000030: 31 32 33 34 35 36 37 38 39 0A 31 32 33 34 35 36 123456789.123456
000040: 37 38 39 0A 31 32 33 34 35 3C 2F 63 6F 6E 74 65 789.12345</conte
000050: 6E 74 3E nt>

And now both .NET and MSXML version 6 have the same result: this file is valid in both cases.

Conclusion

From the above, I have a couple of conclusions.

  1. For XSD validation, MSXML does not adhere to the W3C standard with respect to CR LF end-of-line handling, but the .NET implementation does.
  2. Contrary to what a lot of people believe, the .NET XML implementation is not just a shell around MSXML.
  3. When validating XML with XSD using MSXML, you have to make sure you do not use CR LF end-of-lines. Normalize them to LF line endings (or CR line endings) before putting them through MSXML.

Source code

Since there is not so much sample XML with XSD validation sample source available in Delphi nor in C#/.NET, here are some samples.

For both environments, there are two samples:

  • one for validating an XML document with one or more XSD documents,
  • and one to dump one or more XML documents.

The C# samples are based on XMLReader, XmlReaderSettings and XmlSchema objects. They accommodate for includes in the XSD files (back when I wrote the original code, I had some nasty includes from relative directories).

The Delphi samples are based on MSXML, and use IXMLDOMDocument and IXMLDOMSchemaCollection. I did not add special code for includes in XSD files here, as the XSD files were all in once piece.

C# sample code

The C# sample code needs these assemblies

  • – mscorlib.dll
  • – System.dll
  • – System.Xml.dll

C# console apps

using System;

namespace bo.Xml
{
    class ValidateXmlWithXsd
    {
        static void Main(string[] args)
        {
            if (2 != args.Length)
            {
                Console.WriteLine("use two parameters: XmlFile and XsdFile");
            }
            else
            {
                logic instance = new logic();
                try
                {
                    instance.Run(
                        args[0], args[1]
                    );
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
        }
    }

    class logic
    {
        public void Run(string xmlFileName, string xsdFileName)
        {
            bo.Xml.XmlValidator validator = new bo.Xml.XmlValidator();

            validator.XmlReadEventHandler += new bo.Xml.XmlReadEventHandler(validator_XmlReadEventHandler);
            validator.XmlValidationEventHandler += new bo.Xml.XmlValidationEventHandler(validator.ValidationResultEventHandler);
            validator.ValidateXml(xmlFileName, xsdFileName);
            string result = validator.ToString();
            if (string.IsNullOrEmpty(result))
            {
                result = "OK.";
            }
            Console.WriteLine(result);
        }

        void validator_XmlReadEventHandler(object sender, bo.Xml.XmlReadEventArgs e)
        {
            //if (reader.NodeType == XmlNodeType.Text)
            //    Console.WriteLine(reader.Value);
        }
    }
}
using System;

namespace bo.Xml
{
    class DumpXml
    {
        static void Main(string[] args)
        {
            if (1 > args.Length)
            {
                Console.WriteLine("use parameters: [XmlFile]...");
            }
            else
            {
                logic instance = new logic();
                try
                {
                    instance.Run(args);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }
        }
    }

    class logic
    {
        public void Run(string[] xmlFileNames)
        {
            bo.Xml.XmlDumper dumper = new bo.Xml.XmlDumper();

            dumper.DumpXml(xmlFileNames);

            string result = dumper.ToString();
            if (!string.IsNullOrEmpty(result))
                Console.WriteLine(result);
        }
    }
}

C# supporting classes

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;

namespace bo.Xml
{
    public class XmlDumper
    {
        public void DumpXml(string[] xmlFileNames)
        {

            foreach (string xmlFileName in xmlFileNames)
            {
                DumpXml(xmlFileName);
            }

        }

        public void DumpXml(string xmlFileName)
        {
            using (FileStream xmlFile = File.OpenRead(xmlFileName))
            {
                using (XmlReader reader = XmlReader.Create(xmlFile))
                {
                    while (reader.Read())
                    {
                        string currentNodeAsString = GetCurrentNodeAsString(reader);
                        if (!string.IsNullOrEmpty(currentNodeAsString))
                        {
                            if (null == toString)
                                toString = new StringBuilder();
                            toString.AppendLine(currentNodeAsString);
                        }
                    }
                }
            }
        }

        private StringBuilder toString = null;

        public override string ToString()
        {
            return (null == toString) ? null : toString.ToString();
        }

        private int charCount(char compareValue, string value)
        {
            int result = 0;
            foreach (System.Char item in value)
            {
                if (item == compareValue)
                    result++;
            }
            return result;
        }

        public string GetCurrentNodeAsString(XmlReader reader)
        {
            StringBuilder result = new StringBuilder();

            switch (reader.NodeType)
            {
                case XmlNodeType.Element:
                case XmlNodeType.Text:
                    AppendNodeTypeAndName(reader, result);
                    break;
                default:
                    break;
            }

            switch (reader.NodeType)
            {
                case XmlNodeType.Text:
                    string value = reader.Value;
                    int lfCount = charCount('\n', value);
                    int crCount = charCount('\r', value);
                    result.AppendFormat(" Length={0} LF-Count={1} CR-Count={2}", value.Length, lfCount, crCount);
                    AppendValueOnNewLine(reader, result);
                    break;
                case XmlNodeType.CDATA:
                case XmlNodeType.ProcessingInstruction:
                case XmlNodeType.Comment:
                case XmlNodeType.DocumentType:
                    AppendValueOnNewLine(reader, result);
                    break;
                default:
                    break;
            }
            return result.ToString();
        }

        private static void AppendNodeTypeAndName(XmlReader reader, StringBuilder result)
        {
            result.AppendFormat("NodeType={0}, NodeName={1}", reader.NodeType, reader.Name);
        }

        private static void AppendValueOnNewLine(XmlReader reader, StringBuilder result)
        {
            result.AppendLine();
            result.Append(reader.Value);
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;

namespace bo.Xml
{
    public class XmlReadEventArgs : EventArgs
    {
        public XmlReadEventArgs(XmlReader reader)
            : base()
        {
            this.reader = reader;
        }

        private XmlReader reader;
        public XmlReader Reader
        {
            get { return reader; }
        }
    }

    public class XmlValidationEventArgs : EventArgs
    {
        public XmlValidationEventArgs(XmlReader reader, System.Xml.Schema.ValidationEventArgs e)
            : base()
        {
            this.e = e;
            this.reader = reader;
        }

        private System.Xml.Schema.ValidationEventArgs e;
        public System.Xml.Schema.ValidationEventArgs E
        {
            get { return e; }
        }

        private XmlReader reader;
        public XmlReader Reader
        {
            get { return reader; }
        }
    }

    public delegate void XmlReadEventHandler(Object sender, XmlReadEventArgs e);
    public delegate void XmlValidationEventHandler(Object sender, XmlValidationEventArgs e);

    public class XmlValidator
    {
        public event XmlReadEventHandler XmlReadEventHandler;
        public event XmlValidationEventHandler XmlValidationEventHandler;

        public void ValidateXml(string xmlFileName, string xsdFileName)
        {
            ValidateXml(xmlFileName, new string[] { xsdFileName });
        }

        public void ValidateXml(string xmlFileName, string[] xsdFileNames)
        {
            List<FileStream> xsdFileStreamsToDispose = new List<FileStream>();
            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.ValidationType = ValidationType.Schema;

                foreach (string xsdFileName in xsdFileNames)
                {
                    // resolve the relative SchemaLocation of the included Xsd's using the
                    // base path of the xsdFileName
                    // see http://www.hanselman.com/blog/ValidatingXMLSchemasThatHaveBeenXsimportedAndHaveSchemaLocation.aspx
                    // Refactoring idee: in de toekomst oplossen met een resolver (die recursief werkt)
                    // see http://www.hanselman.com/blog/ValidatingThatXmlSchemasAndTheirImportsAreValidAndAllGoodWithAnXmlResolver.aspx

                    FileStream xsdFileStream = File.OpenRead(xsdFileName);
                    xsdFileStreamsToDispose.Add(xsdFileStream);
                    XmlSchema schema = XmlSchema.Read(xsdFileStream, null);

                    string baseDirectory = Path.GetDirectoryName(xsdFileName);
                    foreach (XmlSchemaInclude include in schema.Includes)
                    {
                        // if include.SchemaLocation is an absolute path, Path.Combine returns that,
                        // otherwise it appends the relative path to the baseDirectory
                        include.SchemaLocation = Path.Combine(baseDirectory, include.SchemaLocation);
                    }

                    settings.Schemas.Add(schema);
                }

                settings.ValidationEventHandler +=
                    new ValidationEventHandler(reader_ValidationEventHandler);

                using (FileStream xmlFile = File.OpenRead(xmlFileName))
                {
                    using (reader = XmlReader.Create(xmlFile, settings))
                    {
                        while (reader.Read())
                        {
                            currentLine = GetCurrentNodeAsString(reader);
                            OnReadEventHandler();
                        }
                    }
                }
            }
            finally
            {
                foreach (IDisposable fileStream in xsdFileStreamsToDispose)
                {
                    fileStream.Dispose();
                }
            }
        }

        protected void OnReadEventHandler()
        {
            if (null != XmlReadEventHandler)
            {
                XmlReadEventHandler(this, new XmlReadEventArgs(reader));
            }
        }

        private XmlReader reader;
        private string currentLine;

        private void reader_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            if (null != XmlValidationEventHandler)
            {
                XmlValidationEventHandler(this, new XmlValidationEventArgs(reader, e));
            }
        }

        private StringBuilder toString = null;

        public override string ToString()
        {
            return (null == toString) ? null : toString.ToString();
        }

        public void ValidationResultEventHandler(object sender, XmlValidationEventArgs e)
        {
            if (null == toString)
            {
                toString = new StringBuilder();
            }

            toString.AppendFormat("{0}: {1}", e.E.Severity.ToString(), e.E.Message);
            toString.AppendLine();

            if (!string.IsNullOrEmpty(currentLine))
                toString.AppendLine(currentLine);

            string line = GetCurrentNodeAsString(e.Reader);
            if (!string.IsNullOrEmpty(line))
                toString.AppendLine(line);
        }

        public string GetCurrentNodeAsString(XmlReader reader)
        {
            string result = "";
            switch (reader.NodeType)
            {
                case XmlNodeType.Element:
                    result = string.Format("<{0}>", reader.Name);
                    break;
                case XmlNodeType.Text:
                    result = reader.Value;
                    break;
                case XmlNodeType.CDATA:
                    result = string.Format("<![CDATA[{0}]]>", reader.Value);
                    break;
                case XmlNodeType.ProcessingInstruction:
                    result = string.Format("<?{0} {1}?>", reader.Name, reader.Value);
                    break;
                case XmlNodeType.Comment:
                    result = string.Format("<!--{0}-->", reader.Value);
                    break;
                case XmlNodeType.XmlDeclaration:
                    result = string.Format("<?{0} {1}?>", reader.Name, reader.Value);
                    break;
                case XmlNodeType.Document:
                    break;
                case XmlNodeType.DocumentType:
                    result = string.Format("<!DOCTYPE {0} [{1}]", reader.Name, reader.Value);
                    break;
                case XmlNodeType.EntityReference:
                    toString.Append(reader.Name);
                    break;
                case XmlNodeType.EndElement:
                    result = string.Format("</{0}>", reader.Name);
                    break;
                default:
                    break;
            }
            return result;
        }
    }
}

Delphi sample code

The Delphi sample code needs MSXML6.DLL (usually found in c:\WINDOWS\system32\msxml6.dll) imported as MSXML2_TLB.pas (in the Delphi IDE, choose the menu item “Components”, followed by “Import Component”, in the dialog choose “Import ActiveX component”, then select the right DLL, and import it into your project).

The code has been tested with Delphi 2007.

Delphi console apps

program DumpXml;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  MSXML2_TLB in 'MSXML2_TLB.pas',
  XmlDumperUnit in 'XmlDumperUnit.pas';

var
  Index: Integer;

begin
  try
    if ParamCount < 1 then
      Writeln('use parameters: [XmlFile]...')
    else
      for Index := 1 to ParamCount do
      with TXmlDumper.Create() do
        try
          Dump(ParamStr(Index));
          Writeln(DumpResult);
        finally
          Free;
        end;
  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.
program ValidateXmlWithXsd;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  XmlValidatorUnit in 'XmlValidatorUnit.pas',
  MSXML2_TLB in 'MSXML2_TLB.pas',
  XmlDumperUnit in 'XmlDumperUnit.pas';

begin
  try
    if ParamCount <> 2 then
    begin
      Writeln('use two parameters: XmlFile and XsdFile');
    end
    else
    begin
      with TXmlValidator.Create do
        try
          if ValidateXml(ParamStr(1), ParamStr(2)) then
            Writeln('OK')
          else
          begin
            Writeln(ValidationResult);
          end;
        finally
          Free;
        end;
      with TXmlDumper.Create() do
        try
          Dump(ParamStr(1));
          Writeln(DumpResult);
        finally
          Free;
        end;
    end;
  except
    on E: Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

Delphi supporting classes

unit XmlDumperUnit;

interface

uses
  Classes, MSXML2_TLB, ActiveX;

type
  TXmlDumper = class(TObject)
  strict private
    FDumpStrings: TStrings;
  strict protected
    function GetHaveDump: Boolean; virtual;
    function GetDumpResult: string; virtual;
    function GetDumpStrings: TStrings; virtual;
    procedure DumpNode(const Node: IXMLDOMNode); virtual;
    property HaveDump: Boolean read GetHaveDump;
    property DumpStrings: TStrings read GetDumpStrings;
  public
    destructor Destroy; override;
    function Dump(const XmlFileName: string): Boolean; overload; virtual;
    function Dump(const XmlFileNames: array of string): Boolean; overload; virtual;
    property DumpResult: string read GetDumpResult;
  end;

implementation

uses
  SysUtils, Variants, ComObj;

destructor TXmlDumper.Destroy;
begin
  inherited;

  FreeAndNil(FDumpStrings);
end;

function TXmlDumper.GetDumpResult: string;
begin
  if HaveDump then
    Result := DumpStrings.Text
  else
    Result := NullAsStringValue;
end;

function TXmlDumper.Dump(const XmlFileName: string): Boolean;
var
  MultipleErrorMessages: Boolean;
  XmlDocument: IXMLDOMDocument3;
  node: IXMLDOMNode;
begin
  XmlDocument := CoFreeThreadedDOMDocument60.Create();
  if not Assigned(XmlDocument) then
    RaiseLastOSError();

  if not XmlDocument.load(XmlFileName) then
    RaiseLastOSError();

  node := XmlDocument.documentElement;

  DumpNode(node);

  Result := not HaveDump;
end;

function CharCount(const S: string; const CharToCount: Char): Integer;
var
  Ch: Char;
begin
  Result := 0;
  for Ch in S do
  begin
    if Ch = CharToCount then
      Inc(Result);
  end;
end;

function TXmlDumper.Dump(const XmlFileNames: array of string): Boolean;
var
  XmlFileName: string;
begin
  for XmlFileName in XmlFileNames do
    Dump(XmlFileName);

  Result := not HaveDump;
end;

procedure TXmlDumper.DumpNode(const Node: IXMLDOMNode);
var
  attribute: IXMLDOMNode;
  attributes: IXMLDOMNamedNodeMap;
  childNode: IXMLDOMNode;
  childNodes: IXMLDOMNodeList;
  HaveNodeValue: Boolean;
  NodeType: DOMNodeType;
  NodeValue: OleVariant;
  NodeValueLength: Integer;
  NodeValueString: string;
  LfCount: Integer;
  CrCount: Integer;
begin
  if not Assigned(Node) then
    Exit;

  NodeType := Node.NodeType;
  NodeValue := Node.nodeValue;

  HaveNodeValue := not VarIsNull(NodeValue);

  DumpStrings.Add(Format('NodeType=%d, NodeName=%s', [NodeType, Node.nodeName]));

  if HaveNodeValue then
  begin
    NodeValueString := NodeValue;
    NodeValueLength := Length(NodeValueString);
    LfCount := CharCount(NodeValueString, #10);
    CrCount := CharCount(NodeValueString, #13);
    DumpStrings.Add(Format('NodeValue-Length=%d, LF-Count=%d, CR-Count=%d', [NodeValueLength, LfCount, CrCount]));
    DumpStrings.Add(NodeValueString);
  end;

  attributes := Node.attributes;
  if Assigned(attributes) then
  begin
    attribute := attributes.nextNode;
    if Assigned(attribute) then
      repeat
        DumpNode(attribute);
        attribute := attributes.nextNode;
      until not Assigned(attribute);

  end;
  childNodes := Node.childNodes;
  if Assigned(childNodes) then
  begin
    childNode := childNodes.nextNode;
    if Assigned(childNode) then
      repeat
        DumpNode(childNode);
        childNode := childNodes.nextNode;
      until not Assigned(childNode);
  end;
end;

function TXmlDumper.GetHaveDump: Boolean;
begin
  Result := Assigned(FDumpStrings)
end;

function TXmlDumper.GetDumpStrings: TStrings;
begin
  if not HaveDump then
    FDumpStrings := TStringList.Create();
  Result := FDumpStrings;
end;

initialization
  // http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks_26.html
  if Assigned(ComObj.CoInitializeEx) then
    ComObj.CoInitializeEx(nil, COINIT_MULTITHREADED)
  else
    CoInitialize(nil);
finalization
  CoUninitialize;
end.
unit XmlValidatorUnit;

interface

uses
  MSXML2_TLB, Classes;

type
  /// loosely based on http://msdn.microsoft.com/en-us/library/ms765386(VS.85).aspx
  /// and http://www.nonhostile.com/howto-validate-xml-xsd-in-vb6.asp
  TXmlValidator = class(TObject)
  strict private
    FValidationResultStrings: TStrings;
  strict protected
    function GetHaveValidationResult: Boolean; virtual;
    function GetValidationResult: string; virtual;
    function GetValidationResultStrings: TStrings; virtual;
    procedure ProcessParseError(const parseError: IXMLDOMParseError); virtual;
    procedure ProcessParseError2(const parseError2: IXMLDOMParseError2); virtual;
    property HaveValidationResult: Boolean read GetHaveValidationResult;
    property ValidationResultStrings: TStrings read GetValidationResultStrings;
  public
    destructor Destroy; override;
    function ValidateXml(const XmlFileName, XsdFileName: string): Boolean; overload;
    function ValidateXml(const XmlFileName: string; const XsdFileNames: array of string): Boolean; overload;
    property ValidationResult: string read GetValidationResult;
  end;

implementation

uses
  Variants,
  SysUtils, ActiveX, ComObj;

destructor TXmlValidator.Destroy;
begin
  inherited;

  FreeAndNil(FValidationResultStrings);
end;

function TXmlValidator.GetHaveValidationResult: Boolean;
begin
  Result := Assigned(FValidationResultStrings)
end;

function TXmlValidator.GetValidationResult: string;
begin
  if HaveValidationResult then
    Result := ValidationResultStrings.Text
  else
    Result := NullAsStringValue;
end;

function TXmlValidator.GetValidationResultStrings: TStrings;
begin
  if not HaveValidationResult then
    FValidationResultStrings := TStringList.Create();
  Result := FValidationResultStrings;
end;

procedure TXmlValidator.ProcessParseError(const parseError: IXMLDOMParseError);
var
  Reason: WideString;
begin
  Reason := parseError.reason;
  //    parseError.errorCode;
  //    parseError.url;
  if Reason <> NullAsStringValue then
    ValidationResultStrings.Add(Reason);
  //    parseError.srcText;
end;

procedure TXmlValidator.ProcessParseError2(const parseError2: IXMLDOMParseError2);
var
  ParseErrorCollection: IXMLDOMParseErrorCollection;
  item: IXMLDOMParseError2;
begin
  ProcessParseError(parseError2);

  ParseErrorCollection := parseError2.allErrors;
  if Assigned(ParseErrorCollection) then
  begin
    item := ParseErrorCollection.Next;
    if Assigned(item) then
      repeat
        ProcessParseError(item);
        item := ParseErrorCollection.Next;
      until not Assigned(item);
  end;
end;

function TXmlValidator.ValidateXml(const XmlFileName, XsdFileName: string): Boolean;
begin
  Result := ValidateXml(XmlFileName, [XsdFileName]);
end;

function TXmlValidator.ValidateXml(const XmlFileName: string; const XsdFileNames: array of string): Boolean;
var
  MultipleErrorMessages: Boolean;
  XmlDocument: IXMLDOMDocument3;
  XsdDocument: IXMLDOMDocument3;
  SchemaCollection: IXMLDOMSchemaCollection2;
  targetNamespaceNode: IXMLDOMNode;
  namespaceURI: string;
  XsdFileName: string;
  parseError: IXMLDOMParseError;
  parseError2: IXMLDOMParseError2;
begin
  XmlDocument := CoFreeThreadedDOMDocument60.Create();
  if not Assigned(XmlDocument) then
    RaiseLastOSError();

  if not XmlDocument.load(XmlFileName) then
    RaiseLastOSError();

  SchemaCollection := CoXMLSchemaCache60.Create();
  if not Assigned(SchemaCollection) then
    RaiseLastOSError();

  for XsdFileName in XsdFileNames do
  begin
    XsdDocument := CoFreeThreadedDOMDocument60.Create();
    if not Assigned(XsdDocument) then
      RaiseLastOSError();

    if not XsdDocument.load(XsdFileName) then
      RaiseLastOSError();

    targetNamespaceNode := XsdDocument.documentElement.attributes.getNamedItem('targetNamespace');
    if Assigned(targetNamespaceNode) then
      namespaceURI := targetNamespaceNode.nodeValue
    else
      namespaceURI := NullAsStringValue;

    SchemaCollection.Add(namespaceURI, XsdDocument);
  end;

  XmlDocument.schemas := SchemaCollection;

  parseError := XmlDocument.validate();

  if Supports(parseError, IXMLDOMParseError2, parseError2) then
    ProcessParseError2(parseError2)
  else
    ProcessParseError(parseError);

  Result := not HaveValidationResult;
end;

initialization
  // http://chrisbensen.blogspot.com/2007/06/delphi-tips-and-tricks_26.html
  if Assigned(ComObj.CoInitializeEx) then
    ComObj.CoInitializeEx(nil, COINIT_MULTITHREADED)
  else
    CoInitialize(nil);
finalization
  CoUninitialize;
end.

Let me know with these samples

Have fun with the sources and let me know.

–jeroen

4 Responses to “Validate XML with XSD in .NET and native MSXML – big difference in string maxLength validation with newlines (samples in C# and Delphi)”

  1. François said

    Thanks Jeroen! Very interesting.
    Quite a coincidence with the latest coding Horror: http://www.codinghorror.com/blog/archives/001319.html

  2. Blaz said

    Thank you for the code!

    Components4Developers
    http://www.components4developers.com
    The best SOA, ESB and EAI components for the best developers.

Leave a Reply to François Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: