Русские документы
Ежедневные компьютерные новости RSS rusdoc.ru  Найти :
http://www.rusdoc.ru. Версия для печати.

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

Раздел: Programming / XML @ 04.05.2008 | Ключевые слова: LINQ XML

Автор: dmx
Источник: habrahabr

В первой статье в блоге .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 файл следующего содержания:



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



Вернуться в раздел: Programming / XML
© Copyright 1998-2012 Александр Томов. All rights reserved.