Разбор XML с Linq - PullRequest
       47

Разбор XML с Linq

0 голосов
/ 21 января 2011

У меня есть следующий XML-документ, который я хотел бы проанализировать в DataSet.

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> 
<Response Status="OK">
  <Item>
    <Field Name="ID">767147519</Field> 
    <Field Name="Name">Music</Field> 
    <Field Name="Path">Family\Music</Field> 
    <Field Name="Type">Playlist</Field> 
  </Item>
</Response>

Я хочу получить значения атрибутов для идентификатора, имени и пути.

Вот что я пытался сделать:

Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Item") Select c
For Each result In name
  Dim str1 = result.Attribute("ID").Value 'Returns Nothing and causes a validation error
  Dim str2 = result.Value ' Returns all the attribute values in one long string (ie "767147519MusicFamilyPlaylist")
Next

Любая помощь будет принята с благодарностью.

Спасибо

Мэтт

EDIT:

Следуя одному из приведенных ниже ответов, я пытался реализовать анонимный тип в моем Linq, однако я продолжаю сталкиваться с ошибкой

Ссылка на объект не установлена ​​на экземпляр объекта.

Мой обновленный код выглядит следующим образом:

Dim name = From c In loaded.Descendants("Item") Select c Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
Dim Id As String = String.Empty
For Each result In name
  Id = result.sID
Next

Я думаю, что эта ошибка означает, что атрибут ("ID") не может быть найден, поэтому я попытался несколько вариантов этого с похожими результатами.

Может ли кто-нибудь определить, куда я иду, и указать мне правильное направление.

Спасибо

Мэтт

Ответы [ 12 ]

2 голосов
/ 25 января 2011

Вы можете использовать XPath:

Dim data = From item In loaded.Descendants("Item")
           Select
             ID = item.XPathSelectElement("Field[@Name='ID']").Value,
             Name = item.XPathSelectElement("Field[@Name='Name']").Value,
             Path = item.XPathSelectElement("Field[@Name='Path']").Value,
             Type = item.XPathSelectElement("Field[@Name='Type']").Value

(Обязательно импортируйте пространство имен System.Xml.XPath)

Или добавить его непосредственно в DataTable:

Dim dt As New DataTable()
dt.Columns.Add("ID")
dt.Columns.Add("Name")
dt.Columns.Add("Path")
dt.Columns.Add("Type")
For Each item In loaded.Descendants("Item")
  dt.Rows.Add(
    item.XPathSelectElement("Field[@Name='ID']").Value,
    item.XPathSelectElement("Field[@Name='Name']").Value,
    item.XPathSelectElement("Field[@Name='Path']").Value,
    item.XPathSelectElement("Field[@Name='Type']").Value
  )
Next
1 голос
/ 25 января 2011

Использовать XPath и избавить всех от головной боли?

XmlDocument xml = new XmlDocument();
xml.Load(xmlSource);

string id = xml.SelectSingleNode("/Response/Item/Field[@Name='ID']").InnerText;
string name = xml.SelectSingleNode("/Response/Item/Field[@Name='Name']").InnerText;
string path = xml.SelectSingleNode("/Response/Item/Field[@Name='Path']").InnerText;
1 голос
/ 25 января 2011

Вот моя попытка решения вашей проблемы. Я только что заметил, что вы хотите использовать как можно больше LINQ, поэтому я соответствующим образом структурировал свой запрос LINQ. Обратите внимание, что тип результата (для «идентификаторов») будет IEnumerable (), т. Е. Вам нужно будет запустить для каждого цикла, чтобы получить индивидуальные идентификаторы даже с одним элементом:

Dim loaded As XDocument = XDocument.Load(uriString)

Dim IDs = From items In loaded.Descendants("Item") _
         Let fields = items.Descendants("Field") _
         From field In fields _
         Where field.Attribute("Name").Value = "ID" _
         Select field.Value

С другой стороны: если вы в будущем столкнетесь с анонимным типом "var" на C #, то в vb эквивалент будет dim, как в моем запросе выше (без части "as type").

Надеюсь, это поможет. Maverik

1 голос
/ 21 января 2011

Еще одно решение с анонимными типами:

        var doc = XDocument.Load("c:\\test");

        var list = doc.Root
         .Elements("Item")
         .Select(item =>
          new
          {
              Id = item.Elements("Field").Where(e => e.Attribute("Name").Value == "ID").Select(e => e.Value).FirstOrDefault(),
              Path = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Path").Select(e => e.Value).FirstOrDefault(),
              Name = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Name").Select(e => e.Value).FirstOrDefault(),
          })
         .ToArray();

        foreach (var item in list)
        {
            var id = item.Id;
            var name = item.Name;
        }

Уродливое выражение внутри нового оператора может быть сокращено следующей анонимной функцией:

Func<XElement, string, string> getAttrValue = (node, attrName) =>
{
 return node.Elements("Field")
  .Where(e => e.Attribute("Name").Value == attrName)
  .Select(e => e.Value)
  .FirstOrDefault();
};

Тогда новый оператор выглядит следующим образом:

 new 
 { 
  Id = getAttrValue(item, "ID"), 
  Path = getAttrValue(item, "Path"),
  Name = getAttrValue(item, "Name"),
 }
0 голосов
/ 28 января 2011

В вашем коде есть несколько ошибок:

Вы должны получить потомков, у которых XName равно Field вместо Item

Dim name = From c In loaded.Descendants("Field") Select c

Атрибут, который вам нужен, называется Name, а не ID

Dim str1 = result.Attribute("Name").Value

На первой вашей итерации для каждого str1 будет «ID», на следующей - «Имя» и т. Д.

Общий код:

Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Field") Select c
For Each result In name
  Dim str1 = result.Attribute("Name").Value 'Returns "ID"
  Dim str2 = result.Value ' Returns "767147519"
Next
0 голосов
/ 26 января 2011

После некоторых дальнейших исследований и с помощью частей из предоставленных ответов я нашел следующее, которое возвращает информацию, которую я ищу.

Dim Query = From items In loaded.Descendants("Item") _   
Let sID = ( From q In items.Descendants("Field") _       
Where q.Attribute("Name").Value = "ID" ) _ 
Let sName = ( From r In items.Descendants("Field") _       
Where r.Attribute("Name").Value = "Name" ) _ 
Let sPath = ( From s In items.Descendants("Field") _       
Where s.Attribute("Name").Value = "Path" ) _ 
Where (Ctype(sPath.Value,String) Like "Family\*") _
Select pId=sID.Value, pName=sName.Value, pPath = sPath.Value

Если это можно улучшить каким-либо образом, чтобы повысить производительность, пожалуйста, дайте мне знать.

Спасибо всем за вашу помощь, хотя ни один ответ не смог полностью решить проблему, я смог многое узнать о Linq благодаря помощи каждого.

Мэтт

0 голосов
/ 26 января 2011

Я надеюсь, что вы ожидаете что-то вроде этого короткого ответа, а не другой реализации:

Dim items = From c In loaded.Descendants("Item") Select c (...)

Хорошо, пока ничего не должно возникнуть.Имя переменной 'name' немного сбивало с толку, поэтому я изменил его на 'items'.

Вторая часть содержит ошибку:

Dim items = (...) Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault

Следующее работает, потому что есть атрибутназывается Name, хотя результатом является 'ID', чего совершенно не ожидалось:

c.Element("Field").Attribute("Name").Value

Здесь появляется ошибка:

c.Attribute("ID").Value.FirstOrDefault

c - это XmlNode ' ... 'и не имеет никаких атрибутов, поэтому результат c.Attribute ("ID") равен нулю.

Я думаю, вы хотели что-то вроде следующего:

Dim loaded = XDocument.Load("XMLFile1.xml")
Dim items = From item In loaded.Descendants("Item") Select _
            sID = (From field In item.Descendants("Field") _
                   Where field.Attribute("Name") = "ID" _
                   Select field.Value).FirstOrDefault() _
            , _
            sName = (From field In item.Descendants("Field") _
                     Where field.Attribute("Name") = "Name" _
                     Select field.Value).FirstOrDefault()
0 голосов
/ 26 января 2011

Вот общее решение, которое обрабатывает все поля с разными именами полей в нескольких элементах. Он сохраняет результат в одной таблице, содержащей все различные имена полей в качестве имен столбцов.

Module Module1

Function createRow(ByVal table As DataTable, ByVal item As XElement) As DataRow
    Dim row As DataRow = table.NewRow

    Dim fields = item.Descendants("Field")
    For Each field In fields
        row.SetField(field.Attribute("Name").Value, field.Value)
    Next

    Return row

End Function


Sub Main()
    Dim doc = XDocument.Load("XMLFile1.xml")

    Dim items = doc.Descendants("Item")

    Dim columnNames = From attr In items.Descendants("Field").Attributes("Name") Select attr.Value

    Dim columns = From name In columnNames.Distinct() Select New DataColumn(name)

    Dim dataSet As DataSet = New DataSet()
    Dim table As DataTable = New DataTable()
    dataSet.Tables.Add(table)

    table.Columns.AddRange(columns.ToArray())

    Dim rows = From item In items Select createRow(table, item)

    For Each row In rows
        table.Rows.Add(row)
    Next

    ' TODO Handle Table
End Sub

End Module

Я пытался использовать как можно больше Linq, но Linq немного негибок, когда речь идет о рекурсивной обработке вложенных элементов.

Вот пример файла xml, который я использовал:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Response Status="OK">
  <Item>
    <Field Name="ID">767147519</Field>
    <Field Name="Name">Music</Field>
    <Field Name="Path">Family\Music</Field>
    <Field Name="Type">Playlist</Field>
  </Item>
  <Item>
    <Field Name="ID">123</Field>
    <Field Name="Name">ABC</Field>
    <Field Name="RandomFieldName">Other Value</Field>
    <Field Name="Type">FooBar</Field>
  </Item>
</Response>

И результат:

ID         Name     Path          Type        RandomFieldName

767147519  Music    Family\Music  Playlist

123        ABC                    FooBar      Other Value
0 голосов
/ 24 января 2011

Простое решение:

        var result = doc.Root.Descendants(XName.Get("Item")).Select(x =>  x.Descendants(XName.Get("Field")));


        foreach (var v in result)
        {
            string id = v.Single(x => x.Attribute(XName.Get("Name")).Value == "ID").Value;

            string name = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Name").Value;

            string path = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Path").Value;

            string type = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Type").Value;

        }

Его можно легко преобразовать в код vb.

0 голосов
/ 23 января 2011

Есть еще один способ решить эту проблему. Преобразуйте этот XML в формат, который хочет DataSet, а затем загрузите его, используя DataSet.ReadXml. Это что-то вроде боли, если вы не знаете XSLT. Но действительно важно знать XSLT, если вы работаете с XML.

XSLT, который вам нужен, довольно прост. Начните с преобразования XSLT . Затем добавьте шаблон, который преобразует элементы Response и Item в формат, ожидаемый DataSet:

<xsl:template match="Response">
   <MyDataSetName>
      <xsl:apply-templates select="Item"/>
   </MyDataSetName>
</xsl:template>

<xsl:template match="Item">
   <MyDataTableName>
      <xsl:apply-templates select="Field[@Name='ID' or @Name='Name' or @Name='Path']"/>
   </MyDataTableName>
</xsl:template>

<xsl:template match="Field">
   <xsl:element name="{@Name}">
      <xsl:value-of select="."/>
   </xsl:element>
</xsl:template>

Это изменит ваш XML на документ, который выглядит следующим образом:

<MyDataSetName>
  <MyDataTableName>
    <ID>767147519</ID> 
    <Name>Music</Name> 
    <Path>Family\Music</Path> 
  </MyDataTableName>
</MyDataSetName>

... и вы можете просто передать это DataSet.ReadXml.

Edit:

Я должен отметить, что, если вы не сделаете этого много, не очевидно, что одним из эффектов этого является то, что количество кода C #, которое вам нужно создать и заполнить DataSet, минимально:

    private DataSet GetDataSet(string inputFilename, string transformFilename)
    {
        StringBuilder sb = new StringBuilder();
        using (XmlReader xr = XmlReader.Create(inputFilename))
        using (XmlWriter xw = XmlWriter.Create(new StringWriter(sb)))
        {
            XslCompiledTransform xslt = new XslCompiledTransform();
            xslt.Load(transformFilename);
            xslt.Transform(xr, xw);
        }
        using (StringReader sr = new StringReader(sb.ToString()))
        {
            DataSet ds = new DataSet();
            ds.ReadXml(sr);
            return ds;
        }
    }

Это также многоразовое использование. Вы можете использовать этот метод, чтобы заполнить столько разных DataSet s, сколько вам нужно; вам просто нужно написать преобразование для каждого формата.

...