Pull to refresh

Работаем с LINQ to XML

.NET *
В первой статье в блоге .NET «Работаем с XML» в комментариях народ потребовал статьи LINQ to XML. Что же, попробуем раскрыть принципы работы этой новой технологии от Microsoft.

Создадим базу для ведения каталога аудиозаписей. База будет состоять из треков:
  • Код
  • Название
  • Исполнитель
  • Альбом
  • Продолжительность
  • Жанр

Мы научимся добавлять, редактировать, удалять и делать различные выборки из нашей базы.

Для начала создадим консольное приложение (я пишу свои проекты на 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, надеюсь, написанная статья поможет это сделать быстрее, ведь не так страшен черт, как его рисуют. Как видно из примеров выше — многие вещи делать значительно проще, количество строк кода сокращается. Это дает нам очевидные преимущется, начиная со скорости разработки, заканчивая более легким сопровождением кода, написанного ранее.
Tags:
Hubs:
Total votes 32: ↑30 and ↓2 +28
Views 125K
Comments Comments 66