Работаем с 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