Xdocument get subelemnts attributes with Linq - c#

I have XML as below which I am loading into XDocument
<ConversionSpecification xmlns="http://tempuri.org/X12ParserSpecification.xsd" TransactionSetIdentifierCode="837">
<Loop LoopId="1000A" Name="SNAME" Identifier="NM1*41">
<Segment SegmentId="NM1" Usage="R"/>
<Segment SegmentId="PER" Usage="R"/>
</Loop>
<Loop LoopId="1000B" Name="RNAME" Identifier="NM1*40">
<Segment SegmentId="NM1" Usage="R"/>
</Loop>
<Loop LoopId="2000B" Name="SHLOOP" Identifier="22">
<Segment SegmentId="SBR" Usage="R"/>
<Segment SegmentId="PAT" Usage="S"/>
</Loop>
</ConversionSpecification>
XDocument document = XDocument.Load("Files/Loops.xml");
XNamespace ns = XNamespace.Get("http://tempuri.org/X12ParserSpecification.xsd");
var loopInfo = from loop in document.Descendants(ns + "Loop")
select new Loop
{
LoopID = loop.Attribute("LoopId").Value,
LoopIdentifier = loop.Attribute("Identifier").Value
LoopSegments =
LoopUsage =
};
Where Loop is:
class Loop
{
public string LoopID { get; set; }
public string LoopIdentifier { get; set; }
public string[] LoopSegments { get; set; }
public string[] LoopUsage { get; set; }
}
Now I would like to assign LoopElements with SegmentID attribute values and LoopUsage with usage attribute values. Is there a way to do it with a single subquery?

Is this what you're after?
var loopInfo = from loop in document.Descendants(ns + "Loop")
select new Loop
{
LoopID = loop.Attribute("LoopId").Value,
LoopIdentifier = loop.Attribute("Identifier").Value,
LoopSegments = loop.Descendants(ns + "Segment").Select(s => s.Attribute("SegmentId").Value).ToArray(),
LoopUsage = loop.Descendants(ns + "Segment").Select(s => s.Attribute("Usage").Value).ToArray()
};
The output looks like this:

Related

How to deserialize this xml to objects?

I'm fetching data in the format of this:
<ReplyUserAccount xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" requestid="" version="1.0" xmlns="url">
<Sender partnerid="xx">xx</Sender>
<Users>
<User method="GET" ResultCode="OK" Description="">
<Guid>xx</Guid>
<FirstName>xx</FirstName>
<LastName>xx</LastName>
<Phone>xx</Phone>
<Mobile>xx</Mobile>
<Email>xx</Email>
<EmplNo>xx</EmplNo>
<TacPermission />
<InvPermission>xx</InvPermission>
<CustomerId>xx</CustomerId>
</User>
</Users>
</ReplyUserAccount>
With the following C# objects:
[XmlRoot("ReplyUserAccount")]
public class ReplyUserAccount
{
[XmlElement("Users")]
public Users Users{ get; set; }
}
[XmlType("Users")]
public class Users
{
[XmlElement("User")]
public List<User> UserList{ get; set; }
}
[XmlType("User")]
public class User
{
[XmlElement("EmplNo")]
public string Id{ get; set; }
[XmlElement("Guid")]
public string Guid { get; set; } = null;
[XmlElement("Email")]
public string Email { get; set; }
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
public bool Active { get; set; } = true;
public string PhoneNumber { get; set; } = null;
}
And the following deserializing:
var result = await httpClient.GetAsync(url);
var xdoc = XDocument.Parse(await result.Content.ReadAsStringAsync());
XmlSerializer serializer = new XmlSerializer(typeof(ReplyUserAccount));
var content = xdoc.ToString();
TextReader reader = new StringReader(content);
var res = (ReplyUserAccount)serializer.Deserialize(reader);
But I get the following error:
InvalidOperationException: <ReplyUserAccount xmlns='xxx'> was not expected.
I'm a little bit lost as to how to properly deserialize this specific xml data. Any and all help with regards to this is greatly appreciated.
To fix the error, You have to remove xmlns and xsi in xml text before deserialize. You can remove xmlns and xsi like this:
var content = xdoc.ToString();
string strXMLPattern = #"xmlns(:\w+)?=""([^""]+)""|xsi(:\w+)?=""([^""]+)""";
content = Regex.Replace(content, strXMLPattern, "");
Therefore the method should be as follows
var result = await httpClient.GetAsync(url);
var xdoc = XDocument.Parse(await result.Content.ReadAsStringAsync());
XmlSerializer serializer = new XmlSerializer(typeof(ReplyUserAccount));
var content = xdoc.ToString();
string strXMLPattern = #"xmlns(:\w+)?=""([^""]+)""|xsi(:\w+)?=""([^""]+)""";
content = Regex.Replace(content, strXMLPattern, "");
TextReader reader = new StringReader(content);
var res = (ReplyUserAccount)serializer.Deserialize(reader);

Selecting XmlNode's Child Nodes based on Tag

I'm trying to parse this XML Document:
<MPD>
<Period duration="PT0H3M1.63S" start="PT0S">
<AdaptationSet>
<ContentComponent contentType="video" id="1" />
<Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
<BaseURL>car-20120827-89.mp4</BaseURL>
</Representation>
<Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
<BaseURL>car-20120827-88.mp4</BaseURL>
</Representation>
</AdaptationSet>
<AdaptationSet>
<ContentComponent contentType="audio" id="2" />
<Representation bandwidth="127236" codecs="mp4a.40.2" id="6" mimeType="audio/mp4" numChannels="2" sampleRate="44100">
<BaseURL>car-20120827-8c.mp4</BaseURL>
</Representation>
<Representation bandwidth="255236" codecs="mp4a.40.2" id="7" mimeType="audio/mp4" numChannels="2" sampleRate="44100">
<BaseURL>car-20120827-8d.mp4</BaseURL>
</Representation>
</AdaptationSet>
</Period>
</MPD>
using this C# code:
var rootDoc = new XmlDocument();
rootDoc.LoadXml(xmlString); // the one from above
var adaptationSets = rootDoc.GetElementsByTagName("AdaptationSet");
if (adaptationSets.Count > 0)
foreach (XmlNode adaptionSet in adaptationSets) // Loop through AdaptionSets
{
// Get the one Node in this AdaptionSet with the ContentComponent-Tag
var contentComponent = adaptionSet.SelectSingleNode("ContentComponent");
if (contentComponent != null)
{
// parse attributes
}
// Get All Nodes in this AdaptionSet with the Representation-Tag
var representations = adaptionSet.SelectNodes("Representation");
if(representations?.Count > 0)
foreach (XmlNode representation in representations)
{
// parse attributes of XmlNode
}
}
It all works except for the XPath queries. I tried a lot of variations with leading "/", "//", "./" and without any leading characters, but it just won't work. What am I doing wrong? I'm not using XPath on a regular basis and I couldn't find anything more than the leading characters I mentioned. Because I've seen it on a lot of other answers on this website I feel like I should mention that I'm explicitly looking for the XPath that will help me solve this, not some Linq variation or an entirely different approach.
Any help is greatly appreciated. Thanks in advance!
I think this solves your problem:
var rootDoc = new XmlDocument();
rootDoc.LoadXml(xmlString); // the one from above
var adaptationSets = rootDoc.SelectNodes("//AdaptationSet");
if (adaptationSets.Count > 0)
foreach (XmlNode adaptionSet in adaptationSets) // Loop through AdaptionSets
{
// Get the one Node in this AdaptionSet with the ContentComponent-Tag
var contentComponent = adaptionSet.SelectSingleNode("./ContentComponent");
if (contentComponent != null)
{
// parse attributes
}
// Get All Nodes in this AdaptionSet with the Representation-Tag
var representations = adaptionSet.SelectNodes("./Representation");
if (representations?.Count > 0)
foreach (XmlNode representation in representations)
{
// parse attributes of XmlNode
}
}
Try using xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XElement period = doc.Descendants("Period").FirstOrDefault();
MPD mpd = new MPD();
mpd.duration = (string)period.Attribute("duration");
mpd.start = (string)period.Attribute("start");
mpd.adaptions = period.Elements("AdaptationSet").Select(x => new Adaption() {
contentType = (string)x.Element("ContentComponent").Attribute("contentType"),
id = (int)x.Element("ContentComponent").Attribute("id"),
representations = x.Elements("Representation").Select(y => new Representation() {
bandwidth = (int)y.Attribute("bandwidth"),
codecs = (string)y.Attribute("codecs"),
height = (int?)y.Attribute("height"),
id = (int)y.Attribute("id"),
mimeType = (string)y.Attribute("mimeType"),
width = (int?)y.Attribute("width"),
baseURL = (string)y.Descendants("BaseURL").FirstOrDefault()
}).ToList()
}).ToList();
}
}
public class MPD
{
public string duration { get; set; }
public string start { get; set; }
public List<Adaption> adaptions { get; set; }
}
public class Adaption
{
public string contentType { get; set; }
public int id { get; set; }
public List<Representation> representations { get; set; }
}
public class Representation
{
public int bandwidth { get; set; }
public string codecs { get; set; }
public int? height { get; set; }
public int id { get; set; }
public string mimeType { get; set; }
public int? width { get; set; }
public string baseURL { get; set; }
}
}

How to parse xml with LINQ to an Object with XDocument

I have an xml file as below:
<Message xsi:schemaLocation ="..">
<Header>..</Header>
<Body>
<History
xmlns="..">
<Number></Number>
<Name></Name>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description></Description>
</Item>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description>1..</Description>
<Description>2..</Description>
</Item>
</History>
</Body>
</Message>
I would like to create and object from this as History object.
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
var xElement = XDocument.Parse(xmlString);
XElement body = (XElement)xElement.Root.LastNode;
XElement historyElement = (XElement)body.LastNode;
var history = new History
{
Name = (string)historyElement.Element("Name"),
Number = (string)historyElement.Element("Number"),
Items = (
from e in historyElement.Elements("Item")
select new Item
{
CraeteDate = DateTime.Parse(e.Element("CreateDate").Value),
Type = (string)e.Element("Type").Value,
Description = string.Join(",",
from p in e.Elements("Description") select (string)p.Element("Description"))
}).ToList()
};
Why this does not work?
The values are always null.
It seems that "historyElement.Element("Name")" is always null even there is an element and value for the element.
Any idea what am I missing?
Thanks
It's due to the namespace, try doing this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";// the namespace you have in the history element
var xElement = XDocument.Parse(xmlString);
var history= xElement.Descendants(ns+"History")
.Select(historyElement=>new History{ Name = (string)historyElement.Element(ns+"Name"),
Number = (string)historyElement.Element(ns+"Number"),
Items = (from e in historyElement.Elements(ns+"Item")
select new Item
{
CraeteDate= DateTime.Parse(e.Element(ns+"CreateDate").Value),
Type = (string) e.Element(ns+"Type").Value,
Description= string.Join(",",
from p in e.Elements(ns+"Description") select (string)p)
}).ToList()
}).FirstOrDefault();
If you want to read more about this subject, take a look this link
A couple of minor things here, the xml was malformed here. So had to take a while to test and make it work.
You have an xsi in the front which I assume should be somewhere mentioned in the xsd.
Turns out you have to append the namespace if your xml node has a namespace attached to it as you are parsing the xml tree here:
My sample solution looked like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public DateTime? CreateDate { get; set; }
public string Type { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xmlString =
#"<Message>
<Header>..</Header>
<Body>
<History
xmlns=""http://schemas.somewhere.com/types/history"">
<Number>12</Number>
<Name>History Name</Name>
<Item>
<CreateDate></CreateDate>
<Type>Item 1 Type</Type>
<Description>Item 1 Description</Description>
</Item>
<Item>
<CreateDate></CreateDate>
<Type>Item 2 Type</Type>
<Description>Item 2 Description 1</Description>
<Description>Item 2 Description 2</Description>
</Item>
</History>
</Body>
</Message>";
XNamespace ns = "http://schemas.somewhere.com/types/history";
var xElement = XDocument.Parse(xmlString);
var historyObject = xElement.Descendants(ns +"History")
.Select(historyElement => new History
{
Name = historyElement.Element(ns + "Name")?.Value,
Number = historyElement.Element(ns + "Number")?.Value,
Items = historyElement.Elements(ns + "Item").Select(x => new Item()
{
CreateDate = DateTime.Parse(x.Element(ns + "CreateDate")?.Value),
Type = x.Element(ns + "Type")?.Value,
Description = string.Join(",", x.Elements(ns + "Description").Select(elem=>elem.Value))
}).ToList()
}).FirstOrDefault();
}
}
}
If you dont wan't to care about finding the namespace, you might want to try the following:
var document = XDocument.Parse(xmlString);
var historyObject2 = document.Root.Descendants()
.Where(x=>x.Name.LocalName == "History")
.Select(historyElement => new History
{
Name = historyElement.Element(historyElement.Name.Namespace + "Name")?.Value,
Number = historyElement.Element(historyElement.Name.Namespace+ "Number")?.Value,
Items = historyElement.Elements(historyElement.Name.Namespace + "Item").Select(x => new Item()
{
//CreateDate = DateTime.Parse(x.Element("CreateDate")?.Value),
Type = x.Element(historyElement.Name.Namespace + "Type")?.Value,
Description = string.Join(",", x.Elements(historyElement.Name.Namespace + "Description").Select(elem => elem.Value))
}).ToList()
}).FirstOrDefault();

LINQ to XML join with idref

I have a XML structure like the following:
[...]
<Fruits>
<Fruit>
<Specification>id_1001_0</Specification>
<Weight>23</Weight>
</Fruit>
</Fruits>
<FruitSpecification id="id_1001_0">
<Type>Apple</Type>
</FruitSpecification>
[...]
I want to use Linq to XML to read this into (non-anonymous) objects.
Let´s say i have the following code:
var fruits = from xele in xDoc.Root.Element("Fruits").Elements("Fruit")
select new Fruit()
{
Weight = xele.Element("Weight").Value
}
How can i extend the query to join the correct FruitSpecification tag?
The goal would be to be able to write this:
var fruits = from xele in xDoc.Root.Element("Fruits").Elements("Fruit")
//some join?
select new Fruit()
{
Weight = xele.Element("Weight").Value,
Type = xjoinedelement.Element("Type").Value
}
I hope this is understandable, i made this "fruit" sample up, my actual XML is much more complicated...
Yes, simple join will do the trick:
var fruits =
from f in xdoc.Root.Element("Fruits").Elements("Fruit")
join fs in xdoc.Root.Elements("FruitSpecification")
on (string)f.Element("Specification") equals (string)fs.Attribute("id")
select new Fruit()
{
Weight = (int)f.Element("Weight"),
Type = (string)fs.Element("Type")
};
Fruit class definition:
public class Fruit
{
public int Weight { get; set; }
public string Type { get; set; }
}
Try this:
class Fruit
{
public int Weight { get; set; }
public string Type { get; set; }
}
string xml = #"
<root>
<Fruits>
<Fruit>
<Specification>id_1001_0</Specification>
<Weight>23</Weight>
</Fruit>
<Fruit>
<Specification>id_1002_0</Specification>
<Weight>25</Weight>
</Fruit>
</Fruits>
<FruitSpecification id='id_1001_0'>
<Type>Apple</Type>
</FruitSpecification>
<FruitSpecification id='id_1002_0'>
<Type>Orange</Type>
</FruitSpecification>
</root>";
XElement element = XElement.Parse(xml);
IEnumerable<XElement> xFruites = element.Descendants("Fruit");
IEnumerable<XElement> xFruitSpecifications = element.Descendants("FruitSpecification");
IEnumerable<Fruit> frits = xFruites.Join(
xFruitSpecifications,
f => f.Element("Specification").Value,
s => s.Attribute("id").Value,
(f, s) =>
new Fruit
{
Type = s.Element("Type").Value,
Weight = int.Parse(f.Element("Weight").Value)
});

XML Parsing to c# objects

I am trying to do a XML parser which will extract data from a website using REST service, the protocol for communication is HTTP, the data I get is in XML format, and I get to the data I need after several requests to different addresses on the server. I need to parse this data to c# objects so I can operate with them lately.
The information on the server is on 5 levels( I am willing to make work only 4 of them for know)
1-List of vendors
2-List of groups
3-List of subgroups
4-List of products
5-List of full information about products
After I get to 4th level I need to check if the product is in my database or if it has different details so I can add or update it.
With "GET" request to a server I get XML with this structure:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<vendors>
<vendor>
<id>someID</id>
<name>someName</name>
</vendor>
<vendor>
<id>someId1</id>
<name>somename1</name>
</vendor>
</vendors>
XML structure for groups is the same :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<groups vendor_id="43153185318">
<group>
<id>someID</id>
<name>someName</name>
</group>
<group>
<id>someId1</id>
<name>somename1</name>
</group>
The XML structure is analogical for subgroups and products, except that for products I have more elements like catalog_num, price etc.
I made the classes as follows :
public class VendorList
{
public List<Vendor> vendor_list { get; set; }
public VendorList()
{
vendor_list = new List<Vendor>();
}
}
public class Vendor
{
public string id { get; set; }
public string name { get; set; }
public List<Group> groups_list { get; set; }
public Vendor()
{
id = "N/A";
name = "N/A";
groups_list = new List<Group>();
}
}
public class Group
{
public string id { get; set; }
public string name { get; set; }
public List<SubGroup> subgroup_list { get; set; }
public Group()
{
id = "N/A";
name = "N/A";
subgroup_list = new List<SubGroup>();
}
}
public class SubGroup
{
public string id { get; set; }
public string name { get; set; }
public List<Product> product_list { get; set; }
public SubGroup()
{
id = "N/A";
name = "N/A";
product_list = new List<Product>();
}
}
public class Product
{
public string available { get; set; }
public string catalog_num { get; set; }
public string code { get; set; }
public string currency { get; set; }
public string description { get; set; }
public string haracteristics { get; set; }
public string product_id { get; set; }
public string model { get; set; }
public string name { get; set; }
public string price { get; set; }
public string price_dds { get; set; }
public string picture_url { get; set; }
public Product()
{
available = "N/A";
catalog_num = "N/A";
code = "N/A";
currency = "N/A";
description = "N/A";
haracteristics = "N/A";
product_id = "N/A";
model = "N/A";
name = "N/A";
price = "N/A";
price_dds = "N/A";
picture_url = "N/A";
}
}
and the Parser method like this :
public static void FillVendor(string url)
{
string result = GetXMLstream(url);
var vendors = new VendorList();
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(result));
doc.Save(#"D:/proba/proba.xml");
XDocument d = XDocument.Load(#"D:/proba/proba.xml");
vendors.vendor_list = (from c in d.Descendants("vendor")
select new Vendor()
{
id = c.Element("id").Value,
name = c.Element("name").Value
}).ToList<Vendor>();
foreach (Vendor v in vendors.vendor_list)
{
FillGroups(v.id);
}
}
public static void FillGroups(string vendorID)
{
string url = "main address" + vendorID;
string result = GetXMLstream(url);
var group = new Vendor();
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(result));
doc.Save(#"D:/proba/proba1.xml");
XDocument d = XDocument.Load(#"D:/proba/proba1.xml");
group.groups_list = (from g in d.Descendants("group")
select new Group()
{
id = g.Element("id").Value,
name = g.Element("name").Value
}).ToList<Group>();
foreach (Group g in group.groups_list)
{
FillSubGroup(vendorID, g.id);
}
}
public static void FillSubGroup(string vendorID, string groupID)
{
string url = "main address" + vendorID+"/"+groupID;
string result = GetXMLstream(url);
var subgroup = new Group();
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(result));
doc.Save(#"D:/proba/proba2.xml");
XDocument d = XDocument.Load(#"D:/proba/proba2.xml");
subgroup.subgroup_list = (from g in d.Descendants("subgroup")
select new SubGroup()
{
id = g.Element("id").Value,
name = g.Element("name").Value
}).ToList<SubGroup>();
foreach (SubGroup sb in subgroup.subgroup_list)
{
FillProduct(vendorID, groupID, sb.id);
}
}
public static void FillProduct(string vendorID,string groupID,string subgroupID)
{
string url = "main address" + vendorID + "/" + groupID+"/"+subgroupID;
string result = GetXMLstream(url);
var product = new SubGroup();
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(result));
doc.Save(#"D:/proba/proba2.xml");
XDocument d = XDocument.Load(#"D:/proba/proba2.xml");
product.product_list = (from g in d.Descendants("subgroup")
select new Product()
{
available = g.Element("available").Value,
catalog_num = g.Element("catalog_num").Value,
code = g.Element("code").Value,
currency = g.Element("currency").Value,
description = g.Element("description").Value,
haracteristics = g.Element("haracteristics").Value,
product_id = g.Element("id").Value,
model = g.Element("model").Value,
name = g.Element("name").Value,
price = g.Element("price").Value,
price_dds = g.Element("price_dds").Value,
picture_url = g.Element("small_picture").Value,
}).ToList<Product>();
}
But after finishing parsing I try to check if my Lists are populated with objects, but I get an error which says that they are null "NullReferenceException"
So my question is did I make classes properly and is my parsing method right ( you can suggest how to parse the xml without creating a file on my computer) and if I didn't where is my mistake and how should I make it work properly?
Thanks in advance!
modify this line add 's'( vendor -> vendors)
-> vendors.vendor_list = (from c in d.Descendants("vendor")
and the same case for group -> groups
Instead of making the classes yourself.
Create a properly formatted XML Schema either manually or with Visual Studio and then from that XSD File you've created generate your C# Classes.

Categories

Resources