Confusion about the proper way to read child nodes with XmlReader - c#

Let's suppose i have the following Xml:
<Sections>
<Section>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
</Section>
<Section>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
</Section>
</Sections>
Now what i want is to loop though Sections, and work on each item separately, so i was thinking to do something like the following:
reader.ReadToDescendant("Section")
do
{
Console.WriteLine("Section");
reader.ReadToDescendant("Item");
do
{
var element = (XElement)XNode.ReadFrom(reader);
foreach (XElement el in element.Elements())
{
Console.WriteLine(el.Value);
}
}while(reader.ReadToNextSibling("Item"));
}while (reader.ReadToNextSibling("Section"))
My question is. If i repeat the same do-while loop for Item nodes, does the reader stop when it finds the closing Section tag or it will search in all the xml? Should i use reader.ReadSubtree() before the inner loop?
Note that i'm not looking for standard answer like "Use XDocument". I know that dom are easier to use, but they are not suitable for my situation

Use ReadSubtree to create inner reader to work with current node. Without this, the reader will not stop and continue the search until the end of the document.
reader.ReadToDescendant("Section");
do
{
Console.WriteLine("Section");
using (var innerReader = reader.ReadSubtree())
{
while (innerReader.ReadToFollowing("Field"))
{
Console.WriteLine("field");
}
}
} while (reader.ReadToNextSibling("Section"));

Related

XDocument Descendant Selector using Wildcard?

I have some XML structured like this:
<form>
<section-1>
<item-1>
<value />
</item-1>
<item-2>
<value />
</item-2>
</section-1>
<section-2>
<item-3>
<value />
</item-3>
<item-4>
<value />
</item-4>
</section-2>
</form>
...and want to turn it into something sane like this:
<form>
<items>
<item id="1">
<value/>
</item>
<item id="2">
<value/>
</item>
<item id="3">
<value/>
</item>
<item id="4">
<value/>
</item>
</items>
</form>
I am struggling to turn the old XML into an array or object of values. Once in the new format I'd be able to do the following:
XDocument foo = XDocument.Load(form.xml);
var items = foo.Descendants("item")
.Select(i => new Item
{
value = i.Element("value").Value
});
...but in the current mess the xml is in can I wildcard the descendants selector?
var items = foo.Descendants("item"*)
...or something? I tried to follow this question's answer but failed to adapt it to my purpose.
Ah-ha! It did click in the end. If I leave the descendants selector blank and add in a where statement along the lines of what's in this question's answer
.Where(d => d.Name.ToString().StartsWith("item-"))
Then we get:
XDocument foo = XDocument.Load(form.xml);
var items = foo.Descendants()
.Where(d => d.Name.ToString().StartsWith("item-"))
.Select(i => new Item
{
value = i.Element("value").Value
});
...and I'm now able to iterate through those values while outputting the new XML format. Happiness.

Editing xml on live in C#. Deleting nodes that contain specific value

I have an xml document of type like this:
<?xml version="1.0" encoding="UTF-16"?>
<Recordset>
<Table>Recordset</Table>
<Rows>
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>10.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
...
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>150.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
</Rows>
</Recordset>
I have different values in field with alias of StatusName. There are some Scheduled, notScheduled, Realeased, Finished etc values. What I would like to do is to delete each node that contain node with alias StatusName and value lets say Scheduled or Finished.
I was thinking to do this more or less in that way however I am doing something wrong. May anybody let me on right way ?
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
XmlNodeList nodes = xmlDocument.SelectNodes("//Rows[#StatusName='Finished']");
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = nodes.ToString();
I would like to delete the whole node if contains with alias StatusName and specific value lets say Finished.
I would expect the result in new string variable.
I like to work with DataTable with xml, I found it very easy.
I used a DataTable to work with your nodes.
So, I took your xml file and wrote some code for you that might help you:
//READ THE XML FILE
XmlDocument xmlDoc = new XmlDocument();
//My path
xmlDoc.LoadXml(Properties.Resources.test);
//Read the xml file into a dataSet
DataSet ds = new DataSet();
XmlNodeReader xnr = new XmlNodeReader(xmlDoc);
ds.ReadXml(xnr);
//Your data will be store in the 4's dataTable of the dataSet ( the <field> )
for(int i=0;i<ds.Tables[4].Rows.Count;i++)
{
//Check the value as you wish
//Here i want to suppress all the <Field> nodes with <Value> = "Scheduled"
if ( ds.Tables[4].Rows[i]["Value"].ToString().Equals("Scheduled"))
{
//RemoteAt will remove all the node, so the node <Field> in your example data
ds.Tables[4].Rows.RemoveAt(i);
//If you want to only remove the node <Value> (and not all the <Field> node ) just do ds.Tables[4].Rows["Value"]=null;
}
}
//Write your new content in a new xml file
//As you wanted here you just read the new xml file created as a string
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
ds.WriteXml(xmlTextWriter);
xmlTextWriter.Flush();
stringWriter.GetStringBuilder().ToString();
//Here the result is in stringWriter, and there is 6 <Field> nodes, and not 8 like before the suppress
}
//If you want to create a new xml file with the new content just do
ds.WriteXml(yourPathOfXmlFile);
//( like rewriting the previous xml file )
I assume, you are going to delete entire <Row> which matches your condition
i.e.,
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Finished</Value>
</Field>
</Fields>
</Row>
The required XPath:
//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]
C#
string xPath = #"//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]";
var nodes = xmlDocument.SelectNodes(xPath);
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = xmlDocument.OuterXml;

Get entire node XML rather than InnerXml

Given the following XML:
<Root>
<Item id="1">
<name>Foo</name>
<status>Active</status>
</Item>
<Item id="2">
<name>Bar</name>
<status>Inactive</status>
</Item>
</Root>
Let's say I have this XML in an XmlDocument object and then have the following code:
var nodes = xmlDocumentObject.GetElementsByTagName("Item");
foreach (var node in nodes)
{
var nodeXml = ??
}
I can easily get the InnerXml of each node, which for the first node would be:
<name>Foo</name>
<status>Active</status>
But how can I get the XML for the node including the containing tag and its attributes, such as this:
<Item id="1">
<name>Foo</name>
<status>Active</status>
</Item>
Try using XmlNode.OuterXml instead of InnerXml :
foreach (XmlNode node in nodes)
{
var nodeXml = node.OuterXml;
}

Parse xmldocument based on permissions

I have been dealing with this xml document for a long time, and now it turns out that user should be able to see certain fields based on the permission level defined in the xml document. This is what the document looked previously:
<?xml version="1.0" encoding="utf-8" ?>
<AccessControl>
<Field>
<name>First_Name</name>
<label>First Name</label>
</Field>
<Field>
<name>Last_Name</name>
<label>Last Name</label>
</Field>
......
.....
</AccessControl>
This is how is parsed the document:
doc.Load(System.Web.Hosting.HostingEnvironment.MapPath("~/ConfigFile.xml"));
XmlNode root = doc.DocumentElement;
XmlNodeList xnList = root.SelectNodes("/AccessControl/Field");
foreach (XmlNode xn in xnList)
{
string fieldName = xn["name"].InnerText;
.....
....
}
Now I am adding a few permission nodes in the document, which will contain field nodes, similar to this:
<AccessControl>
<Permission Name = "permissionXYZ" >
<Field>
<name>First_Name</name>
<label>First Name</label>
</Field>
<Field>
<name>Last_Name</name>
<label>Last Name</label>
</Field>
....
</Permission>
<Permission Name = "permission123" >
<Field>
...
...
</Field>
</Permission>
</AccessControl>
How do I get only the required fields based on the permission defined in the document??
Using LINQ you could filter based on the attribute value and then select the Fields. Test.xml file in my example just holds your sample XML.
XDocument document = XDocument.Load("c:\\temp\\test.xml");
var fields = document.Descendants("Permission")
.Where(i => i.Attribute("Name") != null && i.Attribute("Name").Value == "permissionXYZ")
.Select(i => i.Descendants("Field"));

XMLdocument Sort

I've figured out how to append nodes to my rss document in the right structyre. I now need to sort it in the pubDate order and then output to screen. Looking at the examples online, I've found lots of XDocument and Linq stuff but nothing with XmlDocument. Scratching my head whether to scrap what code I have and work out how to do it in XDocument with advice from here or continue with XMLDocument and figure out a way to sort.
With XMLDocument I've got the code working exactly as I want, just need my feed to be sorted in pubDate order when it spits it out to the screen. So I think I will stick with this for the timebeing. I've found this article http://support.microsoft.com/kb/555060 and an xslt someone posted in Stack Overflow, but I dont know how to call the "XmlHelperFunctions" from my code. Is XSLT the easiest option I have, or is there something easier out there?
This is my code:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(rssFeed.ToString());
XmlNodeList nl = xmlDoc.SelectNodes("/rss/channel/item");
foreach (XmlNode xn in nl)
{
string title = xn["title"].InnerText;
string link = xn["link"].InnerText;
string desc = xn["description"].InnerText;
string auth = xn["author"].InnerText;
string pdate = xn["pubDate"].InnerText;
XmlElement itemnode = xmlDoc.CreateElement("item");
itemnode.InnerXml = "<title></title><link></link><description></description><author></author><pubDate></pubDate>";
itemnode["title"].InnerText = title;
itemnode["link"].InnerText = link;
itemnode["description"].InnerText = desc;
itemnode["author"].InnerText = auth;
itemnode["pubDate"].InnerText = pdate;
xmlDoc.DocumentElement.SelectNodes("/rss/channel")[0].AppendChild(itemnode);
}
// Output to screen
xmlDoc.Save(Response.Output);
my rss feed
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<channel>
<title>My RSS Feed</title>
<link>http://www.mylink.aspx</link>
<description>
</description>
<item>
<title>Top marks</title>
<link>http://www.mymarks.aspx</link>
<description>
<p>description field here</p>
</description>
<author>Viv</author>
<pubDate>Thu, 16 Aug 2012 12:10:54 GMT</pubDate>
</item>
<item>
<title>Costa Coffee</title>
<link>http://www.Costa.aspx</link>
<description>
<p>Costa Coffee have special offers.</p>
</description>
<author>Mike</author>
<pubDate>Thu, 23 Aug 2012 15:55:53 GMT</pubDate>
</item>
<item>
<title>Celebrate success</title>
<link>http://www.Celebrate.aspx</link>
<description>
<p>Lets all celebrate </p>
</description>
<author>Viv</author>
<pubDate>Thu, 22 Aug 2012 09:10:21 GMT</pubDate>
</item>
</channel>
</rss>
You can do this fairly quickly and painlessly using Linq to XML.
If you parse your XML using XElement.Parse(...) you can then use OrderBy or OrderByDescending functions and alter the content pretty easily.
Here is a simplified example:
XElement element = XElement.Parse(#"
<rss>
<channel>
<item title='something' pubDate='22/11/2012'/>
<item title='something2' pubDate='24/03/2012'/>
<item title='something3' pubDate='10/02/2010'/>
<item title='something4' pubDate='22/01/2011'/>
</channel>
</rss>");
var elements = element.Element("channel")
.Elements("item")
.OrderBy<XElement, DateTime>(e => DateTime.ParseExact(e.Attribute("pubDate").Value, "dd/MM/yyyy", null))
.Select(e => new XElement("item",
new XElement("title", e.Attribute("title").Value),
new XElement("pubDate", e.Attribute("pubDate").Value))); // Do transform here.
element.Element("channel").ReplaceAll(elements);
Console.Write(element.ToString());
The XML is not going to be the same as yours, but hopefully it gives you an idea of what you could do. You can just specify XElement and XAttribute objects as content for your new XML, this outputs the following:
<rss>
<channel>
<item>
<title>something3</title>
<pubDate>10/02/2010</pubDate>
</item>
<item>
<title>something4</title>
<pubDate>22/01/2011</pubDate>
</item>
<item>
<title>something2</title>
<pubDate>24/03/2012</pubDate>
</item>
<item>
<title>something</title>
<pubDate>22/11/2012</pubDate>
</item>
</channel>
</rss>
I hope this is useful.

Categories