Страницы

Поиск по вопросам

среда, 3 апреля 2019 г.

c# Net core Поиск номера строки ошибки в файле XML, имея путь к тегу (cases XPath, Regex, JSON?)

Здравствуйте. Ситуация: есть модель(дисериализованный файл), довольно большая. В ней много строк и вложенных объектов. (Модель YML каталога).
[XmlRoot("yml_catalog")] public class YMLCatalog { ///

/// Дата и время генерации YML файла на стороне магазина /// [Required] [XmlAttribute("date")] public string Date { get; set; }
/// /// Магазин /// [Required] [ValidateObject] [XmlElement(ElementName = "shop")] public Shop Shop { get; set; } }
/// /// Точка продаж /// https://yandex.ru/support/webmaster/goods-prices/technical-requirements.html /// public class Shop { /// /// Название магазина. /// [Required] [XmlElement("name")] public string Name { get; set; }
/// /// Название компании /// [Required] [XmlElement("company")] public string Company { get; set; } ... и тд. }
При валидации модели, в случае ошибки, я получаю (при помощи магии. См. краткое описание в комментариях) путь к ошибке. Например для YML файла с ошибкой в теге price, 0го оффера, в виде отсутствия цены, я получаю путь: shop/offers/offer['0 или 1', смотря как удобнее. Для XPath лучше 1]/price Value: Текст ошибки. (Value - поле со значением тега, аналогично может быть для атрибутов)
Нас интересует: shop/offers/offer[0]/price
По этому пути, мне нужно определить номер строки, где находиться этот тег. Чтобы указать пользователю где ошибка.
Что я пробовал:
1) Сделал через регулярные выражения. Сначала последовательно нахожу индекс элементов по пути, как получу индекс начала тега Price в файле. Затем собираю список строк в файле и нахожу номер строки для вывода, относительно всего файла. - Это работает, но меня не устраивает. Слишком медленно, а у меня могут быть очень большие файлы
2) Попробовал сделать через XPath. Т.е например можно легко получить элемент тега вот так:
XmlDocument document = new XmlDocument(); document.LoadXml(text); var node = document.SelectSingleNode("//shop/offers/offer[1]/price");// это для примера
3) Есть вариант с созданием своего дессериализатора + атрибутов валидации для него, чтобы к каждому полю в модели приписывать в метаданных IXmlLineInfo - который в стандартном XmlSerializer к сожалению используется только при выдаче информации об ошибке - но это долго, а те готовые, что я нашел, не поддерживаются в net core... UPD: также в таком случае требуется поддержка не только составных объектов, но и простых + простых атрибутов (string, int, long и тп)
Вопрос 1: XPath как-нибудь позволяет получить кол-во элементов выше (или лучше их список) ? Вроде комбинировать preceding и ancestor нельзя (ссылка) - Но опять же, даже если это получиться, придется искать индекс, создавая список строк файла. - Не очень хорошо, хотелось бы узнать номер строки ошибки другим способом
Пробовал "//shop/offers/offer[ 1 ]/price/preceding::*", но в таком случае игнорируются все элементы выше shop, почему не знаю. (когда использовал count(...), получал 27 элементов) - Думаю это всяко лучше только регулярных выражений
Знаете еще способ? Прошу в ответы:)
Главный вопрос: Так как лучше всего найти номер строки ошибки, имея XML и путь к тегу?
P.S Производительность решения приветствуется)


Ответ

Не выдержал, решил дать свой ответ.
Во-первых, XmlSerializer выдаёт ошибку не только при ошибке синтаксиса XML (например, незакрытый тег), но и в том случае, если тип данных не соответствует ожидаемому (например, свойство Price имеет тип decimal, а в узле Price пусто или нечисловая строка). При этом в выбрасываемом исключении содержится сообщение, содержащее номер и позицию ошибки:
В документе XML (4, 18) присутствует ошибка.
На мой взгляд, это уже фактически решает вопрос. Строка с ошибкой известна, можно её исправлять.

Чуть погуглив, я легко выяснил, что для документов YML имеется схема. Скачать её можно здесь
При валидации по схеме будет выдаваться описание ошибки наподобие следующего:
Элемент "Price" недействителен: значение "" недействительно с точки зрения его типа данных "http://www.w3.org/2001/XMLSchema:decimal" - Строка "" не является допустимым значением Decimal.
Это даёт огромное удобство: пользователь легко поймёт и исправит ошибку.
Сама валидация делается в несколько строк:
var xs = new XmlSerializer(typeof(Test)); var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema };
var schemas = new XmlSchemaSet(); schemas.Add(null, "schema.xsd"); settings.Schemas = schemas; settings.ValidationEventHandler += Settings_ValidationEventHandler;
using (var xr = XmlReader.Create("test.xml", settings)) //while (xr.Read()) ; item = (Test)xs.Deserialize(xr);
private void Settings_ValidationEventHandler(object sender, ValidationEventArgs e) { Console.WriteLine(e.Severity); Console.WriteLine(e.Message); Console.WriteLine(e.Exception.LineNumber + " " + e.Exception.LinePosition); }
В данном коде валидация происходит в процессе чтения xml. То есть нет лишних проходов/загрузок, всё максимально эффективно по скорости и объёму памяти.
Если нужно только валидация без десериализации, то раскомментируйте строку с while, закомментировав следующую.

В том подходе, который используете вы сейчас, всё делается, имхо, очень неэффективно.
Десериализация - отдельно (проход по xml, потребление памяти). Валидация - отдельно (ещё проход, ещё память). Поиск строки с ошибкой с помощью XPath - ещё одна загрузка xml в XmlDocument/XDocument (опять полный парсинг xml, опять тратится время и память).
Кроме того, если xml синтаксически некорректен (незакрытый тег), то он вообще не загрузится в XmlDocument/XDocument и вы никогда не узнаете, где же ошибка...
Кроме того, вы вручную расставляете атрибуты валидации и пишете код валидации... Лишняя работа.

Комментариев нет:

Отправить комментарий