I am trying to read in data from an XML file, which I can do with the code below.
How would I then sort the data by name alphabetically to be used on an electronic menu app?
Could someone show me how to do this?
The code block below does work, but I have no idea how to sort the data alphabetically by node type.
For instance, I would like to sort by "name" alphabetically.
Other sorting methods could include sorting by price or category, etc.
Here is the XML data structure:
<?xml version="1.0" encoding="UTF-8" ?>
<base>
<menu>
<item>
<category></category>
<name></name>
<price></price>
<featured></featured>
<code></code>
<timestart></timestart>
<timeend></timeend>
</item>
</menu>
</base>
Here is the code:
public void XML_Get_MenuData()
{
try
{
XmlDocument myDoc2 = new XmlDocument();
myDoc2.Load("\\user\\today1.xml");
XmlNodeList itemNodes = myDoc2.GetElementsByTagName("item");
ushort i = 0;
Menu_Items = 0;
foreach (XmlNode sINode in itemNodes)
{
Category[i] = sINode["category"].InnerText;
Console.PrintLine("category: {0}", Category[i]);
ItemName[i] = sINode["name"].InnerText;
Console.PrintLine("name: {0}", ItemName[i]);
Price[i] = sINode["price"].InnerText;
Console.PrintLine("price: {0}", Price[i]);
Featured[i] = sINode["featured"].InnerText;
Console.PrintLine("featured: {0}", Featured[i]);
if (Featured[i] == "yes")
{
uFeatured[i] = 1;
}
else
{
uFeatured[i] = 0;
}
Code[i] = sINode["code"].InnerText;
Console.PrintLine("code: {0}", Code[i]);
TimeStart[i] = sINode["timestart"].InnerText;
Console.PrintLine("timestart: {0}", TimeStart[i]);
TimeEnd[i] = sINode["timeend"].InnerText;
Console.PrintLine("timeend: {0}", TimeEnd[i]);
i++;
}
Menu_Items = i;
Console.PrintLine("Menu Items: {0}", Menu_Items);
}
catch
{
Console.PrintLine("missed Menu Items: {0}");
}
}
You should look into the lovely LINQ to XML, which allows you to do stuff like this:
var Doc = XDocument.Load("PathToYourXml");
var OrderedItems = Doc.Descendants("item").OrderBy(x => x.Element("name").Value);
OrderedItems contains your objects alphabetically ordered by their name, in the shape of a bunch (bunch = IOrderedEnumerable) of XElements.
If you wanted do make the order descending, you'd just use OrderByDescending:
var OrderedItems = Doc.Descendants("item").OrderByDescending(x => x.Element("name").Value);
As a side note, right now you're iterating your items and printing them, and apparently storing the data horizontally in arrays. Mapping them to a class instead would be useful to avoid having to use magical strings like "item" and "name", which is error-prone. I'd encourage you to create a model for your items and investigate a bit on how to parse the XML into a list of your object, using something like an XmlSerializer, and maybe taking a look at questions like this, where you'll find some alternatives.
Related
I'm trying to read through .xml -file and get information out of there. Here is a sample of the .xml -file I have:
<?xml version="1.0" encoding="UTF-8"?>
<XmlFile>
<xmlsource>
<Name>TestXml</Name>
<filename>MyXmlFile.xml</filename>
<Information Key="GeneralInfo"/>
<Products>
<Product>
<ProductName>Product1</ProductName>
<Name Key="SomeName"/>
<Usages>
<Usage>
<Specs>
<Spec1 Key="Moving"/>
<Spec2 Key="Lifting"/>
</Specs>
<Info1>
<MovingInfo1>yes</MovingInfo1>
</Info1>
<Info2>Noup</Info2>
<MoreSpecs>
<ProductModel1>
<DetInfo1>DetInfo1</DetInfo1>
<DetInfo2>DetInfo2</DetInfo2>
</ProductModel11>
</MoreSpecs>
</Usage>
</Usages>
</Product>
<Product>
<ProductName>Product2</ProductName>
<Name Key="SomeName2"/>
<Usages>
<Usage>
<Specs>
<Spec1 Key="Moving"/>
<Spec2 Key="Lifting"/>
</Specs>
<Info1>
<MovingInfo1>not</MovingInfo1>
</Info1>
<Info2>Yes</Info2>
<MoreSpecs>
<ProductModel1>
<DetInfo1>DetInfo1</DetInfo1>
</ProductModel1>
</MoreSpecs>
</Usage>
<Usage>
<Specs>
<Spec1 Key="Turning"/>
</Specs>
<Info1>
<TurningInfo1>Infoooo</TurningInfo1>
</Info1>
<Info2>No</Info2>
<MoreSpecs>
<ProductType1>
<DetInfo1>DetInfo1</DetInfo1>
</ProductType1>
</MoreSpecs>
</Usage>
</Usages>
</Product>
</Products>
</xmlsource>
(This is just a sample, original file has a lot more data in it.)
I want to know only the values of ProductName and Spec1. As you can see from the sample, 'Product2' has two different values of Spec1: 'Moving' and 'Turning'.
What I'm trying to achieve:
Read ProductName ("Product1") from the first <Product> and then the Spec1 ("Moving"), then do something with the information. After that, move to next <Product>, read ProductName ("Product2"), Spec1 ("Moving") and the other Spec1 ("Turning"), and skipping all the other possible Spec values - meaning, that I want only Spec1 value. And so on go through the hole file.
Here is what I have tried to do:
public void getNodes(string filepath)
{
xmlFilePath = filepath;
XmlDocument xDoc = new XmlDocument();
xDoc.Load(xmlFilePath);
XmlNodeList products = xDoc.SelectNodes("//Product");
XmlNodeList productnames = xDoc.SelectNodes("//Product/ProductName");
XmlNodeList specs = xDoc.SelectNodes("//Product//Spec1");
AllocConsole();
Console.WriteLine(products.Count);
Console.WriteLine(specs.Count);
foreach (XmlNode xn in specs)
{
XmlAttributeCollection spec1Atts = xn.Attributes;
Console.WriteLine(spec1Atts["Key"].Value.ToString());
}
for (int i = 0; i < products.Count; i++)
{
Console.WriteLine(products.Item(i).InnerText);
Console.ReadLine();
}
}
This is the closest I have got (closest to what I'm trying to do).
There, first I have load the .xml -file.
Then, in lines containing XmlNodeList etc. I'm filtering with those requirements.
Here (below), is being checked the amount of products specs:
Console.WriteLine(products.Count);
Console.WriteLine(specs.Count);
Finally I'm printing out the values which has been read. With this, the print-out is obviously:
First comes out the amounts
Second comes the specs
And finally the productnames
As said above, I want ProductNames and Spec1's to be "linked" together.
I tried many methods e.g. shown in here: Reading multiple child nodes of xml file
Somehow I couldn't make any example work in my situation. Maybe it's because in my case, there is so deep parent-child pairs?
I can't change the structure of the .xml -file. If I could, I would have changed it already...
So, my question is: Could someone show me a hint/way how to achieve my goal? Thanks in advance.
What you need to do is traverse the hierarchy. In the revised code below, I find the ProductName, then within that, I look for the next node and so on until I find the Specs that correspond to that product.
private static void getNodes(string filePath)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filePath);
var productNodes = xmlDoc.SelectNodes("//Product");
if (productNodes != null)
{
foreach (XmlNode product in productNodes)
{
var childNodes = product.ChildNodes;
foreach (XmlNode child in childNodes)
{
if (child.Name == "ProductName")
{
Console.WriteLine(child.InnerText);
}
else if (child.Name == "Usages")
{
var childNodes2 = child.ChildNodes;
foreach (XmlNode child2 in childNodes2)
{
if (child2.Name == "Usage")
{
var childNodes3 = child2.ChildNodes;
foreach (XmlNode child3 in childNodes3)
{
if (child3.Name == "Specs")
{
var childNodes4 = child3.ChildNodes;
foreach (XmlNode child4 in childNodes4)
{
foreach (XmlNode a in child4.Attributes)
{
Console.WriteLine($" {a.InnerText}");
}
}
}
}
}
}
}
}
}
}
Console.ReadLine();
}
Hope that helps. If so, please vote for my answer because I need the reputation. Thank you.
If I understand your question correctly, you are trying to retrieve the specs for each product and you want to use them together. (Apologies if I don't fully get it). if that is the case you can try probing the product element directly in your example loop. like
for (int i = 0; i < products.Count; i++) {
var specs = products[i].SelectNodes("Usages/Usage/Specs")[0].ChildNodes;
for (int j = 0; j < specs.Count; j++)
Console.WriteLine("{0}->{1}", products[i].FirstChild.InnerText, specs[j].Attributes["Key"].Value);
}
I hope this helps
I have to extract values belonging to certain elements in an XML file and this is what I ended up with.
XDocument doc = XDocument.Load("request.xml");
var year = (string)doc.Descendants("year").FirstOrDefault();
var id = (string)doc.Descendants("id").FirstOrDefault();
I'm guessing that for each statement I'm iterating through the entire file looking for the first occurrence of the element called year/id. Is this the correct way to do this? It seems like there has to be a way where one would avoid unnecessary iterations. I know what I'm looking for and I know that the elements are going to be there even if the values may be null.
I'm thinking in the lines of a select statement with both "year" and "id" as conditions.
For clearance, I'm looking for certain elements and their respective values. There'll most likely be multiple occurrences of the same element but FirstOrDefault() is fine for that.
Further clarification:
As requested by the legend Jon Skeet, I'll try to clarify further. The XML document contains fields such as <year>2015</year> and <id>123032</id> and I need the values. I know which elements I'm looking for, and that they're going to be there. In the sample XML below, I would like to get 2015, The Emperor, something and 30.
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<make>Apple</make>
<year>2015</year>
<customer>
<name>The Emperor</name>
<level2>
<information>something</information>
</level2>
<age>30</age>
</customer>
A code that doesn't parse the whole xml twice would be like:
XDocument doc = XDocument.Load("request.xml");
string year = null;
string id = null;
bool yearFound = false, idFound = false;
foreach (XElement ele in doc.Descendants())
{
if (!yearFound && ele.Name == "year")
{
year = (string)ele;
yearFound = true;
}
else if (!idFound && ele.Name == "id")
{
id = (string)ele;
idFound = true;
}
if (yearFound && idFound)
{
break;
}
}
As you can see you are trading lines of code for speed :-) I do feel the code is still quite readable.
if you really need to optimize up to the last line of code, you could put the names of the elements in two variables (because otherwise there will be many temporary XName creation)
// before the foreach
XName yearName = "year";
XName idName = "id";
and then
if (!yearFound && ele.Name == yearName)
...
if (!idFound && ele.Name == idName)
I have an XML file in the following format
<?xml version="1.0" ?>
<AA someattrib="xyz">
<BB someOtherAttrib="xyz">
<Title></Title>
<CC>
<myNode rowid="">
<subNode1></subNode1>
<subNode2></subNode2>
<nodeOfInterest></nodeOfInterest>
</myNode >
<myNode rowid="">
<subNode1> </subNode1>
</myNode>
</CC>
</BB>
</AA>
I want to use Linq to pick out one node by the name 'MyNode' where the rowid is a particular number that I will be getting from a collection in an object. Once I get myNode I want to update the value of the child nodeOfInterest if it is present. If not present, then I would like to add it. Once done I want to save the file.
This is what I have at the moment but it may not be the right approach.
foreach (User employee in Users)
{
XPathNavigator node = xNav.SelectSingleNode("/AA/BB/CC/myNode[#rowid = '"+employee.ID.ToString()+"']");
XPathNodeIterator nodeIterator= node.SelectChildren("nodeOfInterest", "");
if (nodeIterator.Count == 1)
{
}
else
{
}
}
Is there a way this can be done using a direct join between the List and the xmldoc in memory? This will be a large list and an equally large xml file. I dont think running a loop and calling selectSingleNode is the most efficient way.
Thanks for your inputs
Well one starting point would be to create a Dictionary<string, XElement> mapping the row ID to the element:
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
Then:
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
// Use myNode
}
else
{
// Employee not found
}
}
Personally I prefer using the selection methods provided by LINQ to XML (Elements, Element, Descendants etc) rather than SelectSingleNode, SelectChildren etc.
The full answer, with help from Jon's replies...
var doc = XDocument.Load("thefile.xml");
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
XElement nodeOfInterest = myNode.Elements("nodeOfInterest").FirstOrDefault();
if (nodeOfInterest != null)
{
nodeOfInterest.Value = "update with this value";
}
else
{
XElement nodeOfInterest = new XElement("nodeOfInterest", "Add nodeOfInterest with this value");
myNode.Add(newElement);
}
}
}
doc.Save("TheFile.xml");
I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}
I'd like to get all the element name from a xml file, for example the xml file is,
<BookStore>
<BookStoreInfo>
<Address />
<Tel />
<Fax />
<BookStoreInfo>
<Book>
<BookName />
<ISBN />
<PublishDate />
</Book>
<Book>
....
</Book>
</BookStore>
I would like to get the element's name of "BookName". "ISBN" and "PublishDate " and only those names, not include " BookStoreInfo" and its child node's name
I tried several ways, but doesn't work, how can I do it?
Well, with XDocument and LINQ-to-XML:
foreach(var name in doc.Root.DescendantNodes().OfType<XElement>()
.Select(x => x.Name).Distinct())
{
Console.WriteLine(name);
}
There are lots of similar routes, though.
Using XPath
XmlDocument xdoc = new XmlDocument();
xdoc.Load(something);
XmlNodeList list = xdoc.SelectNodes("//BookStore");
gives you a list with all nodes in the document named BookStore
I agree with Adam, the ideal condition is to have a schema that defines the content of xml document. However, sometimes this is not possible. Here is a simple method for iterating all of the nodes of an xml document and using a dictionary to store the unique local names. I like to keep track of the depth of each local name, so I use a list of int to store the depth. Note that the XmlReader is "easy on the memory" since it does not load the entire document as the XmlDocument does. In some instances it makes little difference because the size of the xml data is small. In the following example, an 18.5MB file is read with an XmlReader. Using an XmlDocument to load this data would have been less effecient than using an XmlReader to read and sample its contents.
string documentPath = #"C:\Docs\cim_schema_2.18.1-Final-XMLAll\all_classes.xml";
Dictionary<string, List<int>> nodeTable = new Dictionary<string, List<int>>();
using (XmlReader reader = XmlReader.Create(documentPath))
{
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element)
{
if (!nodeTable.ContainsKey(reader.LocalName))
{
nodeTable.Add(reader.LocalName, new List<int>(new int[] { reader.Depth }));
}
else if (!nodeTable[reader.LocalName].Contains(reader.Depth))
{
nodeTable[reader.LocalName].Add(reader.Depth);
}
}
reader.Read();
}
}
Console.WriteLine("The node table has {0} items.",nodeTable.Count);
foreach (KeyValuePair<string, List<int>> kv in nodeTable)
{
Console.WriteLine("{0} [{1}]",kv.Key, kv.Value.Count);
for (int i = 0; i < kv.Value.Count; i++)
{
if (i < kv.Value.Count-1)
{
Console.Write("{0}, ", kv.Value[i]);
}
else
{
Console.WriteLine(kv.Value[i]);
}
}
}
The purists way of doing this (and, to be fair, the right way) would be to have a schema contract definition and read it in that way. That being said, you could do something like this...
List<string> nodeNames = new List<string>();
foreach(System.Xml.XmlNode node in doc.SelectNodes("BookStore/Book"))
{
foreach(System.Xml.XmlNode child in node.Children)
{
if(!nodeNames.Contains(child.Name)) nodeNames.Add(child.Name);
}
}
This is, admittedly, a rudimentary method for obtaining the list of distinct node names for the Book node's children, but you didn't specify much else in the way of your environment (if you have 3.5, you could use LINQ to XML to make this a little prettier, for example), but this should get the job done regardless of your environment.
If you're using C# 3.0, you can do the following:
var data = XElement.Load("c:/test.xml"); // change this to reflect location of your xml file
var allElementNames =
(from e in in data.Descendants()
select e.Name).Distinct();
You can try doing it using XPATH.
XmlDocument doc = new XmlDocument();
doc.LoadXml("xml string");
XmlNodeList list = doc.SelectNodes("//BookStore/Book");
If BookStore is ur root element then u can try following code
XmlDocument doc = new XmlDocument();
doc.Load(configPath);
XmlNodeList list = doc.DocumentElement.GetElementsByTagName("Book");
if (list.Count != 0)
{
for (int i = 0; i < list[0].ChildNodes.Count; i++)
{
XmlNode child = list[0].ChildNodes[i];
}
}