Показаны сообщения с ярлыком XmlReader. Показать все сообщения
Показаны сообщения с ярлыком XmlReader. Показать все сообщения

Сложная валидация XML

среда, 15 апреля 2009 г.,

Если во время валидации xml требуется выполнять какие-то вычисления, запрашивать данные из БД, фиксировать Xpath к некорректному тегу и т.д. ... Решение:
XmlElementReader reader = new XmlElementReader();
reader.ProcessElement += delegate
{
    // для текущего элемента получить XPath и ...
    Trace.WriteLine(reader.GetXPath());
};
reader.Read("test.xml");

Код XmlElementReader'а приводится ниже. Работает он следующим образом: с помощью XmlReader'а читает теги, их атрибуты, а также использует стек для фиксации пути к текущему тегу. После это посылает событие ProcessElement.
public class XmlElementReader
{
    public event EventHandler ProcessElement = delegate { };

    public XmlElementReader()
    {
        _Stack = new Stack<TagInfo>();
    }
    private Stack<TagInfo> _Stack;

    public void Read(string xmlPath)
    {
        XmlReader r = XmlReader.Create(xmlPath);
        while (r.Read())
        {
            if (r.NodeType == XmlNodeType.Element)
                this.ReadElement(r);
        }
    }

    public TagInfo[] GetPath()
    {
        return _Stack.ToArray().Reverse().ToArray();
    }

    [DebuggerDisplay("Name={Name}, Depth={Depth}, Index={Index}")]
    public class TagInfo
    {
        public string Name { get; set; }
        public int Index { get; set; }
        public int Depth { get; set; }
        public Dictionary<string, string> Attributes { get; set; }
    }

    private void ReadElement(XmlReader xr)
    {
        TagInfo stackTop = _Stack.Count > 0 ? _Stack.Peek() : null;
        if (stackTop == null || xr.Depth > stackTop.Depth)
        {
            _Stack.Push(new TagInfo() { Name = xr.Name, Index = 0, Depth = xr.Depth, Attributes = xr.ReadAttributes() });
            this.ProcessElement(this, EventArgs.Empty);
            return;
        }

        if (xr.Depth < stackTop.Depth)
        {
            _Stack.Pop();
            this.ReadElement(xr);    // рекурсия
            return;
        }

        stackTop.Index++;
        stackTop.Name = xr.Name;
        stackTop.Attributes = xr.ReadAttributes();
        this.ProcessElement(this, EventArgs.Empty);
    }
}

public static class XmlReaderHelper
{
    public static Dictionary<string, string> ReadAttributes(this XmlReader reader)
    {
        Dictionary<string, string> ret = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        if (reader.HasAttributes)
        {
            while (reader.MoveToNextAttribute())
                ret.Add(reader.Name, reader.Value);

            reader.MoveToElement();
        }
        return ret;
    }
}

public static class XmlElementReaderHelper
{
    public static string GetXPath(this XmlElementReader reader)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("/");
        foreach (var ti in reader.GetPath())
        {
            sb.Append(ti.Name);
            string id;
            if (ti.Attributes.TryGetValue("id", out id))
                sb.Append("[@id='").Append(ti.Attributes["id"]).Append("']");

            sb.Append("/");
        }
        return sb.ToString().TrimEnd('/');
    }
}

Валидация XML


Например, есть следующий XML:
<?xml version="1.0" encoding="utf-8" ?>
<root>
  <item id="0" />
  <item id="1">
    <item id="0" />
  </item>
  <item id="0" />
</root>
Требуется выполнить валидацию XML, например, проверить уникальность значений атрибутов id у вложенных (nested) тегов item. Для это надо определить схему xml в виде .xsd-файла:
<?xml version="1.0" standalone="yes"?>
<xs:schema id="root" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <!-- корневым тегом может быть root -->
  <xs:element name="root" msdata:IsDataSet="true" msdata:Locale="en-US">
    <xs:complexType>
      <!-- root может содержать неограниченное кол-во item -->
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element ref="item" />
      </xs:choice>
    </xs:complexType>
    <!-- правило для атрибута id в item -->
    <xs:unique name="uid" msdata:PrimaryKey="true">
      <xs:selector xpath=".//item" />
      <xs:field xpath="@id" />
    </xs:unique>
  </xs:element>
  <!-- определение тега item -->
  <xs:element name="item">
    <xs:complexType>
      <xs:sequence>
        <!-- может содержать неограниченное кол-во подтегов item -->
        <xs:element ref="item" minOccurs="0" maxOccurs="unbounded" />
      </xs:sequence>
      <!-- определение атрибута id -->
      <xs:attribute name="id" type="xs:string" />
    </xs:complexType>
  </xs:element>
</xs:schema>

Валидация выполняется с помощью следующего кода:
XmlReaderSettings xs = new XmlReaderSettings();
xs.Schemas.Add("", new XmlTextReader(@"test.xsd"));
xs.ValidationType = ValidationType.Schema;
xs.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings | XmlSchemaValidationFlags.ProcessIdentityConstraints;
xs.ValidationEventHandler += (s, e) =>
    System.Diagnostics.Trace.WriteLine(e.Exception.LineNumber + " " + e.Message);

XmlReader xr = XmlReader.Create("test.xml", xs);
while (xr.Read()) ;

Информация обо всех ошибках передается в обработчик события ValidationEventHandler.
Для приведенного выше xml получим следующие сообщения:
5 There is a duplicate key sequence '0' for the 'uid' key or unique identity constraint.
7 There is a duplicate key sequence '0' for the 'uid' key or unique identity constraint.
(5 и 7 - номера строк в xml)