How retrieve deeper siblings using LINQ to XML? - c#

I have an XML structure as follows. I need to extract "Value" and "String" by matching the command attributes? How to write LINQ for this?
<Root>
<Command val="1001" type="sync">
<Status>
<DataList>
<Info>
<Value>1</Value>
<String>Sample String 1 is set</String>
</Info>
<Info>
<Value>2</Value>
<String>Sample String 2 is set</String>
</Info>
<Info>
<Value>3</Value>
<String>Sample String 3 is set</String>
</Info>
</DataList>
</Status>
<Command>
</Root>
I tried something as below but exception occurred while running.
lst = (
from command in xmlDoc.Descendants("Command")
.Descendants("Status")
.Descendants("DataList")
select new EnumList
{
val = command.Element("Value").Value,
stringVal = command.Element("String").Value,
})
.ToList();

Try
lst = (
from command in xmlDoc.Descendants("Info")
select new EnumList
{
val = command.Element("Value").Value,
stringVal = command.Element("String").Value,
})
.ToList();
and you have error in xml sample (no close tag Command), change it to
</Command>
</Root>

Related

Linq to XML read method throws NullReferenceException using C# in VS2017

This is my XML structure:
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
</results>
</report>
</root>
I want to read the information within result and create an object to set it's attribute to the value. This is my code to do this:
Read from XML:
XDocument rootDocument = XDocument.Load(filePath)
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name").Value
};
Create new object:
foreach(var r in vul)
{
Object a = new Object()
{
Name = r.Name
};
}
The Problem is that this produces a NullReferenceException when calling
rootDocument.Descendants("result")
Thank you in advance!
I am guessing that your select is trying to access an element that does not exist.
For example there is a missing name element from your original xml:
e.g. This xml will fail
<root>
<report>
<results>
<result>
<name> name 1 </name>
</result>
<result>
<name> name 2 </name>
</result>
<result>
<surname> name 2 </surname> //will fail when loop gets here
</result>
</results>
</report>
</root>
If it is possible that name element will not always exist you can update the code to handle null
var vul = from r in rootDocument.Descendants("result")
select new
{
Name = r.Element("name")?.Value, //this will return null
};
..as r.Name can be null you need to account for this in the for loop.
foreach (var r in vul)
{
string Name = r.Name ?? string.empty;
}
I found the cause of this Error. I don't know why but calling the value of an element causes it. I changed
Name = r.Element("name").Value
to
Name= r.Element("name")

How to get the right elements with XDocument query

I am having trouble to get the query right when trying to parse a XML file using XDocument.
What I want:
Give me all Values of "Name" Elements of Step with id = "id_3" So the result should be a list containing "Name of part 1, Name of part 2, Name of part 3, Name of part 4.
Input XML:
<MyXML>
<Step id="id_1" type="type1">
<Name>Some Name</Name>
<Location>1</Location>
<Quantity>1</Quantity>
</Step>
<Step id="id_2" type="type1">
<Name>>Some Name</Name>
<Location>2</Location>
<Quantity>1</Quantity>
</Step>
<Step id="id_3" type="type2">
<Instruction>This is some text</Instruction>
<Component>
<Name>Name of part 1</Name> // --> I want this value
<Transition_Start>-0.2,0.01,0.0</Transition_Start>
<Transition_End>0,0.01,0.0</Transition_End>
</Component>
<Component>
<Name>Name of part 2</Name> // --> and this
<Transition_Start>0.2,0.01,0</Transition_Start>
<Transition_End>0,0.01,0</Transition_End>
</Component>
<Component>
<Name>Name of part 3</Name> // --> and this
<Transition_Start>0.05,0.1004,0.0333</Transition_Start>
<Transition_End>-0.0803,0.1004,0.0333</Transition_End>
</Component>
<Component>
<Name>Name of part 4</Name> // --> and this
<Transition_Start>-0.0107,0.0383,-0.2328</Transition_Start>
<Transition_End>-0.0107,0.0383,-0.2328</Transition_End>
</Component>
</Step>
</MyXML>
I tried something like this (in Unity3D).
IEnumerable<XElement> e_nameOfObjects =
from el in myXMLDoc.Root.Elements ("Step")
where (string)el.Attribute ("id") == "id_" + currentStep
select el.Elements ("Component");
foreach (XElement e in e_nameOfObjects) {
Debug.Log (e.Element("Name"));
}
The error message:
Cannot implicitly convert type
System.Collections.Generic.IEnumerable>'
to System.Collections.Generic.IEnumerable'. An explicit conversion
exists (are you missing a cast?)
You can get all the Component by using the Elements method and then select Name inside those components like this:-
var names = myXMLDoc.Descendants("Step")
.Where(x => (string)x.Attribute("id") == "id_3")
.Elements("Component")
.Select(x => (string)x.Element("Name"));
Here, names will return IEnumerable<string> on which you can easily iterate through foreach loop.
Working Fiddle.

generate XML with nested parameter in c#.net

I want to generate an XML with parameter by parsing the following xml. i am working in c#.net.
<root>
<name1>
<names>
<id>5</id>
<class>space</class>
<from>Germany</from>
<to>France</to>
<through>
<via>
<id>4</id>
<route>Zurich<route>
</via>
<via>
<id>7</id>
<route>Vienna<route>
</via>
</through>
</names>
</name1>
<name2>
<newNames>
<id>8</id>
<path>Road</path>
<dest>USA</dest>
<through>
<route1>
<id>5</id>
<naviagte>Britain</naviagte>
</route1>
<route1>
<id>2</id>
<naviagte>Canada</naviagte>
</route1>
</through>
</newNames>
</name2>
</root>
i want to convert it like following-
<root>
<name1>
<names id = "5";class = "space"; from = "Germany" ; to = "France">
<through>
<via id = "4" ; route = "Zurich">
<via id = "7" ; route = "Vienna">
</through>
</names>
</name1>
<name2>
<newNames id = "8"; path = "Road"; dest = "USA">
<newNames id = "8"; path = "Road"; dest = "USA">
<through>
<route1 = id = "5" ; naviagte = "Britain">
<route1 = id = "2" ; naviagte = "Canada">
</through>
</name2>
</root>
i have tried the following codes.
var doc = XDocument.Load("xml_file.xml");
Console.WriteLine(doc.ToString());
var names = doc.Descendants("name");
var newRootElement = new XElement("root");
foreach (var name in names)
{
var newNameElement = new XElement(name.Name);
foreach (var element in name.Elements())
{
newNameElement.SetAttributeValue(element.Name, element.Value);
}
newRootElement.Add(newNameElement);
}
Console.WriteLine(newRootElement.ToString());
newRootElement.Save("converted_xml_file.xml");
but i can't parse all the nodes. could anyone give me any hints or correction in my codes please ?
I suppose you have closed route tags in your input xml. If so, then you can build new xml by querying original xml and replacing elements with attributes:
var xdoc = XDocument.Load("xml_file.xml");
var root =
new XElement("root",
from name in xdoc.Root.Elements()
select new XElement(name.Name,
from names in name.Elements()
select new XElement(names.Name,
from namesElement in names.Elements()
where namesElement.Name.LocalName != "through"
select new XAttribute(namesElement.Name.LocalName, (string)namesElement),
new XElement("through",
from route in names.Element("through").Elements()
select new XElement(route.Name,
from routeElement in route.Elements()
select new XAttribute(routeElement.Name.LocalName, (string)routeElement))))));
This code produces following xml:
<root>
<name1>
<names id="5" class="space" from="Germany" to="France">
<through>
<via id="4" route="Zurich" />
<via id="7" route="Vienna" />
</through>
</names>
</name1>
<name2>
<newNames id="8" path="Road" dest="USA">
<through>
<route1 id="5" naviagte="Britain" />
<route1 id="2" naviagte="Canada" />
</through>
</newNames>
</name2>
</root>

Xml simplification/extraction of distinct values - possible LINQ

Sorry for this long post....But i have a headache from this task.
I have a mile long xml document where I need to extract a list, use distinct values, and pass for transformation to web.
I have completed the task using xslt and keys, but the effort is forcing the server to its knees.
Description:
hundreds of products in xml, all with a number of named and Id'ed cattegories, all categories with at least one subcategory with name and id.
The categories are unique with ID, all subcategories are unique WITHIN that category:
Simplified example form the huge file (left our tons of info irrelevant to the task):
<?xml version="1.0" encoding="utf-8"?>
<root>
<productlist>
<product id="1">
<name>Some Product</name>
<categorylist>
<category id="1">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
<subcat id="2">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="2">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="3">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
</categorylist>
</product>
<product id="2">
<name>Some Product</name>
<categorylist>
<category id="1">
<name>cat1</name>
<subcategories>
<subcat id="2">
<name>subcat2</name>
</subcat>
<subcat id="4">
<name>subcat4</name>
</subcat>
</subcategories>
</category>
<category id="2">
<name>cat2</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="3">
<name>cat3</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
</categorylist>
</product>
</productlist>
</root>
DESIRED RESULT:
<?xml version="1.0" encoding="utf-8"?>
<root>
<maincat id="1">
<name>cat1</name>
<subcat id="1"><name>subcat1</name></subcat>
<subcat id="2"><name>subcat2</name></subcat>
<subcat id="3"><name>subcat3</name></subcat>
</maincat>
<maincat id="2">
<name>cat2</name>
<subcat id="1"><name>differentsubcat1</name></subcat>
<subcat id="2"><name>differentsubcat2</name></subcat>
<subcat id="3"><name>differentsubcat3</name></subcat>
</maincat>
<maincat id="2">
<name>cat2</name>
<subcat id="1"><name>differentsubcat1</name></subcat>
<subcat id="2"><name>differentsubcat2</name></subcat>
<subcat id="3"><name>differentsubcat3</name></subcat>
</maincat>
</root>
(original will from 2000 products produce 10 categories with from 5 to 15 subcategories)
Things tried:
Xslt with keys - works fine, but pooooor performance
Played around with linq:
IEnumerable<XElement> mainCats =
from Category1 in doc.Descendants("product").Descendants("category") select Category1;
var cDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cDoc.Root.Add(mainCats);
cachedCategoryDoc = cDoc.ToString();
Result was a "categories only" (not distinct values of categories or subcategories)
Applied the same xlst to that, and got fairly better performance..... but still far from usable...
Can i apply some sort of magic with the linq statement to have the desired output??
A truckload of good karma goes out to the ones that can point me in det right direction..
//Steen
NOTE:
I am not stuck on using linq/XDocument if anyone has better options
Currently on .net 3.5, can switch to 4 if needed
If I understood your question corectly, here's a LINQ atempt.
The query below parses your XML data and creates a custom type which represents a category and contains the subcategories of that element.
After parsing, the data is grouped by category Id to get distinct subcategories for each category.
var doc = XElement.Load("path to the file");
var results = doc.Descendants("category")
.Select(cat => new
{
Id = cat.Attribute("id").Value,
Name = cat.Descendants("name").First().Value,
Subcategories = cat.Descendants("subcat")
.Select(subcat => new
{
Id = subcat.Attribute("id").Value,
Name = subcat.Descendants("name").First().Value
})
})
.GroupBy(x=>x.Id)
.Select(g=>new
{
Id = g.Key,
Name = g.First().Name,
Subcategories = g.SelectMany(x=>x.Subcategories).Distinct()
});
From the results above you can create your document using the code below:
var cdoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cdoc.Root.Add(
results.Select(x=>
{
var element = new XElement("maincat", new XAttribute("id", x.Id));
element.Add(new XElement("name", x.Name));
element.Add(x.Subcategories.Select(c=>
{
var subcat = new XElement("subcat", new XAttribute("id", c.Id));
subcat.Add(new XElement("name", c.Name));
return subcat;
}).ToArray());
return element;
}));
Try this i have done something for it.. attributes are missing you can add them using XElement ctor
var doc = XDocument.Load(reader);
IEnumerable<XElement> mainCats =
doc.Descendants("product").Descendants("category").Select(r =>
new XElement("maincat", new XElement("name", r.Element("name").Value),
r.Descendants("subcat").Select(s => new XElement("subcat", new XElement("name", s.Element("name").Value)))));
var cDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cDoc.Root.Add(mainCats);
var cachedCategoryDoc = cDoc.ToString();
Regards.
This will parse your xml into a dictionary of categories with all the distinct subcategory names. It uses XPath from this library: https://github.com/ChuckSavage/XmlLib/
XElement root = XElement.Load(file);
string[] cats = root.XGet("//category/name", string.Empty).Distinct().ToArray();
Dictionary<string, string[]> dict = new Dictionary<string, string[]>();
foreach (string cat in cats)
{
// Get all the categories by name and their subcat names
string[] subs = root
.XGet("//category[name={0}]/subcategories/subcat/name", string.Empty, cat)
.Distinct().ToArray();
dict.Add(cat, subs);
}
Or the parsing as one statement:
Dictionary<string, string[]> dict = root
.XGet("//category/name", string.Empty)
.Distinct()
.ToDictionary(cat => cat, cat => root
.XGet("//category[name={0}]/subcategories/subcat/name", string.Empty, cat)
.Distinct().ToArray());
I give you the task of assembling your resulting xml from the dictionary.

XPathSelectElements => string representation

Hi because of a misunderstanding I want to ask my question again.
I have the following XML structure:
<?xml version="1.0" encoding="utf-8"?>
<xml>
<root>
<Item>
<taxids>
<string>330</string>
<string>374</string>
<string>723</string>
<string>1087</string>
<string>1118</string>
<string>1121</string>
</taxids>
</Item>
</root>
</xml>
I need to get all the string nodes from the xml file to a string variable.
I want to get a string like this:
<taxids><string>330</string><string>374</string><string>723</string><string>1087</string><string>1118</string><string>1121</string></taxids>
My linq to xml:
var query = from ip in doc.XPathSelectElements("xml/root/Item")
select ip.XPathSelectElement("taxids").ToString();
But I am getting the following in one row of the variable query:
"System.Xml.XPath.XPathEvaluator+<EvaluateIterator>d__0`1[System.Xml.Linq.XElement]"
Is this possible?
Thanks!
Try this:
var result = doc.Element("xml")
.Element("root")
.Element("Item")
.Element("taxids")
.ToString(SaveOptions.DisableFormatting);
// result == "<taxids><string>330</string><string>374</string> ... </taxids>"

Categories