2011-01-21 2 views
0

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

<?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> 

Я хочу получить значения атрибутов для ID, Name и Path.

Ниже то, что я пытался:

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 

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

Спасибо,

Matt

EDIT:

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

Object reference not set to an instance of an object.

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

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

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

Спасибо,

Matt

+0

Я обновил мой вопрос, чтобы показать моя попытка реализовать анонимный тип, я столкнулся с ошибкой ссылочной ссылки объекта (см. обновленный вопрос), любая помощь в определении причины этой ошибки была бы весьма признательна. – Lima

+0

Также добавлен новый тег, приложение находится в VB.Net, поэтому примеры в VB будут оценены, но любая помощь будет отличной. – Lima

+0

Вы говорите, что считаете, что эта ошибка означает, что атрибут «ID» не может быть найден, но проблема в том, что ** нет атрибута «ID» **. Существует элемент _element_ под названием «Поле» с именем _attribute_ под названием «Имя». Атрибут Name имеет значение _value_ «ID». Ваш код пытается сделать не то. Вам нужно получить '.Value' элемента Field, где' .Attribute («Name»). Значение == «ID» '. Не значение атрибута «ID», потому что такой вещи нет. –

ответ

0

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

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 через помощь всем.

Matt

0

I am wanting to get the attribute values for ID, Name, and Path.

Если вы не против использовать что-то другое, чем XDocument я бы просто использовать XmlDocument:

 XmlDocument doc = new XmlDocument(); 
     doc.Load(new XmlTextReader("XData.xml")); 
     XmlNodeList items = doc.GetElementsByTagName("Item"); 
     foreach (XmlElement item in items.Cast<XmlElement>()) 
     { 
      XmlElement[] fields = item.GetElementsByTagName("Field").Cast<XmlElement>().ToArray(); 
      string id = (from s in fields where s.Attributes["Name"].InnerText == "ID" select s).First().InnerText; 
      string name = (from s in fields where s.Attributes["Name"].InnerText == "Name" select s).First().InnerText; 
      string path = (from s in fields where s.Attributes["Name"].InnerText == "Path" select s).First().InnerText; 

      //Do stuff with data. 
     } 

С точки зрения производительности это может быть плачевны. Вы также можете иметь петлю в полях, а затем использовать переключатель в атрибуте Name, чтобы вы не проверяли одно и то же поле более одного раза. Зачем вам вообще нужен какой-либо linq?


 XmlDocument doc = new XmlDocument(); 
     doc.Load(new XmlTextReader("XData.xml")); 
     XmlNodeList items = doc.GetElementsByTagName("Item"); 
     foreach (XmlElement item in items.Cast<XmlElement>()) 
     { 
      foreach (XmlNode field in item.GetElementsByTagName("Field")) 
      { 
       string name = field.Attributes["Name"].InnerText; 
       switch (name) 
       { 
        case "ID": 
         string id = field.InnerText; 
         //Do stuff with data. 
         break; 
        case "Path": 
         string path = field.InnerText; 
         //Do stuff with data. 
         break; 
        case "Name": 
         string name = field.InnerText; 
         //Do stuff with data. 
         break; 
        default: 
         break; 
       } 
      } 
     } 
+0

Возможно, вы захотите использовать Linq, потому что он намного более сжат и читабельен ;-) – ColinE

+0

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

0

Ваш LINQ запрос возвращает все элементы Элемента в документе:

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

код, который следует пытается получить атрибут 'ID' от элемента 'Item':

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

Однако атрибут 'ID' находится в дочернем элементе 'Field'.

Что вам нужно следующее:

// find all the Item elements 
var items = loaded.Descendants("Item"); 
foreach(var item in items) 
{ 
    // find all the Field child elements 
    var fields = item.Descendants("Field"); 

    // find the field element which has an ID attribute, and obtain the element value 
    string id = fields.Where(field => field.Attribute("ID")!=null) 
        .Single() 
        .Value; 

    // etc ... 
} 
1

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

 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"), 
} 
-1

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

XSLT, который вам нужен, довольно прост. Начните с XSLT identity transform. Затем добавить шаблон, который преобразует 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 из множества различных возможных форматов ввода, сколько вам нужно; вам просто нужно написать преобразование для каждого формата.

+1

Использование XSLT для преобразования документа во что-то, что может быть прочитано с помощью DataSet, кажется немного OTT, когда вы можете достичь того же самого элемента с помощью очень простого элемента Linq или XmlDocument! – ColinE

+1

Если вы знакомы с XSLT, так как любой, кто работает с XML, должен быть, все вышеизложенное тривиально. Кроме того, «очень простой бит Linq», представленный в этих ответах, просто заполняет переменные; он фактически не создает объекты DataRow, не добавляет их в правую 'DataTable' и т. д. Я не говорю, что мой ответ поэтому превосходен - я пропустил код, который выполняет преобразование, например. Но он * делает * заменяет весь код ADO на один вызов метода и уменьшает количество кода C#, необходимого для минимальной суммы. –

+0

+1 Я бы пошел на загрузку преобразованного Xml тоже. Xslt может быть еще проще. – Filburt

0

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

 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

Как «string id = v.Single (x => x.Attribute (XName.Get (« Имя »)). Значение ==« ID »). Значение; преобразован в VB.NET? Я не сталкивался с тем, как преобразовать этот тип C# -кодирования. – Lima

1

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

На стороне записки: Для будущая ссылка, если вы набегаете на анонимный тип C# «var» в примерах, эквивалент в vb тускнеет, как в моем запросе выше (без части «как тип»).

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

2

Вы можете использовать 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

Использование 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; 
0

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

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 немного негибкой, когда речь идет об обработке вложенных элементов рекурсивно.

Heres образец файла 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

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

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

Хорошо, до сих пор ничто не должно бежать. Имя переменной «name» было немного запутанным, поэтому я изменил его на «элементы».

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

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

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

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

А вот ошибка:

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

с-XmlNode '< Item> .. . </Item> ', и у него нет никаких атрибутов, поэтому результат c.Attribute ("ID") имеет значение NULL.

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

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

Есть несколько ошибок в коде:

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

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

атрибута вы после называется Name, а не ID

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

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

Всего код:

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 
Смежные вопросы