1.4 Основные запросы (linq to xml)
Для возможности построение LINQ to XML запросов к проекту подключить библиотеку System.Xml.Linq;
Среди всех запросов LINQ to XML можно выделить ряд основных.
1. Найти элемент с определенным атрибутом.
xmlLang
<?xml version="1.0"?>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by
driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
В примере показано, как выполнить поиск элемента Address, имеющего атрибут Type со значением «Billing».
XElement root = XElement.Load("PurchaseOrder.xml");
IEnumerable<XElement> address =
from el in root.Elements("Address")
where (string)el.Attribute("Type") == "Billing"select el;
foreach (XElement el in address)
Console.WriteLine(el);
Этот код выводит следующие результаты.
xmlLang
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
2. Найти элемент с определенным дочерним элементом.
xmlLang
<?xml version="1.0"?>
<Tests>
<Test TestId="0001" TestType="CMD">
<Name>Convert number to string</Name>
<CommandLine>Examp1.EXE</CommandLine>
<Input>1</Input>
<Output>One</Output>
</Test>
<Test TestId="0002" TestType="CMD">
<Name>Find succeeding characters</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>abc</Input>
<Output>def</Output>
</Test>
<Test TestId="0003" TestType="GUI">
<Name>Convert multiple numbers to strings</Name>
<CommandLine>Examp2.EXE /Verbose</CommandLine>
<Input>123</Input>
<Output>One Two Three</Output>
</Test>
<Test TestId="0004" TestType="GUI">
<Name>Find correlated key</Name>
<CommandLine>Examp3.EXE</CommandLine>
<Input>a1</Input>
<Output>b1</Output>
</Test>
<Test TestId="0005" TestType="GUI">
<Name>Count characters</Name>
<CommandLine>FinalExamp.EXE</CommandLine>
<Input>This is a test</Input>
<Output>14</Output>
</Test>
<Test TestId="0006" TestType="GUI">
<Name>Another Test</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>Test Input</Input>
<Output>10</Output>
</Test>
</Tests>
В этом примере осуществляется поиск элемента Test, имеющего дочерний элемент CommandLine со значением «Examp2.EXE».
XElement root = XElement.Load("TestConfig.xml");
IEnumerable<XElement> tests =
from el in root.Elements("Test")
where (string)el.Element("CommandLine") == "Examp2.EXE"select el;
foreach (XElement el in tests)
Console.WriteLine((string)el.Attribute("TestId"));
Этот код выводит следующие результаты.
0002
0006
3. Найти потомков элемента с определённым именем.
Иногда возникает необходимость найти всех потомков с определенным именем. В таких случаях можно написать код для просмотра всех потомков, но проще использовать ось xmlns="http://www.w3.org/1999/xhtml"Descendants.
В следующем примере показано, как находить потомков на основе имени элемента.
XElement root = XElement.Parse(@"<root>
<para>
<r>
<t>Some text </t>
</r>
<n>
<r>
<t>that is broken up into </t>
</r>
</n>
<n>
<r>
<t>multiple segments.</t>
</r>
</n>
</para>
</root>");
IEnumerable<string> textSegs =
from seg in root.Descendants("t")
select (string)seg;
string str = textSegs.Aggregate(new StringBuilder(),
(sb, i) => sb.Append(i),
sp => sp.ToString()
);
Console.WriteLine(str);
Этот код выводит следующие результаты:
Some text that is broken up into multiple segments.
4. Найти отдельного потомка.
Метод оси Descendants вы можете использовать для быстрого написания кода с целью поиска одного уникально именованного элемента. Этот способ особенно полезен, если нужно найти конкретного потомка с заданным именем. Можно написать собственный код для перехода к нужному элементу, но часто быстрей и легче написать такой код с помощью оси Descendants.
В этом примере используется стандартный оператор запроса First.
XElement root = XElement.Parse(@"<Root>
<Child1>
<GrandChild1>GC1 Value</GrandChild1>
</Child1>
<Child2>
<GrandChild2>GC2 Value</GrandChild2>
</Child2>
<Child3>
<GrandChild3>GC3 Value</GrandChild3>
</Child3>
<Child4>
<GrandChild4>GC4 Value</GrandChild4>
</Child4>
</Root>");
string grandChild3 = (string)
(from el in root.Descendants("GrandChild3")
select el).First();
Console.WriteLine(grandChild3);
Этот код выводит следующие результаты:
GC3 Value
5. Создать запросы с фильтрацией.
Иногда возникает необходимость в написании запросов LINQ to XML с комплексной фильтрацией. Например, может потребоваться найти все элементы, имеющие дочерние элементы с определенным именем и значением. В этом разделе приводится пример написания запроса с комплексной фильтрацией.
Пример
В этом примере показано, как найти все элементы PurchaseOrder, имеющие дочерний элемент Address с атрибутом Type, равным «Доставка», и дочерним элементом State, равным «NY». В нем используется вложенный запрос в предложении Where, а оператор Any возвращает значение true, если коллекция содержит элементы.
В этом примере выбирается несколько заказов на покупку (LINQ to XML).
xmlLang
<?xml version="1.0"?>
<PurchaseOrders>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99505" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please notify me before shipping.</DeliveryNotes>
<Items>
<Item PartNumber="456-NM">
<ProductName>Power Supply</ProductName>
<Quantity>1</Quantity>
<USPrice>45.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99504" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Seattle</City>
<State>WA</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Items>
<Item PartNumber="898-AZ">
<ProductName>Computer Keyboard</ProductName>
<Quantity>1</Quantity>
<USPrice>29.99</USPrice>
</Item>
<Item PartNumber="898-AM">
<ProductName>Wireless Mouse</ProductName>
<Quantity>1</Quantity>
<USPrice>14.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
</PurchaseOrders>
XElement root = XElement.Load("PurchaseOrders.xml");
IEnumerable<XElement> purchaseOrders =
from el in root.Elements("PurchaseOrder")
where
(from add in el.Elements("Address")
where
(string)add.Attribute("Type") == "Shipping" &&
(string)add.Element("State") == "NY"select add)
.Any()
select el;
foreach (XElement el in purchaseOrders)
Console.WriteLine((string)el.Attribute("PurchaseOrderNumber"));
Этот код выводит следующие результаты:
99505
6. Отсортировать элементы.
В этом примере показано, как создавать запросы с сортировкой результатов.
Пример
xmlLang
<Root>
<TaxRate>7.25</TaxRate>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>24.50</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>1</Quantity>
<Price>89.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>5</Quantity>
<Price>4.95</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>66.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>10</Quantity>
<Price>.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>15</Quantity>
<Price>29.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>8</Quantity>
<Price>6.99</Price>
</Data>
</Root>
XElement root = XElement.Load("Data.xml");
IEnumerable<decimal> prices =
from el in root.Elements("Data")
let price = (decimal)el.Element("Price")
orderby price
select price;
foreach (decimal el in prices)
Console.WriteLine(el);
Этот код выводит следующие результаты:
0.99
4.95
6.99
24.50
29.00
66.00
89.99
7. Вычислить промежуточные значения.
В этом примере показано, как вычислять промежуточные значения, которые используются в сортировке, фильтрации и выборке.
В примере используется документ из примера 6 и предложение Let.
XElement root = XElement.Load("Data.xml");
IEnumerable<decimal> extensions =
from el in root.Elements("Data")
let extension = (decimal)el.Element("Quantity") *
(decimal)el.Element("Price")
where extension >= 25
orderby extension
select extension;
foreach (decimal ex in extensions)
Console.WriteLine(ex);
Этот код выводит следующие результаты:
55.92
73.50
89.99
198.00
435.00
8. Создать запрос для поиска элементов на основе контекста (выбор элементов на основе других элементов в дереве).
Иногда требуется написать запрос, который выбирает элементы, исходя из их контекста. Может потребоваться использовать фильтрацию с учетом предыдущих или следующих одноуровневых элементов. Может потребоваться использовать фильтрацию с учетом дочерних или родительских элементов.
Это можно сделать, написав запрос и используя результаты запроса в предложении where. Если требуется сначала провести проверку на наличие значения null, а затем проверить само значение, более удобным будет выполнить запрос в предложении let, а затем использовать результаты в предложении where.
Пример
В следующем примере выбираются все элементы class="code" p, сразу за которыми следует элемент ul.
XElement doc = XElement.Parse(@"<Root>
<p id=""1""/>
<ul>abc</ul>
<Child>
<p id=""2""/>
<notul/>
<p id=""3""/>
<ul>def</ul>
<p id=""4""/>
</Child>
<Child>
<p id=""5""/>
<notul/>
<p id=""6""/>
<ul>abc</ul>
<p id=""7""/>
</Child>
</Root>");
IEnumerable<XElement> items =
from e in doc.Descendants("p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name.LocalName == "ul"select e;
foreach (XElement e in items)
Console.WriteLine("id = {0}", (string)e.Attribute("id"));
Этот код выводит следующие результаты:
id = 1
id = 3
id = 6
Другой вариант решения
XElement doc = XElement.Parse(@"<Root xmlns='http://www.adatum.com'>
<p id=""1""/>
<ul>abc</ul>
<Child>
<p id=""2""/>
<notul/>
<p id=""3""/>
<ul>def</ul>
<p id=""4""/>
</Child>
<Child>
<p id=""5""/>
<notul/>
<p id=""6""/>
<ul>abc</ul>
<p id=""7""/>
</Child>
</Root>");
XNamespace ad = "http://www.adatum.com";
IEnumerable<XElement> items =
from e in doc.Descendants(ad + "p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name == ad.GetName("ul")
select e;
foreach (XElement e in items)
Console.WriteLine("id = {0}", (string)e.Attribute("id"));
Этот код выводит следующие результаты:
id = 1
id = 3
id = 6
Литература
Общие сведения о программирования LINQ to XML. http://technet.microsoft.com/ru-ru/subscriptions/bb387065(v=vs.90).aspx
http://msdn.microsoft.com/library/bb308960.aspx#xlinqoverview_topic2a
http://msdn.microsoft.com/ru-ru/library/bb943906.aspx