Find a node in an XML file - performance improvement in C# - c#

Say I need to find a particular node in an XML file, using C#.
<node attribute="my-target-attribute">
"my-target-attribute" is a variable input at runtime.
The node is in no particular place in the XML file, I basically just need to scan the entire XML hierarchy until I find a node with the matching attribute.
Is there any way I can pre-process the XML so finding the node will be faster? I need to keep the original XML structure in place. The XML file could potentially have 10,000 nodes.

You can certainly preprocess the XML to make lookups faster:
Dictionary<string, XmlElement> elementMap = new Dictionary<string, XmlElement>();
AddElementToMap(doc.DocumentElement, elementMap);
...
private void AddElementToMap(XmlElement elm, Dictionary<string, XmlElement> elementMap)
{
elementMap[elm.GetAttribute("attribute")] = elm;
foreach (XmlElement child in elm.SelectNodes("node"))
{
AddElementToMap(child, elementMap);
}
}
Once you've done this, a lookup is simple:
XmlElement elm = elementMap[value];
That code assumes that every element in your document is named "node", that every one has an attribute named "attribute", and that all of the attribute values are unique. The code's more complicated if any of those conditions are untrue, but not exceptionally so.

You could use xslt to transform the xml so that the node is in a known depth. Then when you select with XPath, you can select accordingly without using the // operator.

With VTD-XML (http://vtd-xml.sf.net) you can index the XML document into VTD+XML,
which eliminate the overhead of parsing
http://www.codeproject.com/KB/XML/VTD-XML-indexing.aspx

similar to another answer, you can use xpath similar like selectNodes("//[#attribute='my-target-attribute']"). // will search nodes in all level depths.

Related

Parsing an XML file that can be singleline or multiline

I have an XML file that can be one-line:
<webshop><item></item><item></item></webshop>
or multiline:
<webshop>
<item>
</item>
<item>
</item>
</webshop>
or mixed:
<webshop>
<item></item>
<item></item>
</webshop>
Each tag also has a short variant like <webshop/> and <item/> where the tag is opened and closed in one pair of < > brackets.
each tag can appaer any amount of times, but the <item></item> or <item/> tag will only appaer inside <webshop> ... </webshop>. Also, the entire xml tag hierarchy is much larger then just these two tags (but I kept it simple for this question), and each tag can have attributes.
I'm trying to parse such an xmlfile using an xmlreader in c#, but I always run into a problem.
If I try:
while(reader.ReadToFollowing("webshop"))
{
Console.WriteLine("webshop");
//get attributes of webshop tag and do something...
while(reader.ReadToFollowing("item"))
{
Console.WriteLine("Item");
//get attributes of item tag and do something...
}
}
I never get all the data when the xml is singleline, mixed or the tags close themself (<item/> instead of <item></item>). Most of the time, the reader just stops after one instance of <webshop> or <item>
Is there a robust way to parse this xml, even if the exact lining is not known beforehand? I want to loop over all webshops, and for each webshop loop all over items, and then do something with this data.
Here's a very simple Linq to XML way to read your xml file:
var xml = #"<webshop><item></item><item></item></webshop>";
var reader = XDocument.Parse(xml);
var webshops = from w in reader.Elements("webshop")
select w;
foreach(var shop in webshops)
{
var items = from i in shop.Elements("item")
select i;
//can now grab any attributes of the items
}
Without more details on the attributes in these elements, I can't provide much more detail in an example, but I think this is enough to show you how it can be done.
If you aren't going to do any filtering and just want all of the webshop elements and then their constituent item subelements, you can simplify what I have above like so:
var webshops = reader.Elements("webshop");
foreach(var shop in webshops)
{
var items = shop.Elements("item");
//can now grab any attributes of the items
}
I originally included the more verbose way of structuring the queries in case you wanted to do any filtering or wanted to do something more complex then simply selecting the given elements. This simplified method will produce the same results as my first example.
Please take a look at the answer in this stack overflow discussion.
binding xml elements to model in MVC4
Basically, there are many ways to read xml files in you c# code. It all depend on what you are trying to achieve and how flexible it has to be. I personally prefer to XmlSeriealizer as it translate the xml into c# objects. the only downside is that you have to define classes for the xml to translate into.

How to find an XPath query to Element/Element without namespaces (XmlSerializer, fragment)?

Assume this simple XML fragment in which there may or may not be the xml declaration and has exactly one NodeElement as a root node, followed by exactly one other NodeElement, which may contain an assortment of various number of different kinds of elements.
<?xml version="1.0">
<NodeElement xmlns="xyz">
<NodeElement xmlns="">
<SomeElement></SomeElement>
</NodeElement>
</NodeElement>
How could I go about selecting the inner NodeElement and its contents without the namespace? For instance, "//*[local-name()='NodeElement/NodeElement[1]']" (and other variations I've tried) doesn't seem to yield results.
As for in general the thing that I'm really trying to accomplish is to Deserialize a fragment of a larger XML document contained in a XmlDocument. Something like the following
var doc = new XmlDocument();
doc.LoadXml(File.ReadAllText(#"trickynodefile.xml")); //ReadAllText to avoid Unicode trouble.
var n = doc.SelectSingleNode("//*[local-name()='NodeElement/NodeElement[1]']");
using(var reader = XmlReader.Create(new StringReader(n.OuterXml)))
{
var obj = new XmlSerializer(typeof(NodeElementNodeElement)).Deserialize(reader);
I believe I'm missing just the right XPath expression, which seem to be rather elusive. Any help much appreciated!
Try this:
/*/*
It selects children of the root node.
Or
/*/*[local-name() = 'NodeElement']
It selects children with local-name() = 'NodeElement' of the root node.
Anyway in your case both expressions select <NodeElement xmlns="">.
walk the tree
foreach(XmlNode node in doc.DocumentElement.childnodes[0].childnodes)
{
// do something with node
}
hideously fragile of course might want to check for nulls here and there.

Linq duplicate elements when iterating over XML

<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<stock-items>
<stock-item>
<name>Loader 34</name>
<sku>45GH6</sku>
<vendor>HITINANY</vendor>
<useage>Lifter 45 models B to C</useage>
<typeid>01</typeid>
<version>01</version>
<reference>33</reference>
<comments>EOL item. No Re-order</comments>
<traits>
<header>56765</header>
<site>H4</site>
<site>A6</site>
<site>V1</site>
</traits>
<type-validators>
<actions>
<endurance-tester>bake/shake</endurance-tester>
</actions>
<rules>
<results-file>Test-Results.txt</results-file>
<file-must-contain file-name="Test-Results.xml">
<search>
<term>[<![CDATA[<"TEST TYPES 23 & 49 PASSED"/>]]></term>
<search-type>exactMatch</search-type>
</search>
</file-must-contain>
</rules>
</type-validators>
</stock-item>
</stock-items>
Im trying to get the rules fragment from the xml above into a string so it can be added to a database. Currently the search element and its contents are added twice. I know why this is happing but cant figure out how to prevent it.
Heres my code
var Rules = from rules in Type.Descendants("rules")
select rules.Descendants();
StringBuilder RulesString = new StringBuilder();
foreach (var rule in Rules)
{
foreach (var item in rule)
{
RulesString.AppendLine(item.ToString());
}
}
Console.WriteLine(RulesString);
Finally any elements in rules are optional and some of these elements may or may not contain other child elements up to 4 or 5 levels deep. TIA
UPDATE:
To try and make it clearer what im trying to achieve.
From the xml above I should end up with a string containing everthing in the rules element, exactly like this:
<results-file>Test-Results.txt</results-file>
<file-must-contain file-name="Test-Results.xml">
<search>
<term>[<![CDATA[<"TEST TYPES 23 & 49 PASSED"/>]]></term>
<search-type>exactMatch</search-type>
</search>
</file-must-contain>
Objective is to extract the entire contents of the rules element as is while taking account that the rules element may or may not contains child elements several levels deep
If you just want the entirety of the rules element as a string (rather than caring about its contents as xml), you don't need to dig into its contents, you just need to get the element as an XNode and then call ToString() on it :
The following example uses this method to retrieve indented XML.
XElement xmlTree = new XElement("Root",
new XElement("Child1", 1)
);
Console.WriteLine(xmlTree);
This example produces the following output:
<Root>
<Child1>1</Child1>
</Root>
if you want to prevent duplicates than you will need to use Distinct() or GroupBy() after parsing the xml and before building the string.
I'm still not fully understanding exactly what the output should be, so I can't provide a clear solution on what exactly to use, or how, in terms of locating duplicates. If you can refine the original post that would help.
we need the structure of the xml as it would appear in your scenario. nesting and all.
we need an example of the final string.
saving it to a db doesn't really matter for this post so you only need to briefly mention that once, if at all.

Parse XML in C#

Hello I want to know how can I parse this simple XML file content in C#. I can have multiple "in" elements, and from those I want to use date, min, max and state child values.
<out>
<in>
<id>16769</id>
<date>29-10-2010</date>
<now>12</now>
<min>12</min>
<max>23</max>
<state>2</state>
<description>enter text</description>
</in>
<in>
<id>7655</id>
<date>12-10-2010</date>
<now>1</now>
<min>1</min>
<max>2</max>
<state>0</state>
<description>enter text</description>
</in>
</out>
The System.XML namespace has all sorts of tools for parsing, reading, and writing XML data. By the way, your XML isn't well-formed; you've got two <out> elements, but only one </out> element.
Linq to xml is also helpful for parsing xml -
http://msdn.microsoft.com/en-us/library/bb387098.aspx
Also -
http://msdn.microsoft.com/library/bb308960.aspx
You need System.XML, starting with XmlDocument.Load(filename).
Once you have the XmlDocument loaded, you can drill down into it as needed using the built-in .Net XML object model, starting from XmlDocument level. You can walk the tree recursively in a pretty intuitive way, capturing what you want from each XmlNode as you go.
Alternatively (and preferably) you can quickly locate all XmlNodes in your XmlDocument that match certain conditions using XPath - examples here. An example of usage in C# is XmlNode.SelectNodes.
using System;
using System.IO;
using System.Xml;
public class Sample {
public static void Main() {
XmlDocument doc = new XmlDocument();
doc.Load("booksort.xml");
XmlNodeList nodeList;
XmlNode root = doc.DocumentElement;
nodeList=root.SelectNodes("descendant::book[author/last-name='Austen']");
//Change the price on the books.
foreach (XmlNode book in nodeList)
{
book.LastChild.InnerText="15.95";
}
Console.WriteLine("Display the modified XML document....");
doc.Save(Console.Out);
}
}
Examples can be found here http://www.c-sharpcorner.com/uploadfile/mahesh/readwritexmltutmellli2111282005041517am/readwritexmltutmellli21.aspx
This might be beyond what you want to do, but worth mentioning...
I hate parsing XML. Seriously, I almost refuse to do it, especially since .NET can do it for me. What I would do is create an "In" object that has the properties above. You probably have one already, or it would take 60 seconds to create. You'll also need a List of In objects called "Out".
Then just deserialze the XML into the objects. This takes just a few lines of code. Here is an example. BTW, this makes changing and re-saving the data just as easy.
How to serialize/deserialize

Best way to add a string XML snippet into an XML document?

this may have been asked before, but I could not find it.
Suppose I have an XML element
XMLElement nd = xmlDoc.CreateElement("Node");
Now, I would like to add a child to nd with a full XML snippet I get from some other function, like this:
nd.AppendChild("<a1><a2></a2></a1>");
What is the best way to do this?
nb.InnerXML = "<a1><a2></a2></a1>";
The way above is not the "Best" way to do this. The elements are nodes and should be created and added in that fashion. (Not real code, close).
XMLElement nd = xmlDoc.CreateElement("Node");
XMLElement a1 = xmlDoc.CreateElement("Node");
XMLElement a2 = xmlDoc.CreateElement("Node");
//Add the node name etc.
nd.AppendChild(a1);
nd.AppendChild(a2);
It is not good to use "<a1>" strings. What if the namespace changes? What about special characters, don't want to process them yourself, right?

Categories