Linq to XML adding to List Problem send to repeater - c#

I need to get data from a XML file from Node1>Node2
Filter data to group from the xml by X Node where X node is equals A,Y,Z..
Group all elements with same X Node value ordered by date
Store first element of the group(as ordered by date would be by the latest), and the rest in a subgroup of the same object.
If in the filter fails to find one Node equals(A,X or Z) then it creates an first element with hardcoded data for the fist element.
Once this process is finish then send the object (list<>) to a repeater.
I have nearly everything but I don’t know how to do it for when the filter fails to find a Node that is equals to (A,X or Z)
Code Example.
private void BindOutputRepeaterLongTermCondition()
{
XDocument xd = XDocument.Parse(element.OuterXml);
if (xd != null)
{
var results = (from el1 in xd.Descendants("Node1")
from el2 in el1.Descendants("Node2")
where el2.Element("Date") != null
&&(el2.Element("Code").Value.StartsWith("A")
|| el2.Element("Code").Value.StartsWith("Y")
|| el2.Element("Code").Value.StartsWith("Z")
|| el2.Element("Code").Value.StartsWith("B")
)
orderby el2.Element("Date").Value descending
group el2 by el2.Element("Code").Value
into CodeGroup
select new Table(CodeGroup.First())
{
SubTable = (from subCodes in CodeGroup
select new Table(subCodes)).Skip(1).ToList()
}).ToList<Table>();
this.repeaterTable.DataSource = results;
this.repeaterTable.DataBind();
}
}
protected void repeater_Table_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
RepeaterItem item = e.Item;
if ((item.ItemType == ListItemType.Item) || (item.ItemType == ListItemType.AlternatingItem))
{
var recentTable = (Table)item.DataItem;
var TableRepeater = (Repeater)item.FindControl("rptSubTable");
TableRepeater.DataSource = recentTable.SubTable;
TableRepeater.DataBind();
}
}
}
public class Table
{
public string Date { get; set; }
public string Rubric { get; set; }
public string Value { get; set; }
public List<Table> SubTable { get; set; }
public Table(XElement xml)
{
Date = xml.Element("Date") != null ? xml.Element("Date").Value : "";
Code = xml.Element("Code") != null ? xml.Element("Code").Value : "";
var a = (string)xml.Element("Code").Value;
if (a.StartsWith("A"))
{
Name = "Row 1";
}
else if (a.StartsWith("Y"))
{
Name = "Row 2";
}
else if (a.StartsWith("Z"))
{
Name = "Row 3";
}
else if (a.StartsWith("B"))
{
Name = "Row 4";
}
//Tried the following but it does not work.
else if (!a.StartsWith("A") && !a.StartsWith("Y") && !a.StartsWith("B")){
Name = "Row 3";
Value = "Not Found";
}
}
}
XML Code example
<Node1>
<Node2>
<Date>2009-07-16</Date>
<Code>A</Code>
<Value>Some Value</Value>
</Node2>
<Node2>
<Date>2008-02-09</Date>
<Code>Y</Code>
<Value>Some Value</Value>
</Node2>
<Node2>
<Date>2008-02-09</Date>
<Code>Z</Code>
<Value>Some Value</Value>
</Node2>
<Node2>
<Date>2008-07-16</Date>
<Code>A</Code>
<Value>Some Value</Value>
</Node2>
<Node2>
<Date>2006-02-09</Date>
<Code>Y</Code>
<Value>Some Value</Value>
</Node2>
<Node2>
<Date>2001-02-09</Date>
<Code>Z</Code>
<Value>Some Value</Value>
</Node2>
</Node1>
Any help would be appreciated.
Update
The problem is that I need to display all elements whether they are on the xml or not.
At the moment I can only display existing elements.
If one of the elements (by "Code") is not found I need to display hardcoded name of the element and value "No Value".

Instead of the final elseif:
else if (!a.StartsWith("A") && !a.StartsWith("Y") && !a.StartsWith("B")){
Name = "Row 3";
Value = "Not Found";
}
Have you tried
else
{
Name = "Row n";
Value = "Not Found";
}
It should just fall through to that branch if all other conditions are not met (unless I am missing something)

Related

how to parse XML using XmlReader along with their closing tags?

Consider the following XML which I have to parse.
<root>
<item>
<itemId>001</itemId>
<itemName>test 1</itemName>
<description/>
</item>
</root>
I have to parse each of its tag and store it into a table as follows:
TAG_NAME TAG_VALUE IsContainer
------------ -------------- -----------
root null true
item null true
itemId 001 false
itemName test 1 false
description null false
/item null true
/root null true
Now to get this done, I am using XmlReader as this allows us to parse each & every node.
I am doing it as follows:
I created the following class to contain each tag's data
public class XmlTag
{
public string XML_TAG { get; set; }
public string XML_VALUE { get; set; }
public bool IsContainer { get; set; }
}
I am trying to get the list of tags(including closing ones) as follows:
private static List<XmlTag> ParseXml(string path)
{
var tags = new List<XmlTag>();
using (var reader = XmlReader.Create(path))
{
while (reader.Read())
{
var tag = new XmlTag();
bool shouldAdd = false;
switch (reader.NodeType)
{
case XmlNodeType.Element:
shouldAdd = true;
tag.XML_TAG = reader.Name;
//How do I get the VALUE of current reader?
//How do I determine if the current node contains children nodes to set IsContainer property of XmlTag object?
break;
case XmlNodeType.EndElement:
shouldAdd = true;
tag.XML_TAG = string.Format("/{0}", reader.Name);
tag.XML_VALUE = null;
//How do I determine if the current closing node belongs to a node which had children.. like ROOT or ITEM in above example?
break;
}
if(shouldAdd)
tags.Add(tag);
}
}
return tags;
}
but I am having difficulty determining the following:
How to determine if current ELEMENT contains children XML nodes? To set IsContainer property.
How to get the value of current node value if it is of type XmlNodeType.Element
Edit:
I have tried to use LINQ to XML as follows:
var xdoc = XDocument.Load(#"SampleItem.xml");
var tags = (from t in xdoc.Descendants()
select new XmlTag
{
XML_TAG = t.Name.ToString(),
ML_VALUE = t.HasElements ? null : t.Value,
IsContainer = t.HasElements
}).ToList();
This gives me the XML tags and their values but this does not give me ALL the tags including the closing ones. That's why I decided to try XmlReader. But If I have missed anything in LINQ to XML example, please correct me.
First of all, as noted by Jon Skeet in the comments you should probably consider using other tools, like XmlDocument possibly with LINQ to XML (EDIT: an example with XmlDocument follows).
Having said that, here is the simplest solution for what you have currently (note that it's not the cleanest possible code, and it doesn't have much validation):
private static List<XmlTag> ParseElement(XmlReader reader, XmlTag element)
{
var result = new List<XmlTag>() { element };
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
element.IsContainer = true;
var newTag = new XmlTag() { XML_TAG = reader.Name };
if (reader.IsEmptyElement)
{
result.Add(newTag);
}
else
{
result.AddRange(ParseElement(reader, newTag));
}
break;
case XmlNodeType.Text:
element.XML_VALUE = reader.Value;
break;
case XmlNodeType.EndElement:
if (reader.Name == element.XML_TAG)
{
result.Add(new XmlTag()
{
XML_TAG = string.Format("/{0}", reader.Name),
IsContainer = element.IsContainer
});
}
return result;
}
}
return result;
}
private static List<XmlTag> ParseXml(string path)
{
var result = new List<XmlTag>();
using (var reader = XmlReader.Create(path))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
result.AddRange(ParseElement(
reader,
new XmlTag() { XML_TAG = reader.Name }));
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
result.Add(new XmlTag()
{
XML_TAG = string.Format("/{0}",current.Name)
});
}
}
}
return result;
}
An example using XmlDocument. This will give slightly different result for self-enclosing tags (<description/> in your case). You can change this behaviour easily, depending on what you want.
private static IEnumerable<XmlTag> ProcessElement(XElement current)
{
if (current.HasElements)
{
yield return new XmlTag()
{
XML_TAG = current.Name.ToString(),
IsContainer = true
};
foreach (var tag in current
.Elements()
.SelectMany(e => ProcessElement(e)))
{
yield return tag;
}
yield return new XmlTag()
{
XML_TAG = string.Format("/{0}", current.Name.ToString()),
IsContainer = true
};
}
else
{
yield return new XmlTag()
{
XML_TAG = current.Name.ToString(),
XML_VALUE = current.Value
};
yield return new XmlTag()
{
XML_TAG = string.Format("/{0}",current.Name.ToString())
};
}
}
And using it:
var xdoc = XDocument.Load(#"test.xml");
var tags = ProcessElement(xdoc.Root).ToList();

XML read text inside two elements

Here is my xml code:
<?xml version="1.0" encoding="utf-8"?>
<updater>
<version>1.0.7</version>
<Enabled>true</Enabled>
<item>
<url>some url</url>
<name>file name</name>
</item>
<item>
<url>other url</url>
<name>other file name</name>
</item>
</updater>
how can i get the value of url and name inside of both item elements? The full code have 9 elements with the name item. Please make the solution fit with this code:
XmlTextReader reader = null;
try
{
string xmlURL = "someurl";
reader = new XmlTextReader(xmlURL);
reader.MoveToContent();
string elementName = "";
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "updater"))
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element) elementName = reader.Name;
else
{
if ((reader.NodeType == XmlNodeType.Text) && (reader.HasValue))
{
switch (elementName)
{
case "url":
if (nummer >= urls.Length)
Array.Resize(ref urls, urls.Length + 1);
urls[nummer] = reader.Value.ToString();
MessageBox.Show(urls[nummer]);
break;
case "name":
if (nummer >= names.Length)
Array.Resize(ref names, names.Length + 1);
names[nummer] = reader.Value.ToString();
MessageBox.Show(names[nummer]);
break;
}
nummer++;
}
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
Any help will be appreciated. thanks in advance.
PS. If i'm unclear somewhere, or if you need more information then just explain what's needed.
You can use LINQ to XML:
var xdoc = XDocument.Load(path_to_xml);
var items = from i in xdoc.Root.Elements("item")
select new {
Url = (string)i.Element("url"),
Name = (string)i.Element("name")
};
This will give list of anonymous objects corresponding to your item elements. Each object will have strongly-typed properties for url and name:
foreach(var item in items)
{
// use item.Url or item.Name
}
XDocument doc = XDocument.Load("Xml.xml");
IEnumerable<XElement> items = doc.Descendants("updater").Elements("item")
.Select(x => new { Url = x.Element("url").Value,
Name = x.Element("name").Value });

read only particular instance using xmlreader

I have a xml file that looks like
<Name>AAA</Name>
<Age>23</Age>
<I1>
<Element1>A</Element1>
<Element2>B</Element2>
<Element3>C</Element3>
<\I1>
<I2>
<Element1>AA</Element1>
<Element2>BB</Element2>
<Element3>CC</Element3>
</I2>
I am reading all the values of elements using xmlreader in C# 3.0. But now i have to change by reading only the values within particular start and end tage. For the xml file mentioned above, i need to read <Name>, <Age> by default and then i have a function that returns the value "I1" or "I2" which is basically the element names. If it returns "I1" then i should read only the elements between <I1> and </I1> and should not read <I2> and vice versa. So the code structure would be (just the logic please ignore the syntax errors) like
/******function that returns element name I1 or I2*********/
string elementName = func(a,b);
xmlreader reader = reader.create("test.xml");
while(reader.read())
{
switch(reader.nodetype)
{
case xmlnodetype.element:
string nodeName = reader.name
break;
case xmlnodetype.text
switch(nodeName)
{
/*************Read default name and age values*******/
case "Name":
string personName = reader.value
break;
case "Age":
string personAge = reader.value;
break;
/*******End of reading default values**************/
/*** read only elements between the value returned by function name above
If function returns I1 then i need to read only values between <I1> </I1> else read </I2> and </I2>**/
}
}
}
Thanks!
So assuming, since we dont have any other tags to go off, that your file would look something such as this from beginning to end
<?xml version="1.0" encoding="utf-8" ?>
<RootElement>
<UserInformation>
<Name>AAA</Name>
<Age>23</Age>
<I1>
<Element1>A</Element1>
<Element2>B</Element2>
<Element3>C</Element3>
<\I1>
<I2>
<Element1>AA</Element1>
<Element2>BB</Element2>
<Element3>CC</Element3>
</I2>
</UserInformation>
</RootElement>
and then to call it
System.IO.StreamReader sr = new System.IO.StreamReader("test.xml");
String xmlText = sr.ReadToEnd();
sr.Close();
List<UserInfo> finalList = readXMLDoc(xmlText);
if(finalList != null)
{
//do something
}
private List<UserInfo> readXMLDoc(String fileText)
{
//create a list of Strings to hold our user information
List<UserInfo> userList = new List<UserInfo>();
try
{
//create a XmlDocument Object
XmlDocument xDoc = new XmlDocument();
//load the text of the file into the XmlDocument Object
xDoc.LoadXml(fileText);
//Create a XmlNode object to hold the root node of the XmlDocument
XmlNode rootNode = null;
//get the root element in the xml document
for (int i = 0; i < xDoc.ChildNodes.Count; i++)
{
//check to see if we hit the root element
if (xDoc.ChildNodes[i].Name == "RootElement")
{
//assign the root node
rootNode = xDoc.ChildNodes[i];
break;
}
}
//Loop through each of the child nodes of the root node
for (int j = 0; j < rootNode.ChildNodes.Count; j++)
{
//check for the UserInformation tag
if (rootNode.ChildNodes[j].Name == "UserInformation")
{
//assign the item node
XmlNode userNode = rootNode.ChildNodes[j];
//create userInfo object to hold results
UserInfo userInfo = new UserInfo();
//loop through each if the user tag's elements
foreach (XmlNode subNode in userNode.ChildNodes)
{
//check for the name tag
if (subNode.Name == "Name")
{
userInfo._name = subNode.InnerText;
}
//check for the age tag
if (subNode.Name == "Age")
{
userInfo._age = subNode.InnerText;
}
String tagToLookFor = "CallTheMethodThatReturnsTheCorrectTag";
//check for the tag
if (subNode.Name == tagToLookFor)
{
foreach (XmlNode elementNode in subNode.ChildNodes)
{
//check for the element1 tag
if (elementNode.Name == "Element1")
{
userInfo._element1 = elementNode.InnerText;
}
//check for the element2 tag
if (elementNode.Name == "Element2")
{
userInfo._element2 = elementNode.InnerText;
}
//check for the element3 tag
if (elementNode.Name == "Element3")
{
userInfo._element3 = elementNode.InnerText;
}
}
}
}
//add the userInfo to the list
userList.Add(userInfo);
}
}
}
catch (Exception e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
//return the list
return userList;
}
//struct to hold information
struct UserInfo
{
public String _name;
public String _age;
public String _element1;
public String _element2;
public String _element3;
}

Find Xml attribute by value, and change its other value

So, below I made a code which lists my 2 items in comboBox1 by its name(Selena and Maria) on load, and when I select one of those, lets say Maria, and click on button1, it populates my 3 textboxes with Maria's name,usn and pawd attribute values, and it looks like:
Display name: Maria
Username: mary26
Password: d4e5r
and I am happy with that part of code, because it serves my purpose.
But I am struggling with part of code which I am trying to figure out.
I created a button2, and I would like that, when I change values of Display name, Username or Password textboxes, and I click save, that it saves to right location in xml file, to Maria, and does not save it to Selena or something else.
I have tried browsing for a week now, and multiple solutions, and I couldn't find any.
att.xml:
<database>
<item name="Selena" usn="sele22" pawd="fed47a"></item>
<item name="Maria" usn="mary26" pawd="d4e5r"></item>
<database>
myproject:
private void Form3_Load(object sender, EventArgs e)
{
comboBox1.Items.Clear();
XmlTextReader reader = new XmlTextReader("att.xml");
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == "item")
{
comboBox1.Items.Add(reader.GetAttribute("name"));
}
}
}
reader.Close();
}
private void button1_Click(object sender, EventArgs e)
{
string secit = comboBox1.SelectedItem as string;
XmlTextReader lola = new XmlTextReader("att.xml");
while (lola.Read())
{
if (lola.NodeType == XmlNodeType.Element)
{
string poop = lola.GetAttribute("name");
if (poop == secit)
{
string username = lola.GetAttribute("usn");
string password = lola.GetAttribute("pawd");
string dispname = lola.GetAttribute("name");
textBox1.Text = dispname;
textBox2.Text = username;
textBox3.Text = password;
}
}
}
lola.Close();
}
You can use XmlDocument like this:
XmlDocument doc = new XmlDocument();
doc.Load("att.xml");
foreach(XmlNode item in doc.SelectNodes("//item"))
comboBox1.Items.Add(item.Attributes["name"].Value);
void button3_Click(object sender, NotifyArgs e)
{
XmlNode item = doc.SelectSingleNode("//item[#name='" + comboBox1.Text + "']");
if (item == null) return;
item.Attributes["name"].Value = textBox1.Text;
...
doc.Save("att.xml");
}
Another alternative would be to use XDocument, if you care to learn LINQ.
Lets assume you have a separate method to update the XML file, perhaps it looks like this.
private static void Update(string key, string pwd, string usn)
{
// Enter code here to update the item
}
You can use XPath to find an element with a name:
var document = XDocument.Load("XMLFile1.xml");
var element = document.XPathSelectElement(string.Format("/database/item[#name = \"{0}\"]", key));
if (element != null)
{
element.SetAttributeValue("usn", usn);
element.SetAttributeValue("pawd", pwd);
document.Save("XMLFile2.xml");
}
Or by finding the document using XDocument/XElement/LINQ methods:
var document = XDocument.Load("XMLFile1.xml");
var element = document.Elements("database")
.Elements("item")
.Attributes("name")
.Where(a => a.Value == key)
.Select(a => a.Parent)
.SingleOrDefault();
if (element != null)
{
element.SetAttributeValue("usn", usn);
element.SetAttributeValue("pawd", pwd);
document.Save("XMLFile3.xml");
}
Or you could rewrite it as a LINQ expression.
var document = XDocument.Load("XMLFile1.xml");
var elements = from e1 in document.Elements()
where e1.Name == "database"
from e2 in e1.Elements()
where e2.Name == "item"
from attribute in e2.Attributes()
where attribute.Name == "name" && attribute.Value == key
select e2;
var element = elements.SingleOrDefault();
if (element != null)
{
element.SetAttributeValue("usn", usn);
element.SetAttributeValue("pawd", pwd);
document.Save("XMLFile3.xml");
}
Feel free to adapt accordingly.

Count child nodes of node matching listbox item

Im having a problem here with selected index change. My xml file contains module number, module name, assesments, credits etc. What im trying to achieve here is - some course details are loaded into listbox from xml (module name and code) but when user selects a module, label should display how many assessments that module has.
Here is my XML file sample
<module>
<moduleCode>ECWM618</moduleCode>
<moduleTitle>Semantic and Social Web</moduleTitle>
<credits>15</credits>
<semester>2</semester>
<assessmentDetails>
<assessment>
<assessmentName>Coursework1</assessmentName>
<assessmentType>Coursework</assessmentType>
<assessmentWeighting>25</assessmentWeighting>
</assessment>
<assessment>
<assessmentName>Coursework2</assessmentName>
<assessmentType>Coursework</assessmentType>
<assessmentWeighting>25</assessmentWeighting>
</assessment>
<assessment>
<assessmentName>Exam</assessmentName>
<assessmentType>Exam</assessmentType>
<assessmentWeighting>50</assessmentWeighting>
</assessment>
</assessmentDetails>
</module>
And here is the code i got
private void moduleSummaryBox_SelectedIndexChanged(object sender, EventArgs e)
{
// when module from modulelist is selected, it read all indicies assigned to the module.
//!!!!!!!! it reads last node only :(? wtf im tired ...lol
//
// read data from modulelist
string path = Directory.GetCurrentDirectory();
FileStream fs = new FileStream(#"myCourse.xml", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read);
XmlReader moduleReader = XmlReader.Create(fs);
moduleReader.Read();
while (moduleReader.Read())
{
int assessmentNo = 0;
bool match = false;
moduleReader.MoveToElement();
if (moduleReader.Name == "assessmentName")
{
moduleReader.Read();// reads xml file.
XmlNodeType nType = moduleReader.NodeType; //XmlNodeType Specifies the type of node. If node matches specified name = true.
if (nType == XmlNodeType.Text)// if node type match XmlNodeType ( and there is some text in it) statement becomes true
{
this.assno.Text = (moduleReader.Value.ToString()); //set mcode label to matched node.
}
}
if (moduleReader.Value.ToString() == moduleSummaryBox.Items[moduleSummaryBox.SelectedIndex].ToString())
{
match = true;
}
if (match == true)
{
break;
}
}
}
Help appreciated thank you :)
EDIT:
This is what goes into listbox
moduleSummaryBox.Items.Clear();
XmlDocument doc = new XmlDocument();
doc.Load(#"myCourse.xml");
XmlNodeList levelList = doc.GetElementsByTagName("level"+l_level);
foreach (XmlNode node in levelList)
{
XmlElement moduleElement = (XmlElement)node;
XmlNodeList modules_individ = moduleElement.GetElementsByTagName("module");
foreach (XmlNode nodes in modules_individ)
{
XmlElement moduleSeperator = (XmlElement)nodes;
string ll_moduleCode = moduleSeperator.GetElementsByTagName("moduleCode")[0].InnerText;
string ll_moduleTitle = moduleSeperator.GetElementsByTagName("moduleTitle")[0].InnerText;
moduleSummaryBox.Items.Add(ll_moduleCode+" : " + ll_moduleTitle+" ");
}
}
Can you Try With the code sample below:-
const string xmlString =
"<module><moduleCode>ECWM618</moduleCode><moduleTitle>Semantic and Social Web</moduleTitle><credits>15</credits>" +
"<semester>2</semester><assessmentDetails><assessment><assessmentName>Coursework1</assessmentName><assessmentType>Coursework</assessmentType>" +
"<assessmentWeighting>25</assessmentWeighting></assessment><assessment><assessmentName>Coursework2</assessmentName><assessmentType>Coursework</assessmentType>" +
"<assessmentWeighting>25</assessmentWeighting></assessment><assessment><assessmentName>Exam</assessmentName><assessmentType>Exam</assessmentType><assessmentWeighting>50</assessmentWeighting></assessment></assessmentDetails></module>";
var xml = XElement.Parse(xmlString);
var qry =
xml.Descendants()
.Where(e => e.Name == "moduleCode" && e.Value == "ECWM618")
.Ancestors()
.Descendants()
.Where(e => e.Name == "assessmentDetails")
.Elements("assessment").Count();

Categories

Resources