MyTetra Share
Делитесь знаниями!
Работаем с LINQ to XML
Время создания: 09.11.2019 20:29
Текстовые метки: LINQ, XML, XDocument, XDocument, XElement, new XAttribute
Раздел: Компьютер - C# - XML
Запись: Kozlov-AE/Tetra/master/base/1573320591xfnrrss8jw/text.html на raw.githubusercontent.com

Создадим базу для ведения каталога аудиозаписей. База будет состоять из треков:

  • Код
  • Название
  • Исполнитель
  • Альбом
  • Продолжительность
  • Жанр

Мы научимся добавлять, редактировать, удалять и делать различные выборки из нашей базы. Для начала создадим консольное приложение (я пишу свои проекты на C#, но суть в общем-то понятна будет всем) и подключим необходимое пространство имен

using System.Xml.Linq;

Создание файлов XML

Создадим XML файл нашей базы содержащий несколько тестовых записей уже при помощи LINQ:

//задаем путь к нашему рабочему файлу XML

string fileName = "base.xml";


//счетчик для номера композиции

int trackId = 1;

//Создание вложенными конструкторами.

XDocument doc = new XDocument(

new XElement("library",

new XElement("track",

new XAttribute("id", trackId++),

new XAttribute("genre", "Rap"),

new XAttribute("time", "3:24"),

new XElement("name", "Who We Be RMX (feat. 2Pac)"),

new XElement("artist", "DMX"),

new XElement("album", "The Dogz Mixtape: Who's Next?!")),

new XElement("track",

new XAttribute("id", trackId++),

new XAttribute("genre", "Rap"),

new XAttribute("time", "5:06"),

new XElement("name", "Angel (ft. Regina Bell)"),

new XElement("artist", "DMX"),

new XElement("album", "...And Then There Was X")),

new XElement("track",

new XAttribute("id", trackId++),

new XAttribute("genre", "Break Beat"),

new XAttribute("time", "6:16"),

new XElement("name", "Dreaming Your Dreams"),

new XElement("artist", "Hybrid"),

new XElement("album", "Wide Angle")),

new XElement("track",

new XAttribute("id", trackId++),

new XAttribute("genre", "Break Beat"),

new XAttribute("time", "9:38"),

new XElement("name", "Finished Symphony"),

new XElement("artist", "Hybrid"),

new XElement("album", "Wide Angle"))));

//сохраняем наш документ

doc.Save(fileName);

Теперь в папке с нашей программой после запуска появится XML файл следующего содержания:

<?xml version="1.0" encoding="utf-8"?>

<library>

<track id="1" genre="Rap" time="3:24">

<name>Who We Be RMX (feat. 2Pac)</name>

<artist>DMX</artist>

<album>The Dogz Mixtape: Who's Next?!</album>

</track>

<track id="2" genre="Rap" time="5:06">

<name>Angel (ft. Regina Bell)</name>

<artist>DMX</artist>

<album>...And Then There Was X</album>

</track>

<track id="3" genre="Break Beat" time="6:16">

<name>Dreaming Your Dreams</name>

<artist>Hybrid</artist>

<album>Wide Angle</album>

</track>

<track id="4" genre="Break Beat" time="9:38">

<name>Finished Symphony</name>

<artist>Hybrid</artist>

<album>Wide Angle</album>

</track>

</library>

Для создания подобного файла средствами XmlDocument кода понадобилось где-то раза в 2 больше. В коде выше мы воспользовались конструктором класса XDocument, который принимает в качестве параметра перечень дочерних элементов, которыми мы изначально хотим инициализировать документ. Используемый конструктор XElement принимает в качестве параметра имя элемента, который мы создаем, а так же перечень инициализирующих элементов. Удобно то, что мы в этих элементах можем задавать как новые XElement, так и XAttribute. Последние отрендретятся в наш файл как атрибуты самостоятельно. Если вам не нравится использоваться такую вложенность конструкторов и вы считаете такой код громоздким, то можно переписать в более традиционный вариант. Код ниже даст на выходе аналогичный XML файл:

XDocument doc = new XDocument();

XElement library = new XElement("library");

doc.Add(library);


//создаем элемент "track"

XElement track = new XElement("track");

//добавляем необходимые атрибуты

track.Add(new XAttribute("id", 1));

track.Add(new XAttribute("genre", "Rap"));

track.Add(new XAttribute("time", "3:24"));


//создаем элемент "name"

XElement name = new XElement("name");

name.Value = "Who We Be RMX (feat. 2Pac)";

track.Add(name);


//создаем элемент "artist"

XElement artist = new XElement("artist");

artist.Value = "DMX";

track.Add(artist);


//Для разнообразия распарсим элемент "album"

string albumData = "<album>The Dogz Mixtape: Who's Next?!</album>";

XElement album = XElement.Parse(albumData);

track.Add(album);

doc.Root.Add(track);


/*

*остальные элементы добавляем по аналогии

*/


//сохраняем наш документ

doc.Save(fileName);

Естественно выбирать необходимый способ нужно по ситуации.

Чтение данных из файла

Сейчас попробуем просто прочитать данные из уже полученного файла и вывести их в удобном для восприятия виде в консоль:

//задаем путь к нашему рабочему файлу XML

string fileName = "base.xml";

//читаем данные из файла

XDocument doc = XDocument.Load(fileName);

//проходим по каждому элементу в найшей library

//(этот элемент сразу доступен через свойство doc.Root)

foreach (XElement el in doc.Root.Elements())

{

//Выводим имя элемента и значение аттрибута id

Console.WriteLine("{0} {1}", el.Name, el.Attribute("id").Value);

Console.WriteLine(" Attributes:");

//выводим в цикле все аттрибуты, заодно смотрим как они себя преобразуют в строку

foreach (XAttribute attr in el.Attributes())

Console.WriteLine(" {0}", attr);

Console.WriteLine(" Elements:");

//выводим в цикле названия всех дочерних элементов и их значения

foreach (XElement element in el.Elements())

Console.WriteLine(" {0}: {1}", element.Name, element.Value);

}

Здесь в коде, думаю, ничего сложного нету и приведены комментарии. После запуска нашей программый в консоли отобразится следующий результат:

track 1

Attributes:

id="1"

genre="Rap"

time="3:24"

Elements:

name: Who We Be RMX (feat. 2Pac)

artist: DMX

album: The Dogz Mixtape: Who's Next?!

track 2

Attributes:

id="2"

genre="Rap"

time="5:06"

Elements:

name: Angel (ft. Regina Bell)

artist: DMX

album: ...And Then There Was X

track 3

Attributes:

id="3"

genre="Break Beat"

time="6:16"

Elements:

name: Dreaming Your Dreams

artist: Hybrid

album: Wide Angle

track 4

Attributes:

id="4"

genre="Break Beat"

time="9:38"

Elements:

name: Finished Symphony

artist: Hybrid

album: Wide Angle

Изменение данных

Попробуем пройтись по всем узлам library и увеличить аттрибут Id элемента track на 1. (дальше писать объявление пути к файлу и результат вывода в консоль я приводить не буду, чтобы не перегружать лишней информацией статью, все компилировал, все работает:) ):

//Получаем первый дочерний узел из library

XNode node = doc.Root.FirstNode;

while (node != null)

{

//проверяем, что текущий узел - это элемент

if (node.NodeType == System.Xml.XmlNodeType.Element)

{

XElement el = (XElement)node;

//получаем значение аттрибута id и преобразуем его в Int32

int id = Int32.Parse(el.Attribute("id").Value);

//увеличиваем счетчик на единицу и присваиваем значение обратно

id++;

el.Attribute("id").Value = id.ToString();

}

//переходим к следующему узлу

node = node.NextNode;

}

doc.Save(fileName);

Теперь попробуем это сделать более правильным способом для наших задач:

foreach (XElement el in doc.Root.Elements("track"))

{

int id = Int32.Parse(el.Attribute("id").Value);

el.SetAttributeValue("id", --id);

}

doc.Save(fileName);

Как видим – этот способ нам подошел больше.

Добавление новой записи

Добавим новый трек в нашу библиотеку, а заодно вычислим средствами LINQ следующий уникальный Id для трека:

int maxId = doc.Root.Elements("track").Max(t => Int32.Parse(t.Attribute("id").Value));

XElement track = new XElement("track",

new XAttribute("id", ++maxId),

new XAttribute("genre", "Break Beat"),

new XAttribute("time", "5:35"),

new XElement("name", "Higher Than A Skyscraper"),

new XElement("artist", "Hybrid"),

new XElement("album", "Morning Sci-Fi"));

doc.Root.Add(track);

doc.Save(fileName);

Вот таким подним запросом ко всем элементам вычисляется максимальное значение аттрибута id у треков. При добавлении полученное максимальное значение инкрементируем. Само же добавление элемента сводится к вызову метода Add. Обратите внимание, что добавляем элементы в Root, так как иначе нарушим структуру XML документа, объявив там 2 корневых элемента. Так же не забывайте сохранять ваш документ на диск, так как до момента сохранения никакие изменения в нашем XDocument не отразятся в XML файле.

Удаление элементов

Попробуем удалить все элементы исполнителя DMX:

IEnumerable<XElement> tracks = doc.Root.Descendants("track").Where(

t => t.Element("artist").Value == "DMX").ToList();

foreach (XElement t in tracks)

t.Remove();

В этом примере мы вначале выбрали все треки у который дочерний элемент artst удовлетворяет критерии, а потом в цикле удалили эти элементы. Важен вызов в конце выборки ToList(). Этим самым мы фиксируем в отдельном участке памяти все элементы, которые хотим удалить. Если же мы надумаем удалять из набора записей, по которому проходим непосредственно в цикле, мы получим удаление первого элемента и последующий NullReferenceException. Так что важно помнить об этом. По совету xaoccps удалять можно и более простым способом:

IEnumerable<XElement> tracks = doc.Root.Descendants("track").Where(

t => t.Element("artist").Value == "DMX");

tracks.Remove();

В этом случае приводить к списку наш полученный результат вызовом функции ToList() не нужно. Почему этот способ не использовал изначально описал в комментарии :)

Немного дополнительных запросов к нашей базе треков

Отсортируем треки по продолжительности в обратном порядке:

IEnumerable<XElement> tracks = from t in doc.Root.Elements("track")

let time = DateTime.Parse(t.Attribute("time").Value)

orderby time descending

select t;

foreach (XElement t in tracks)

Console.WriteLine("{0} - {1}", t.Attribute("time").Value, t.Element("name").Value);

Отсортируем элементы по жанру, исполнителю, названию альбома, названию трека:

IEnumerable<XElement> tracks = from t in doc.Root.Elements("track")

orderby t.Attribute("genre").Value,

t.Element("artist").Value,

t.Element("album").Value,

t.Element("name").Value

select t;

foreach (XElement t in tracks)

{

Console.WriteLine("{0} - {1} - {2} - {3}", t.Attribute("genre").Value,

t.Element("artist").Value,

t.Element("album").Value,

t.Element("name").Value);

}

Простенький запрос, выводящий количество треков в каждом альбоме:

var albumGroups = doc.Root.Elements("track").GroupBy(t => t.Element("album").Value);

foreach (IGrouping<string, XElement> a in albumGroups)

Console.WriteLine("{0} - {1}", a.Key, a.Count());

Выводы

После того как вы освоили пространство имен System.Xml для работы с XML на более низком уровне, смело переходите на использование System.Xml.Linq, надеюсь, написанная статья поможет это сделать быстрее, ведь не так страшен черт, как его рисуют. Как видно из примеров выше — многие вещи делать значительно проще, количество строк кода сокращается. Это дает нам очевидные преимущется, начиная со скорости разработки, заканчивая более легким сопровождением кода, написанного ранее.

Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования