Как получить имя элемента и его родителя при обнаружении ошибок и несоответствия xsd-схеме в xml файле?

115
21 декабря 2020, 19:10

Есть код для проверки XML файла по XSD-схеме:

public static void ValidateAgainstSchema(string XMLSourceDocument, XmlSchemaSet validatingSchemas)
    {
        if (validatingSchemas == null)
        {
            throw new ArgumentNullException("In ValidateAgainstSchema: No schema loaded.");
        }
        string errorHolder = string.Empty;
        ValidationHandler handler = new ValidationHandler();
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.CloseInput = true;
        settings.ValidationType = ValidationType.Schema;
        settings.ValidationEventHandler += new ValidationEventHandler(handler.HandleValidationError);
        settings.Schemas.Add(validatingSchemas);
        settings.ValidationFlags =
            XmlSchemaValidationFlags.ReportValidationWarnings |
            XmlSchemaValidationFlags.ProcessIdentityConstraints |
            XmlSchemaValidationFlags.ProcessInlineSchema |
            XmlSchemaValidationFlags.ProcessSchemaLocation;
        StringReader srStringReader = new StringReader(XMLSourceDocument);
        XmlReader validatingReader = XmlReader.Create(srStringReader, settings);
        try
        {
            while (validatingReader.Read())
            {
            }
        }
        catch (XmlException ex)
        {
            Console.WriteLine("XMLException occurred: " + ex.Message);
        }

        if (handler.MyValidationErrors.Count > 0)
        {
            foreach (String messageItem in handler.MyValidationErrors)
            {
                errorHolder += messageItem;
            }
            //throw new XmlSchemaValidationException(errorHolder);
        }
        Console.WriteLine(errorHolder);}

public class ValidationHandler
{
    private IList<string> myValidationErrors = new List<String>();
    public IList<string> MyValidationErrors { get { return this.myValidationErrors; } }
    public void HandleValidationError(object sender, ValidationEventArgs ve)
    {
        if (ve.Severity == XmlSeverityType.Error || ve.Severity == XmlSeverityType.Warning)
        {
            this.myValidationErrors.Add(
                String.Format(
                    Environment.NewLine + "Line: {0}, Position {1}: \"{2}\"",
                    ve.Exception.LineNumber,
                    ve.Exception.LinePosition,
                    ve.Exception.Message)
            );
        }
    }
}

который выводит слудующую инфу об ошибках в xml-файле:

Line: 4, Position 20: "Элемент "foo" имеет недопустимый дочерний элемент "baz". Список ожидаемых элементов: "bar"."

Line: 13, Position 20: "Элемент "foo" имеет недопустимый дочерний элемент "baz". Список ожидаемых элементов: "bar"."

Как видно, благодаря свойствам LineNumber, LinePosition можно получить инфу о позиции каждой ошибки, но как получить имя элемента xml, в котором обнаружена ошибка, а также имя его родителя?

Answer 1

Чтобы было легче воспринимать, я упростил код до следующего:

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
namespace ConApp1
{
    class Program
    {
        static Stack<string> stack = new Stack<string>();
        static string previous;
        static int depth;
        static void Main(string[] args)
        {
            try
            {
                var schemaSet = new XmlSchemaSet();
                schemaSet.Add("", "test.xsd");
                var settings = new XmlReaderSettings();
                settings.ValidationType = ValidationType.Schema;
                settings.ValidationEventHandler += Settings_ValidationEventHandler;
                settings.Schemas.Add(schemaSet);
                using (var reader = XmlReader.Create("test.xml", settings))
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType != XmlNodeType.Whitespace)
                        {
                            previous = reader.Name;
                        }
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            if (depth == reader.Depth)
                            {
                                stack.Push(reader.Name);
                                depth++;
                            }
                        }
                        else if (reader.NodeType == XmlNodeType.EndElement)
                        {
                            stack.Pop();
                            depth--;
                        }
                    }
                }
            }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }
        private static void Settings_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            Console.WriteLine(string.Join(", ", stack));
            Console.WriteLine(previous);
            Console.WriteLine("Line: {0}, Position {1}: \"{2}\"",
                    e.Exception.LineNumber, e.Exception.LinePosition, e.Exception.Message);
        }
    }
}

Здесь в поле previous запоминается имя элемента, непосредственно предшествующего тому, в котором произошла ошибка валидации.

А в стеке хранятся все родительские элементы, от текущего до корня.

Аналогичным образом можно сохранять в коллекции любую необходимую информацию.

Для такого xml:

<?xml version="1.0"?>
<root>
  <foo>
    <bar/>
    <bar/>
  </foo>
  <foo>
    <bar/>
    <bar/>
  </foo>
</root>

была создана такая схема:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" name="foo">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="bar" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Если теперь внести ошибку: заменить последний элемент bar на baz, получим следующий вывод:

bar, foo, root
bar

То есть мы имеем все родительские элементы и предыдущий элемент.

READ ALSO
Как сделать, чтобы в динамическом списке имя объекту в списке давал пользователь?

Как сделать, чтобы в динамическом списке имя объекту в списке давал пользователь?

Есть динамический список, который добавляет объекты по их idИмя так же

110
Как настроить удаленное подключение к mysql?

Как настроить удаленное подключение к mysql?

Не получается подключиться удаленно к mysql серверу

101
Получение json из бд

Получение json из бд

В бд хранится данные в формате json, вот так выглядят {"0,2,,,,,,,,,,,,,,": ""} Мне нужно получить их и записать в массив js

146