Pull to refresh

Конструируем XML с использованием LINQ to XML API

Reading time7 min
Views9.5K
До недавнего времени мне ужасно не нравилось работать с XML файлами, я старался избегать этого где только можно, по просту заменяя их стандартными конфигурационными файлами приложения. Но где нужно было использовать обмен данными – от XML’а было не уйти. Приходилось создавать большой объем кода для того чтобы сконвертировать либо прочитать небольшой объем информации. Да, таков уж XML Document Object Model (DOM) API.

Пример использования XML DOM API


Необходимо получить такое сообщение:
<Groups>
  <Group Id="523063B0-47C0-4ABE-8D38-4A449137A00B" DepartmentId="D939743F-BFF7-41E4-9A1B-1193EEFF0486">
    <Name>555A</Name>
    <Course>5</Course>
  </Group>
</Groups>


Для этого потребуется написать довольно простой, но объемный код:
private sealed class Nodes
{
  public const string Root = "Groups";

  // Attributes.
  public const string Group = "Group";
  public const string Id = "Id";
  public const string DepartmentId = "DepartmentId";

  // Elements.
  public const string Name = "Name";
  public const string Course = "Course";
}

// Create XML document.
XmlDocument document = new XmlDocument();

// Create root element.
XmlElement rootElement = document.CreateElement(Nodes.Root);

// Create Group element.
XmlElement groupElement = document.CreateElement(Nodes.Group);
      
// Create attributes.
XmlAttribute idAttribute = document.CreateAttribute(Nodes.Id);
idAttribute.InnerText = "523063B0-47C0-4ABE-8D38-4A449137A00B";
groupElement.Attributes.Append(idAttribute);

XmlAttribute departmentIdAttribute = document.CreateAttribute(Nodes.DepartmentId);
departmentIdAttribute.InnerText = "D939743F-BFF7-41E4-9A1B-1193EEFF0486";
groupElement.Attributes.Append(departmentIdAttribute);
      
// Create elements.
XmlElement nameElement = document.CreateElement(Nodes.Name);
nameElement.InnerText = "555A";
groupElement.AppendChild(nameElement);

XmlElement courseElement = document.CreateElement(Nodes.Course);
courseElement.InnerText = "5";
groupElement.AppendChild(courseElement);

rootElement.AppendChild(groupElement);
document.AppendChild(rootElement);


Согласитесь со мной, что писать, разбирать и сопровождать такой код — не очень приятно.

По данному коду невозможно получить представления о результирующей структуре XML. С каждым элементом или атрибутом необходимо всегда выполнить три шага: создать, задать свойство InnerText и добавить к уже существующему элементу или документу. Так же, без создания XmlDocument’а невозможно просто создать элемент, а ведь это необходимо достаточно часто.

Благо компания Microsoft подарила нам не просто возможность выполнения интегрированных запросов LINQ to XML, но и переработала XML DOM API.
Об этом я и хочу сегодня поведать подробнее. Для этого понадобиться подключить следующие пространства имен:
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;


Как я уже говорил, одним из недостатков стандартной DOM модели было трудночитаемая структура получаемого XML файла. LINQ to XML API решает эту проблему, предлагая новую технику, называемую функциональным конструированием. Функциональное конструирование позволяет конструировать объекты XML и инициализировать их значения — одновременно, в единственном операторе, за счет множества перегруженных конструкторов.

Давайте посмотрим как будет выглядеть конструирование уже известного нам XML файла при помощи LINQ to XML API:
XElement document = new XElement(Nodes.Root,
  new XElement(Nodes.Group,
    new XAttribute(Nodes.Id, "523063B0-47C0-4ABE-8D38-4A449137A00B"),
    new XAttribute(Nodes.DepartmentId, "D939743F-BFF7-41E4-9A1B-1193EEFF0486"),
    new XElement(Nodes.Name, "555A"),
    new XElement(Nodes.Course, 5)));

Ну как? Какой код вам легче читать? Ответ очевидный.

Объектная модель





Классы XObject, XNode и XContainer – являются абстрактными, так что создавать мы их не можем.
Класс XAttribute – не наследуется от XNode, т.е. не является узлом, это простой элемент коллекции, который представляет пару «ключ-значение».
Классы XDocument и XElement – единственные, которые могут содержать узлы, унаследованные от XNode.

XDocument


В LINQ to XML API совсем не обязательно создавать XDocument для того чтобы создать дерево XML или его фрагмент.
XDocument document = new XDocument();
Console.WriteLine(document.ToString());

Получим пустой результат.

Сам по себе XDocument не имеет ничего полезного, только возможность добавить к документу объявление (XDeclaration), создание типа (XDocumentType) либо инструкции обработки (XProcessingInstruction).

Имеет статические методы для чтения содержимого документов, с всевозможными вариантами перегрузок:
public static XDocument Load(Stream stream);
public static XDocument Load(string uri);
public static XDocument Load(TextReader textReader);
public static XDocument Load(XmlReader reader);
public static XDocument Load(Stream stream, LoadOptions options);
public static XDocument Load(string uri, LoadOptions options);
public static XDocument Load(TextReader textReader, LoadOptions options);
public static XDocument Load(XmlReader reader, LoadOptions options);

А так же для сохранения документа:
public void Save(Stream stream);
public void Save(string fileName);
public void Save(TextWriter textWriter);
public void Save(XmlWriter writer);
public void Save(Stream stream, SaveOptions options);
public void Save(string fileName, SaveOptions options);
public void Save(TextWriter textWriter, SaveOptions options);

Такие же методы имеет и элемент (XElement).

Я использую XDocument только в том случае, если необходимо задать объявление для документа, которое содержит номер версии и кодировку.

XDeclaration


Объекты XDeclaration, как вы уже наверное догадались, содержат объявления и добавляются к документу, хотя компилятор допускает добавление объявления к элементу (XElement), но это не даст никакого видимого результата.
XDocument document = new XDocument(
  new XDeclaration("1.0", "UTF-8", "yes"));
Console.WriteLine(document.ToString());

Данный код так же не выведет никаких строк, хотя если посмотреть отладчик, объявление все-таки добавилось:



Объявление будет видно, если использовать метод Save(), класса XDocument.

XElement


Наиболее часто используемый объект API.
Имеет множество конструкторов, рассмотрим два из них:

public XElement(XName name, object content);
public XElement(XName name, params object[] content);

Первый конструктор – простейший случай, когда элемент не имеет дочерних элементов и мы задаем только его значение, второй конструктор – с заданием дочерних элементов, таких как элементы, атрибуты, инструкции обработки, комментарии (XComment) и т.д.

Создадим простейший элемент:
XElement element = new XElement("Element", 777);
Console.WriteLine(element.ToString());
Console.WriteLine((int)element);

Результат:
<Element>777</Element>
777

Как видите доступ к значению стал еще проще благодаря переопределенному явному приведению элементов, достаточно привести элемент к его исходному типу, для того чтобы посмотреть его значение. XElement переопределяет явное приведение для всех простейших типов. Теперь даже к свойству Value обращаться нет необходимости.
Вы спросите, конструктор требует XName, а я передаю ему строку? И я вам отвечу: строка неявно приводится к объекту XName. По сути, XName – это и есть строка, только с возможностью задания пространства имен (XNamespace).

Приведу более детальный пример с созданием XML из объектов:
public sealed class Teacher
{
  public Guid Id;
  public string FirstName;
  public string LastName;
}

IEnumerable<Teacher> teachers = new[]
{
  new Teacher() { Id = Guid.NewGuid(), FirstName = "Василий", LastName = "Иванович" },
  new Teacher() { Id = Guid.NewGuid(), FirstName = "Мария", LastName = "Николаевна" }
};

XElement elements = new XElement("Teachers",
  teachers.Select(t => new XElement("Teacher",
    new XAttribute("Id", t.Id),
    new XElement("FirstName", t.FirstName),
    new XElement("LastName", t.LastName))));

Console.WriteLine(elements.ToString());

В итоге имеем:
<Teachers>
  <Teacher Id="80321d66-ee4b-4b55-ace8-3892b682d483">
    <FirstName>Василий</FirstName>
    <LastName>Иванович</LastName>
  </Teacher>
  <Teacher Id="33f58b19-1bbb-468f-acee-961edb63ce0b">
    <FirstName>Мария</FirstName>
    <LastName>Николаевна</LastName>
  </Teacher>
</Teachers>

XAttribute


Как я уже и говорил ранее, является парой «ключ-значение» для коллекции атрибутов элемента. Создание и добавление атрибутов к элементу достаточно простая задача:
XAttribute attribute = new XAttribute("Id", Guid.NewGuid());
XElement element = new XElement("Teachers");
element.Add(attribute);
Console.WriteLine(element.ToString());

Либо:
XElement element = new XElement("Teachers",
  new XAttribute("Id", Guid.NewGuid()));
Console.WriteLine(element.ToString());

Результат один и тот же:
<Teachers Id="713c6cbf-f698-42fd-b5fc-74415b93d824" />


XComment


Создание и добавление комментариев так же не вызывает никаких сложностей:
XElement teacherElement = new XElement("Teacher",
  new XComment("Teacher element"));
Console.WriteLine(teacherElement.ToString());

Результат:
<Teacher>
  <!--Teacher element-->
</Teacher>


Заключение


На данном этапе я хотел бы закончить. Да, я не рассмотрел на примерах использование инструкций обработки, создание типов документа и другие классы модели, но данных знаний будет достаточно для создания как простых так и сложных по структуре XML документов.

В следующей статье я планирую рассмотреть проход по содержимому XML документа.
Tags:
Hubs:
+21
Comments31

Articles

Change theme settings