Привет, Хабр!
Обработка XML-данных остаётся актуальной задачей несмотря на появление более крутых технологий для работы с данными. XML — это все еще гибкий и мощный инструмент для структурирования данных, который используется в самых разнообразных приложениях.
В статье рассмотрим как работать с XML в C#.
Работа с XML в C#
Начнем с базы.
XmlDocument
— это класс, который позволяет работать с XML в доменной модели. Можно загружать, изменять и сохранять XML-документы. Читать XML можно через методыLoad
и LoadXml
, которые считывают данные из файлов или строк. Создание новых узлов и атрибутов происходит с помощью методов CreateElement
и CreateAttribute
, после чего элементы добавляются в документ с помощью AppendChild
.
Cоздадим XML-документ, добавим в него элементы и атрибуты, а затем сохраним его:
using System;
using System.Xml;
class Program
{
static void Main()
{
// новый XML-документ
XmlDocument doc = new XmlDocument();
// корневой элемент
XmlElement root = doc.CreateElement("users");
doc.AppendChild(root);
// новый элемент
XmlElement user = doc.CreateElement("user");
root.AppendChild(user);
// атрибут для элемента user
XmlAttribute attr = doc.CreateAttribute("id");
attr.Value = "1";
user.Attributes.Append(attr);
// вложенные элементы в элемент user
XmlElement firstName = doc.CreateElement("firstName");
firstName.InnerText = "John";
user.AppendChild(firstName);
XmlElement lastName = doc.CreateElement("lastName");
lastName.InnerText = "Doe";
user.AppendChild(lastName);
// сейвим XML-документ в файл
doc.Save("users.xml");
}
}
Но есть способы получше.
XmlReader и XmlWriter
XmlReader
и XmlWriter
предоставляют более производительные альтернативы для чтения и записи XML по сравнению с DOM, т.к они работают в потоковом режиме. XmlReader
читает XML поэлементно.
Пример XmlReader:
using System;
using System.Xml;
class Program
{
static void Main()
{
// создаем XmlReader для чтения файла
using (XmlReader reader = XmlReader.Create("example.xml"))
{
while (reader.Read()) // чтение некст элемента
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "name")
{
Console.WriteLine(reader.ReadElementContentAsString()); // читаем содержимое элемента <name>
}
}
}
}
}
Здесь юзаем XmlReader
для поэлементного чтения XML-файла. Фильтруем элементы по типу узла XmlNodeType.Element
и имени name
, чтобы извлечь информацию только из тех элементов, которые нас интересуют.
Пример XmlWriter:
using System;
using System.Xml;
class Program
{
static void Main()
{
// создаем XmlWriter
using (XmlWriter writer = XmlWriter.Create("output.xml"))
{
writer.WriteStartDocument(); // начало документа
writer.WriteStartElement("users"); // начало корневого элемента <users>
writer.WriteStartElement("user"); // начало элемента <user>
writer.WriteElementString("name", "Ivan"); //добавление <name>
writer.WriteEndElement(); // закрытие элемента <user>
writer.WriteEndElement(); // закрытие корневого элемента <users>
writer.WriteEndDocument(); // закрытие документа
}
}
}
Используем XmlWriter
для создания нового XML-файла с простой структурой. Стартуем с создания корневого элемента <users>
, добавляем в него дочерний элемент <user>
с вложенным элементом <name>
, и последовательно закрываем все открытые элементы и документ.
XDocument
Также существует XDocument
и связанные с ним классы XElement
, XAttribute
.
XElement
— это отдельный элемент в XML-документе и он позволяет управлять содержимым элемента, включая его вложенные элементы, текст и атрибуты.
XAttribute
служит для работы с атрибутами XML-элементов. Атрибуты представляют собой пары имя-значение, которые прикрепляются к элементам.
Cоздадим простой XML-документ, который содержит инфу о нескольких юзерах, каждый из которых имеет уникальный идентификатор и имя:
using System;
using System.Xml.Linq;
class Program
{
static void Main()
{
// новый XML-документа
XDocument xmlDoc = new XDocument(
new XDeclaration("1.0", "utf-8", null),
new XElement("users", // корневой элемент
new XElement("user", // дочерний элемент
new XAttribute("id", "1"), // атрибут элемента user
new XElement("name", "Ivan")
),
new XElement("user",
new XAttribute("id", "2"),
new XElement("name", "Kolya")
)
)
);
Console.WriteLine(xmlDoc.ToString());
// сохранение XML-документа в файл
xmlDoc.Save("users.xml");
}
}
XDocument
создаёт новый XML-документ.
XElement
используется для создания элементов users
и user
. Элемент users
служит корневым элементом, а user
— дочерним элементом, представляющим пользователя.
XAttribute
применяется для добавления атрибутов к элементу user
, в данном случае это идентификатор пользователя.
LINQ
Можно интегрировать LINQ с XML и сделать код более читабельным.
Например, так можно сделать чтение и запрос данных:
using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main()
{
XDocument doc = XDocument.Load("books.xml");
var books = from book in doc.Descendants("book")
where (int)book.Attribute("id") == 1
select new
{
Title = book.Element("title").Value,
Author = book.Element("author").Value
};
foreach (var book in books)
{
Console.WriteLine($"Title: {book.Title}, Author: {book.Author}");
}
}
}
А так сделать агрегацию данных из XML:
using System;
using System.Xml.Linq;
using System.Linq;
class Program
{
static void Main()
{
XDocument doc = XDocument.Load("books.xml");
var bookCount = doc.Descendants("book").Count(); // cчитаем количество книг
var maxId = doc.Descendants("book").Max(book => (int)book.Attribute("id")); // Находим максимальный id
Console.WriteLine($"Total books: {bookCount}, Max ID: {maxId}");
}
}
Десериализация XML и обработка исключений
Для начала десериализации нужно создать класс, структура которого соответствует структуре XML документа. Этот класс должен содержать поля или свойства, соответствующие элементам XML.
Создаем экземпляр XmlSerializer
, указав тип объекта, который нужно десериализовать:
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
Также используемXmlSerializer
для чтения XML из файла или потока и его преобразования в объект. Чаще всего используется StreamReader
или StringReader
:
using (StreamReader reader = new StreamReader("path_to_file.xml"))
{
MyClass myObject = (MyClass)serializer.Deserialize(reader);
}
При десериализации могут возникать различные исключения, например, InvalidOperationException
при несоответствии XML схемы ожидаемому классу или XmlException
при синтаксических ошибках в XML. Обрабатывать эти исключения можно с помощью блоков try-catch
:
try
{
using (StreamReader reader = new StreamReader("path_to_file.xml"))
{
MyClass myObject = (MyClass)serializer.Deserialize(reader);
}
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Invalid XML format: {ex.Message}");
}
catch (XmlException ex)
{
Console.WriteLine($"XML Parsing Error at line {ex.LineNumber}: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"General error: {ex.Message}");
}
Рассмотрим класс Person
и XML файл, который содержит данные о человеке:
<Person>
<Name>Ivan</Name>
<Age>30</Age>
</Person>
Соответствующий класс Person
в C# будет выглядеть так:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Десериализация этого XML в объект Person
с XmlSerializer
выглядит так:
string xmlData = @"<Person><Name>John Doe</Name><Age>30</Age></Person>";
using (StringReader stringReader = new StringReader(xmlData))
{
XmlSerializer serializer = new XmlSerializer(typeof(Person));
Person person = (Person)serializer.Deserialize(stringReader);
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
XML схемы и валидация в C#
XML схемы — это формальное описание структуры XML документа, в которым описаны все ограничения на содержимое и структуру элементов и атрибутов. Так можно автоматом проверять, что XML документы соответствуют заданным стандартам.
XML схема обычно определяет элементы и атрибуты, которые могут появляться в документе, их типы данных, и другие ограничения. Например, схема для описания книг в библиотеке может выглядеть так:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="library">
<xs:complexType>
<xs:sequence>
<xs:element name="book" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="title" type="xs:string"/>
<xs:element name="author" type="xs:string"/>
<xs:element name="isbn" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Для валидации XML документа в соответствии с XSD схемой, можно использовать классы XmlReader
и XmlReaderSettings
из пространства имен System.Xml.Schema
. Например:
using System;
using System.Xml;
using System.Xml.Schema;
class Program
{
static void Main()
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, "library.xsd");
settings.ValidationType = ValidationType.Schema;
XmlReader reader = XmlReader.Create("library.xml", settings);
XmlDocument document = new XmlDocument();
document.Load(reader);
ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationEventHandler);
document.Validate(eventHandler);
}
static void ValidationEventHandler(object sender, ValidationEventArgs e)
{
if (e.Severity == XmlSeverityType.Error)
{
Console.WriteLine("Error: {0}", e.Message);
}
else if (e.Severity == XmlSeverityType.Warning)
{
Console.WriteLine("Warning: {0}", e.Message);
}
}
}
Таким образом можно обеспечить, что XML документы точно соответствуют определенным структурам и правилам.
Современные приложения иногда потребляют очень много памяти. Приглашаем вас на бесплатный урок, на котором будут рассмотрены основные приемы эффективной работы и экономии памяти в современных .net приложениях, и как помогают в этом ArrayPool, Span.
Комментарии (16)
CorwinH
20.05.2024 06:19+4По-моему, тема не раскрыта. В чём разница между XmlDocument и XDocument, между XmlReader и SAX-парсерами и т.п.
SpiderEkb
Если правильно понимаю, то то, что является "потоковым" Xml.Reader работает по принципу XML-SAX. А есть ли в нем автоматическая обработка сложных структур, когда один и тот же тег может находиться внутри разных тегов. Например:
Т.е. теги Идентификатор и Наименование являются универсальными и относятся к тому тегу, внутри которого находятся (ТипСубъекта или ТипДокумента), являясь контекстно-зависимыми.
Есть возможность такие вещи автоматически распознавать (т.е. не reader.Name == "Идентификатор", но reader.Name == "ТипДокумента.Идентификатор"), или надо руками отслеживать внутри какого тега сейчас находимся?
mayorovp
Блин, да посмотрите вы на код из статьи внимательнее! Никакой это не SAX, это, ну, Reader.
Нет там никакой обработки структур вообще, только валидация структуры документа. Все тэги должны обрабатываться пользовательским кодом.
Второе, потому что
reader.Name
- это имя тэга и ничего кроме имени тэга. Однако, если использовать валидацию по схеме, то можно получить ссылку на текущий элемент и его тип в схеме.SpiderEkb
SAX (англ. «Simple API for XML») — способ последовательного чтения/записи XML-файлов.
Ну, собственно, о том и спрашивал. Т.е. никаких там наворотов поверх потокового чтения нет. Все максимально просто, быстро, минимум использования памяти, но много отдается на волю разработчика.
Это действительно удобно когда нужно просто извлечь из XML какие-то данные и потом с ними что-то делать.
Работал с таким (причем, XML были достаточно объемные - несколько десятков Мб). Правда, в той реализации, что у меня была под рукой, задавался callback handler, вызываемый "изнутри" с параметрами "код события" + "данные". Коды собятия там были типа "начало тега" (с именем тега в данных), "содержимое тега", "конец тега" и т.п. И да, для описанной выше ситуации приходилось выставлять флаги "внутри тега ТипСубъекта" или "внутри тега ТипДокумента" чтобы правильно обрабатывать теги Идентификатор и ТипДокумента.
Спросил потому что думал вдруг придумали что-то более умное, типа "полного имени тега" (какое-нибудь свойство fullname) для вложенных тегов.
mayorovp
Это не любой способ, а конкретный способ с конкретным подходом к построению API. XmlReader не реализует этот подход.
Этот способ и называется SAX...
Ну, кое-что придумано, только в другом направлении. В отличии от SAX, XmlReader не требует единого места обработки абсолютно всех элементов. Никто не мешает делать вложенные циклы, и вообще структурировать программу по всем правилам структурного программирования:
Примерный код:
Кстати, для SAX я видел другой подход, не через флаги. Там создавался делегирующий обработчик со стеком вложенных обработчиков, каждый из которых отвечал за конкретный элемент. То есть у вас есть обработчик ТипСубъекта, который получает события только для дочерних элементов ТипСубъекта, обработчик ТипДокумента, который получает события только для дочерних элементов ТипДокумента и корневой обработчик, который получает события уровнем выше и кладёт в стек первые два при необходимости.
SpiderEkb
Подобный подход тоже использовал.
Но в SAX все равно передается что-то одно - обработчик верхнего уровня. Ну а дальше он уже должен решать по текущему состоянию - куда отдавать дальнейшую обработку элемента. Т.е. там все равно какие-то флаги нужны.
В моем понимании SAX - это общий принцип. Не построение всего дерева документа в памяти (как в DOM), а потоковое чтение и действия на каждый прочитанный элемент. В том SAX с которым я работал те циклы, что указаны в reader просто спрятаны внутрь. Не берусь судить что удобнее - надо пробовать под конкретную задачу.
А для простейших одноуровневых XML типа
В используемом мной языке вообще есть XML-INTO - там просто создаешь структуру с именами полей, совпадающими с именами тегов, дергаешь XML-INTO и сразу получаешь заполненную структуру.
Минус - с русскими тегами не работает :-(
mayorovp
Потоковое чтение называется "потоковое чтение", ему не требуется специальное название.
То-то и оно, что они не "просто спрятаны", а фундаментально спрятаны. Архитектура SAX и XmlReader принципиально различаются, как и способы их использования, возникающие при этом проблемы и способы их решения.
Это называется "десериализация", в C# ей занимается XmlSerializer. С русскими тэгами без проблем работает. Можно комбинировать с потоковым чтением при необходимости (верхний уровень читается потоково, а нижний десериализуется).
SpiderEkb
Я привел ссылку на определение SAX как способ потокового чтения XML файлов. Именно способ, на конкретную реализацию, которых может быть много.
Событийная реализация просто наиболее общепринятая. Никто не мешает реализовать эту же модель аналогично последовательному чтению записей БД с проверкой содержимого полей очередной прочитанной записи.
Впрочем, это все вопрос терминологии.
mayorovp
Если походить по страницам по вашей ссылке, то можно найти и такое: "Росси́я, или Росси́йская Федера́ция (сокр. РФ), — государство в Восточной Европе и Северной Азии.". Означает ли это, что любое государство в Восточной Европе - это Россия?
SAX - это имя собственное, и оно означает один конкретный набор интерфейсов в Java и ничего более.
К слову, гляньте один из источников по вашей же ссылке: http://www.saxproject.org/
rukhi7
тогда вам не ридер нужен, а язык запросов, например XPath (XML Path Language)
Ридер просто загружает XML из файла в память, есть два основных способа, загрузить и отдать пользователю полностью дерево XML объектов (узлов, элементов, ... как хотите называйте)
или отдавать XML объекты по мере чтения.
Поскольку поиск все равно должен читать XML, сначала, а потом еще проверить условие запроса, и вернуть пользователю только то что соответствует вашему запросу, поиск также в основе своей использует два этих метода чтения.
Через XPath можно запросить, то есть найти, все узлы которые имеют
Идентификатор
как дочерний узел, можно найти все узлы путь к которым заканчивается на "ТипДокумента.Идентификатор"Вам только нужно решить что вы хотите видеть в ответ на ваш запрос. Проблема в том что там всегда есть очень много вариантов построения запросов к XML, поэтому правильное их построение это особое исскусство, которое подвластно очень не многим.
SpiderEkb
Не совсем так. Мне вполне хватило чтобы ридер знал "полное имя тега". Т.е. для указанного выше случая (как уже написал выше) можно было смотреть как свойство name - например, "Идентификатор", так и свойство fullname (или qualifiedname - как угодно), например, "ТипДокумента.Идентификатор". Ну или свойство path - "ТипДокумента"
С точки зрения реализации это не является чем-то невозможным - ридер идет по документу последовательно и вполне способен "накапливать" последовательность имен тегов, внутри которой находится в текущий момент.
И да, все это легко реализуется руками, но было бы чуть удобнее, если бы оно уже было "из коробки". Но раз нет - говорить не о чем.
rukhi7
XML парсер существует (даже с С++ интерфейсом) и успешно (хотя не всегда конечно) используется уже более 20 лет, и интерфейс к нему существует столько же. Вы считаете что за 20 лет никто бы не догадался сделать что-то лучше, если оно действительно лучше?
Потом что значит "чуть удобнее"? Так можно дойти до того, что было бы даже намного удобнее если бы всю работу уже кто-то сделал за вас, только возникает тогда вопрос, а вы то зачем тогда нужны как технический специалист, коробки переставлять? Никогда не понимал такого отношения!
tuxi
XPath это уже про DOM модель парсера. На паре гигибайт данных это такое себе удовольствие.
SAX парсер безальтернативен на больших данных, и да, там придется флажки ставить "нода открылась, нода закрылась".
mayorovp
XmlReader - вполне себе альтернатива SAX парсеру. Но да, XPath он тоже не умеет.
Кстати, на Java тот же Saxon умудряется ограниченно комбинировать XPath и потоковый разбор, однако я не вижу способа дёшево "достать" из него этот механизм.
SpiderEkb
Фактически - тот же SAX, просто в другой реализации.
Два подхода - DOM с построением всего дерева в памяти и SAX потоковым чтением. Как все это реализовать к конкретике - тут уже возможны варианты.
tuxi
Именно так, это просто SAX с красивой оберткой. Полноценный XPath возможен только после полного построения дерева элементов. А это однозначно DOM парсер.
И еще комментарий: использовать DOM без четкого понимания (контракта с внешней системой) объемов данных - это стрелять себе в ноги крупной дробью. SAX при первоначальной бОльшей трудности реализации, делает систему гораздо более устойчивой и прогнозируемой. Надо только способ получения потока данных реализовывать не полным чтением в память.