Чтение дочерних элементов XML переменной длины в объект
Мне нужно прочитать все элементы в XML-файле, который имеет формат, который больше похож на древовидную иерархию, а затем заполнить класс им, вот пример:
<?xml version="1.0"?>
<WBs>
<WP GeneralID ="1">
<P name="General_Header">
<Q name= "Category">
<Tools>
<Tool id ="1">
<TName> QT 1</TName>
<Rev>1</Rev>
</Tool>
<Tool id ="2">
<TName> QT 2</TName>
<Rev>3</Rev>
</Tool>
</Tools>
<Contacts>
<Contact>
<CName>MM</CName>
<CMail>m.m@i.com</CMai>
</Contact>
<Contact>
<CName>AM</CName>
<CMail>a.m@i.com</CMail>
</Contact>
</Contacts>
</Q>
<ss name= "Category">
<Tools>
<Tool id ="1">
<TName> SST 1</TName>
<Rev>3</Rev>
</Tool>
<Tool id ="2">
<TName> SST 2</TName>
<Rev>3</Rev>
</Tool>
</Tools>
<Contacts>
<Contact>
<CName>KE</CName>
<CMail>K.E@i.com</CMai>
</Contact>
<Contact>
<CName>AM</CName>
<CMail>a.m@i.com</CMail>
</Contact>
</Contacts>
</ss>
</P>
</WP>
</WBs>
Я сделал класс для каждого из WP, инструмент, контакт. следующее:
class WP
{
public string GeneralHeader { get; set; } //level 1 i.e p,
public string Category { get; set; }// level 2
public string SubCategory { get; set; }//level 3
public string SubSubCategory { get; set; } // level 4
public List<Tool> WPTools { get; set; }
public List<Contact> WPContacts { get; set; }
}
Я хочу пройти через все элементы и дочерние элементы, а затем заполнить класс WP таким образом, чтобы при каждом обнаружении двух дочерних элементов он находился в двух разных объектах WP, но имел один и тот же родительский атрибут.
Например: для приведенного выше примера я надеялся получить два объекта из класса WP с тем же параметром "General_Header", что и "P", но один объект имеет "Category", равную "Q", а другой - "SS", затем продолжайте заполнять каждый соответствующими инструментами и контактами. Идея та же, что и у остальных файлов XML, например, на разных уровнях одна и та же проблема: у тегов WP есть ветви в "подкатегории", а у других - в "подкатегории".
Все, что я могу придумать, - это изменить файл xml так, чтобы каждая полная ветвь (до тегов инструментов и контактов) была включена в отдельный набор <WP>---</WP>
теги, но в этом случае я бы повторил общие родительские теги, которые я не считаю эффективным способом использования xml.
Какие-либо предложения?
Заранее спасибо.
1 ответ
Это действительно своеобразный кусок XML. Обычно мы видим такую структуру:
<Category name= "Q">
Вместо этого:
<Q name = "Category">
Я бы сказал, что все зависит от того, что вы собираетесь делать с этим XML после, и, поскольку я ничего не знаю об этом, я извлеку пользу из сомнений и предположу, что это правильная структура. Но если это не так, пожалуйста, измените его.
Как вы сказали, вы не хотите повторять <P name="General_Header">
снова и снова для каждой категории и подкатегории.
Прежде всего, давайте проанализируем ваш xml текст (или загрузим, если вы загружаете из файла):
XDocument document = XDocument.Parse(content);
Теперь давайте получим заголовок, используя немного Linq:
var generalHeader = document.Descendants()
.Where(p=>p.Attributes("name")
.Any(a=>a.Value=="General_Header"))
.FirstOrDefault();
Теперь давайте получим все категории, подкатегории, подкатегории в целом:
var allCategories = generalHeader.Descendants()
.Where(p=>p.Attributes("name")
.Any(a=>new[]{"Category","SubCategory","SubSubCategory"}
.Contains(a.Value)));
Хорошо, теперь нам нужно создать WP
класс для каждой категории у нас есть. Но мы также должны заполнить свойства Category, SubCategory и SubSubCategory. Таким образом, мы должны знать, что соответствующие категории (кошка, суб или Subub). Для этого я создал следующий метод:
public static IEnumerable<WP> CreateWP(XElement header, IEnumerable<XElement> categories)
{
foreach(XElement category in categories)
{
WP wp = new WP();
wp.GeneralHeader = header.Name.LocalName;
wp.Category = category.Ancestors().Concat(new []{category}).Where(c=> c.Attributes("name").Any(a=>a.Value == "Category")).Select(elem=>elem.Name.LocalName).FirstOrDefault();
wp.SubCategory = category.Ancestors().Concat(new []{category}).Where(c=> c.Attributes("name").Any(a=>a.Value == "SubCategory")).Select(elem=>elem.Name.LocalName).FirstOrDefault();
wp.SubSubCategory = category.Ancestors().Concat(new []{category}).Where(c=> c.Attributes("name").Any(a=>a.Value == "SubSubCategory")).Select(elem=>elem.Name.LocalName).FirstOrDefault();
XmlSerializer xt = new XmlSerializer(typeof(Tool));
wp.WPTools = category.Descendants("Tool").Select(t=> (Tool) xt.Deserialize(t.CreateReader())).ToList();
XmlSerializer xc = new XmlSerializer(typeof(Contact));
wp.WPContacts = category.Descendants("Contact").Select(t=> (Contact) xc.Deserialize(t.CreateReader())).ToList();
yield return wp;
}
}
Важная строка такова:
category.Ancestors().Concat(new []{category})
.Where(c=> c.Attributes("name").Any(a=>a.Value == "Category"))
.Select(elem=>elem.Name.LocalName).FirstOrDefault();
Он находит (сам по себе или ВСЕХ своих предков) имя, которое равно "Категория", или возвращает null
если ничего не найдено
По сути, то, что мы делаем здесь, это выравнивание всех элементов (всех видов категорий) и создание одного объекта для каждой найденной категории. Я использовал этот файл, чтобы проверить это:
http://pastebin.com/raw.php?i=cAUnhUgf
А вот полная скрипка, чтобы вы могли убедиться в этом:
https://dotnetfiddle.net/ZBvmhe
РЕДАКТИРОВАТЬ Объясняя это утверждение:
var allCategories = generalHeader.Descendants()
Это получает все элементы-потомки (включая категорию, подкатегорию, инструмент, контакт, все)
.Where(p=>p.Attributes("name")
.Any(a=>new[]{"Category","SubCategory","SubSubCategory"}.Contains(a.Value)));
И это переводится как
Где-нибудь из потомков
p
атрибуты которых XNamename
имеет значение, установленное какCategory
,SubCategory
, или жеSubSubCategory
Что значит:
p.Attributes("name")
Получает все атрибуты p
идентифицируется как "имя"
.Any(a=>new[]{"Category","SubCategory","SubSubCategory"}.Contains(a.Value))
Это возвращает истину, независимо от того, равно ли какое-либо из значений внутри атрибута имени одной из трех возможностей (Категория, Подкатегория или Суб-категория), или ложь в противном случае.
Вкратце, он получит всех потомков общего заголовка, которые являются категорией, подкатегорией или подкатегорией.