Валидация XML по схеме XSD - Как определить элемент, в котором находится ошибочный атрибут?
Пишу программу проверки файла XML на соответствие схемы XSD. Общий код:
private void buttonCheck_Click(object sender, EventArgs e)
{
string xmlFileName = tFileXML.Text; // поле ввода файла XML
string xsdFileName = tFileXSD.Text; // поле ввода файла XSD
tMessage.Text = ""; // поле сообщения об ошибке
// пробуем определить целевое пространство имен (targetNameSpace) из файла схемы
// и проверить, что оно соответствует значению атрибута xmlns элемента <Файл>
// (для файлов запросов targetNamespace не указан, так что "")
XmlDocument xsd = new XmlDocument();
try
{
xsd.Load(xsdFileName);
} catch (Exception ex)
{
tMessage.Text = "Ошибка в файле схемы " + xsdFileName+"\n"+ex.Message;
return;
}
string targetNamespace = xsd.DocumentElement.GetAttribute("targetNamespace");
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
settings.Schemas.Add(targetNamespace, xsdFileName);
settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
XmlReader reader = XmlReader.Create(xmlFileName, settings);
while (reader.Read()) { }
reader.Close();
if (tMessage.Text.Length == 0)
{
tMessage.AppendText("Успешно");
}
} // buttonCheck_Click
public void ValidationCallBack(object sender, ValidationEventArgs e)
{
IXmlLineInfo lineInfo = sender as IXmlLineInfo;
XmlReader r = (XmlReader)sender;
if (r.NodeType == XmlNodeType.Attribute)
{
// как здесь получить имя элемента, в котором находится ошибочный атрибут?
tMessage.AppendText(String.Format("Line {0} Column {1}: Ошибка: {2}\r\n\r\n", lineInfo.LineNumber, lineInfo.LinePosition,
e.Message));
}
else
{
tMessage.AppendText(String.Format("Line {0} Column {1}: Элемент <{2}>, Ошибка: {3}\r\n\r\n", lineInfo.LineNumber, lineInfo.LinePosition,
r.Name, e.Message));
}
}
Если есть проблема с атрибутом какого-то элемента, то как получить этот самый проблемный элемент, чтобы вывести подробное сообщение? Т.к. одноименные атрибуты могут быть в разных элементах, а бывают файлы XML в одну строку.
В процедуре ValidationCallBack() из XmlREader надо как-то получить элемент, на котором произошла ошибка и выдать его название. Но как?
Ответы (2 шт):
Просто сделайте ваш XML всегда отформатированным и с отступом (indented). Таким образом, значения в lineInfo.LineNumber, lineInfo.LinePosition будут очень полезны.
Остальное - зависит от парсера XML.
Один из вариантов:
if (r.NodeType == XmlNodeType.Attribute)
{
string attrName = r.Name;
r.MoveToElement(); // перемещаем читатель к элементу, в котором находится атрибут
string elemName = r.Name; // получаем имя элемента
tMessage.AppendText(...);
r.MoveToAttribute(attrName); // обязательно возвращаем ридер в прежнее состояние
}
Таким образом мы получим имя элемента. Но если узлов с таким именем в xml много, то хотелось бы получить полный путь (xpath) к нему.
Сохранять путь будем в стеке. Вводим дополнительное поле:
Stack<string> nodes;
Код чтения-валидации становится следующим:
using (var reader = XmlReader.Create(xmlFileName, settings))
{
nodes = new Stack<string>();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && !reader.IsEmptyElement)
nodes.Push(reader.Name);
else if (reader.NodeType == XmlNodeType.EndElement)
nodes.Pop();
}
}
То есть по мере чтения xml добавляем в стек имена узлов. При выходе из узла удаляем его из стека.
Изменяем обработчик события:
string xpath = string.Join("/", nodes.Reverse());
if (r.NodeType == XmlNodeType.Attribute)
{
string attrName = r.Name;
r.MoveToElement();
string elemName = r.Name;
string fullXpath = xpath + "/" + elemName;
tMessage.AppendText(...); // тут используем fullXpath
r.MoveToAttribute(attrName);
}