I'm having a bit of an issue. I'm trying to parse an XML file and put it's contents into a TreeView. I got everything pretty much working, but I'm having issues with one thing.
Here is an example of the XML file:
<AnswerIt>
<category name="Category 1">
<question is="Question 1">
<answer couldbe="Answer 1" />
<answer couldbe="Answer 2" />
</question>
</category>
<category name="Category 2">
<question is="Question 1">
<answer couldbe="Answer 1" />
</question>
<question is="Question 2">
<answer couldbe="Answer 1" />
</question>
</category>
</AnswerIt>
The code I am using to prase the XML file pulls all the categories just fine. When it gets to the question part it pulls the first question, but none after that. All the answers get pulled just fine (as long as they belong to the first question). Here is my C# code:
public void LoadQuestionDatabase()
{
XmlTextReader reader = new XmlTextReader("http://localhost/AnswerIt.xml");
TreeNode node = new TreeNode();
TreeNode subnode = new TreeNode();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "category")
{
node = lstQuestions.Nodes.Add(reader.GetAttribute(0));
categories.Add(reader.GetAttribute(0));
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.Read();
if (reader.Name == "question")
{
subnode = node.Nodes.Add(reader.GetAttribute(0));
questions.Add(reader.GetAttribute(0));
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.Read();
if (reader.Name == "answer")
{
// add each answer
subnode.Nodes.Add(reader.GetAttribute(0).Replace("\t", ""));
}
}
}
}
}
}
reader.Close();
}
I'm not very good at C#, I'm guessing somewhere along the lines it's not looping through all the questions and adding them. Any ideas what I'm doing wrong? Anywhere I can read up on to help me out? Every example I read puts the root node (AnswerIt) in the treeview, and I do not want that.
var xDocument = XDocument.Load("http://localhost/AnswerIt.xml");
foreach (var element in xDocument.Descendants("category"))
{
var node = lstQuestions.Nodes.Add(element.Attribute("name").Value);
foreach (var subElement in element.Elements("question"))
{
var subnode = node.Nodes.Add(subElement.Attribute("is").Value);
foreach (var answer in subElement.Elements("answer"))
subnode.Nodes.Add(answer.Attribute("couldbe")
.Value.Replace("\t", ""));
}
}
Instead of using the XmlTextReader try using
XDocument doc = XDocument.Load("http://localhost/AnswerIt.xml");
then you don't have to manually construct all the elements.
XDocument is provided in the System.Xml.Linq assembly and namespace. Once it's loaded into the XDocument you can then use any of its built in methods to manipulate the data.
Related
I'm trying to write and remove items (categories) that I've stored in an XML file. I've figured out how to add using new XElement and doc.Root.Add but I don't know how to remove and item that has the same title as the input.
XML:
<?xml version="1.0" encoding="utf-8"?>
<categories>
<category title="Horror"></category>
<category title="Romance"></category>
<category title="Health"></category>
<category title="SciFi"></category>
<category title="Programming" />
<category title="History" />
</categories>
C#:
public static void RemoveFromCategoryXMLFile(string title)
{
XmlDocument doc = new XmlDocument();
doc.Load("../../DAL/XML_Categories/Categories.xml");
XmlNode node = doc.SelectSingleNode($"/categories/category[#name='{title}']");
if (node != null)
{
XmlNode parent = node.ParentNode;
parent.RemoveChild(node);
doc.Save("../../DAL/XML_Categories/Categories.xml");
}
}
I want the item that matches the string title to be removed from the document. Right now nothing happens and it seems like the XmlNode returns null.
Using XDocument is recommended, as it is a newer class for parsing XML. With such class it's enought to use such code:
var title = "Horror";
var xml = XDocument.Load(#"path to XML");
xml.Root.Elements("category").Where(e => e.Attribute("title").Value == title).Remove();
xml.Save(#"path to output XML");
I have an XML file that have nodes and child nodes, some type of a tree view. I would like to read these elements and extract the content to write them to a new XML with a new schema with lighter tree hirarchy.
In my code, I parse the XML file and read the nodes and child nodes but i can only print the nodes to console. I cant figure out how to write the nodes to the new XML in XML structure using the method's recursion.
i'm a noob to XML? am i missing something ?
XML EXAMPLE
<node clasification= some data about the node">
<dimension = some sort of info layer>
<children>
<node clasification= some data about the node">
<dimension = some sort of info layer>
<children>
<node clasification= some data about the node">
<dimension = some sort of info layer>
</children>
</node>
<node clasification= some data about the node">
<dimension = some sort of info layer>
</children>
</node>
</children>
</node>
</children>
</node>
my code is based on this:
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load("../../Employees.xml");
XmlNode root = doc.SelectSingleNode("*");
ReadXML(root);
}
private static void ReadXML(XmlNode root)
{
if (root is XmlElement)
{
DoWork(root);
if (root.HasChildNodes)
ReadXML(root.FirstChild);
if (root.NextSibling != null)
ReadXML(root.NextSibling);
}
else if (root is XmlText)
{}
else if (root is XmlComment)
{}
}
private static void DoWork(XmlNode node)
{
if (node.Attributes["Code"] != null)
if(node.Name == "project" && node.Attributes["Code"].Value == "Orlando")
Console.WriteLine(node.ParentNode.ParentNode.Attributes["Name"].Value);
}
Please Help :)
i found a way to do it in a recursive way.
i cant share the final code because it is on a closed network with no access to the internet, but basically, i created a new node on the beginning of readxml , and returned it at the end, so i cant send the child object to it recursively...
something like that (pseudo code)
private static XmlNode ReadXML(XmlNode root)
{
XmlNode tmp = new XmlNode;
// pupulate the node attributes
tmp = DoWork(root);
foreach (xmlnode childnode in node.childnodes)
{
if (check if you want this node)
{
XmlNodeList tmplist = childnode.childnodes;
if (tmplist.HasChildNodes)
{
tmp.newnode = new ChildNode[tmplist.count];
for (i=0;i<tmpnode.count;i++)
{
tmp.tmpnode[i] = ReadXML(tmplist.item(i));
}
}
else
{}
}
}
now its working fine
I am trying to divide an XML file into parts
I have an XML file like this
<?xml version="1.0" encoding="utf-8"?>
<RegistrationOpenData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://example.gov">
<Description>Registration data is collected by ABC XYZ</Description>
<InformationURL>http://www.example.com/html/hpd/property-reg-unit.shtml</InformationURL>
<SourceAgency>ABC Department of Housing</SourceAgency>
<SourceSystem>PREMISYS</SourceSystem>
<StartDate>2016-02-29T00:03:06.642772-05:00</StartDate>
<EndDate i:nil="true" />
<Registrations>
<Registration xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<RegistrationID>1</RegistrationID>
<BuildingID>1A</BuildingID>
<element1>E11</element1>
<element2>E21</element2>
<element3>E31</element3>
<element4>E41</element4>
</Registration>
<Registration xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<RegistrationID>2</RegistrationID>
<BuildingID>2A</BuildingID>
<element1>E21</element1>
<element2>E22</element2>
<element3>E32</element3>
<element4>E42</element4>
</Registration>
</Registrations>
</RegistrationOpenData>
And I am trying to fetch the number of nodes trough this code
XmlDocument doc = null;
doc = new XmlDocument();
doc.Load(#"D:\Registrations20160229.xml");
XmlNodeReader nodeReader = new XmlNodeReader(doc);
XmlElement root = doc.DocumentElement;
XmlNodeList elemList = root.GetElementsByTagName("Registration");
int totalnode = elemList.Count;
int nodehalf = totalnode / 2;
MessageBox.Show(nodehalf.ToString());
But after this I am unable to proceed, This code I have used to calculate number of Registration Nodes and then made them into half, now I don't know how to proceed further to split this file, I have total of 158718 entries (Registration Nodes) inside the file (sometimes even more) and I am trying to break all into parts, maybe 3 to 4 parts.
Try this , it should not load whole xml to memory
using(XmlReader reader = XmlReader.Create(new FileStream(#"D:\Registrations20160229.xml" , FileMode.Open))){
while (reader.Read())
{
if(reader.NodeType == XmlNodeType.Element && reader.Name == "Registration")
counter++;
}
Console.WriteLine(counter);
}
I have a XML file like below:
<report timestamp="3201" reportVersion="2" request="3981135340">
<question timedOut="false" time="3163" attempts="2" correct="true" id="13">
<answer status="attempt">
<radioButton correct="false" value="true" id="17" />
</answer>
<answer status="correct">
<radioButton correct="true" value="true" id="15" />
</answer>
</question>
</report>
I want to read the child nodes based on 'status' attribute of 'answer' node.
Use XmlReader (fastest but forward only), XDocument (LINQ to XML) or XmlDocument. See the examples in the msdn documentation.
Using LINQ to XML:
using System.Xml.Linq;
var doc = XDocument.Parse(xml); // or XDocument.Load()
var elements = from e in doc.Descendants("answer")
where e.Attribute("status").Value == "attempt"
select e;
// elements will be IEnumerable<XElement>
Use XmlDocument and XPath:
XmlDocument document = new XmlDocument();
//here you should load your xml for example with document.Load();
XmlNodeList nodes = document.SelectNodes("/report/question/answer[#status = 'correct']/radioButton");
Just modify the XPath to your needs.
try this one..
foreach (XmlNode xnode in xdoc.SelectNodes("report/question/answer"))
{
if (xnode.Attributes.GetNamedItem("status").Value == "correct")
{
string value = xdoc.SelectSingleNode("report/question/answer[#status='correct']/radioButton").Attributes.GetNamedItem("id").Value;
}
if (xnode.Attributes.GetNamedItem("status").Value == "attempt")
{
string value = xdoc.SelectSingleNode("report/question/answer[#status='attempt']/radioButton").Attributes.GetNamedItem("id").Value;
}
}
I have a xml document like this :
<Node1 attrib1="abc">
<node1_1>
<node1_1_1 attrib2 = "xyz" />
</ node1_1>
</Node1>
<Node2 />
Here <node2 /> is the node i want to remove since it has not children/elements nor any attributes.
Using an XPath expression it is possible to find all nodes that have no attributes or children. These can then be removed from the xml. As Sani points out, you might have to do this recursively because node_1_1 becomes empty if you remove its inner node.
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(
#"<Node1 attrib1=""abc"">
<node1_1>
<node1_1_1 />
</node1_1>
</Node1>
");
// select all nodes without attributes and without children
var nodes = xmlDocument.SelectNodes("//*[count(#*) = 0 and count(child::*) = 0]");
Console.WriteLine("Found {0} empty nodes", nodes.Count);
// now remove matched nodes from their parent
foreach(XmlNode node in nodes)
node.ParentNode.RemoveChild(node);
Console.WriteLine(xmlDocument.OuterXml);
Console.ReadLine();
Smething like this should do it:
XmlNodeList nodes = xmlDocument.GetElementsByTagName("Node1");
foreach(XmlNode node in nodes)
{
if(node.ChildNodes.Count == 0)
node.RemoveAll;
else
{
foreach (XmlNode n in node)
{
if(n.InnerText==String.Empty && n.Attributes.Count == 0)
{
n.RemoveAll;
}
}
}
}
To do this for all empty child nodes, use a for loop (not foreach) and in reverse order. I resolved it as:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(#"<node1 attrib1=""abc"">
<node1_1>
<node1_1_1 />
</node1_1>
<node1_2 />
<node1_3 />
</node1>
<node2 />
");
RemoveEmptyNodes(xmlDocument );
private static bool RemoveEmptyNodes(XmlNode node)
{
if (node.HasChildNodes)
{
for(int I = node.ChildNodes.Count-1;I >= 0;I--)
if (RemoveEmptyNodes(node.ChildNodes[I]))
node.RemoveChild(node.ChildNodes[I]);
}
return
(node.Attributes == null ||
node.Attributes.Count == 0) &&
node.InnerText.Trim() == string.Empty;
}
The recursive calls (similarly to other solutions) eliminate the duplicated document processing of the xPath approach. More importantly the code is more readable and more readily editable. Win-Win.
So, this solution will remove <node2>, but also correctly removes <node1_2> and <node1_3>.
Update: Found a notable performance increase by using the following Linq implementation.
string myXml = #"<node1 attrib1=""abc"">
<node1_1>
<node1_1_1 />
</node1_1>
<node1_2 />
<node1_3 />
</node1>
<node2 />
");
XElement xElem = XElement.Parse(myXml);
RemoveEmptyNodes2(xElem);
private static void RemoveEmptyNodes2(XElement elem)
{
int cntElems = elem.Descendants().Count();
int cntPrev;
do
{
cntPrev = cntElems;
elem.Descendants()
.Where(e =>
string.IsNullOrEmpty(e.Value.Trim()) &&
!e.HasAttributes).Remove();
cntElems = elem.Descendants().Count();
} while (cntPrev != cntElems);
}
The loop handles cases where a parent needs to be removed because its only child was removed. Using the XContainer or derivatives tends to have similar performance increases due to the IEnumerable implementations behind the scenes. It's my new favorite thing.
On an arbitrary 68MB xml file RemoveEmptyNodes tends to take about 90sec, while RemoveEmptyNodes2 tends to take about 1sec.
This stylesheet uses an identity transform with an empty template matching elements without nodes or attributes, which will prevent them from being copied to the output:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!--Identity transform copies all items by default -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--Empty template to match on elements without attributes or child nodes to prevent it from being copied to output -->
<xsl:template match="*[not(child::node() | #*)]"/>
</xsl:stylesheet>