Страницы

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

воскресенье, 2 февраля 2020 г.

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

#c_sharp #регулярные_выражения #json #xpath #net_core


Здравствуйте. Ситуация: есть модель(дисериализованный файл), довольно большая. В
ней много строк и вложенных объектов. (Модель 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 Производительность решения приветствуется)
    


Ответы

Ответ 1



Не выдержал, решил дать свой ответ. Во-первых, 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 и вы никогда не узнаете, где же ошибка... Кроме того, вы вручную расставляете атрибуты валидации и пишете код валидации... Лишняя работа.

Ответ 2



а через LINQ to XML нельзя сделать? что-то вроде такого ... XDocument xdoc = XDocument.Load(file, LoadOptions.SetLineInfo); var node = xdoc.XPathSelectElement("//shop/offers/offer[1]/price"); var lineNumber = ((IXmlLineInfo)node).LineNumber;

Ответ 3



а нельзя добавить в сам цикл и обработку, индекс, который и будет указывать на номер тега. Прошу прощения, вы это сами уже предложили и отвергли. И все таки почти всегда есть главный цикл обработки, и передать место сбоя в виде текущего номера обрабатываемого элемента, вроде как можно, ведь оно будет равняться текущему элементу цикла. ParentNode возвращает родительский узел у текущего узла. Наверное можно обратиться к родителю, узнать количество смежных узлов, обратиться к родителю родителя и так далее. Можно посчитать количество переводов строки после закрывающихся тегов, не уверен что это вообще из этой оперы, но может помочь в идентификации нужного узла и места.

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

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