I'm using System.Xml to get attributes from my xml file.
It seems that following code which I found somewhere is able to find nodes correctly however it doesn't recognizes attributes (it's weird because I've created this xml files with System.Xml too):
DataSet task_data = new DataSet("Root");
adapter.Fill(task_data); // MySqlDataAdapter is being used here
task_data.WriteXml(path, XmlWriteMode.WriteSchema);
So I don't know why any other xml which can be found on the internet works and mine which was created with the same module doesn't...
using System;
using System.Xml;
using System.IO;
public class Catalog
{
private XmlDocument xmldoc;
private string path = #"C:\Users\Me\Desktop\task.xml";
public static void Main()
{
Catalog c = new Catalog();
}
public Catalog()
//Constructor
{
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
xmldoc = new XmlDocument();
xmldoc.Load(fs);
DisplayCatalog();
}
// Method for Displaying the catalog
private void DisplayCatalog()
{
XmlNodeList xmlnode = xmldoc.GetElementsByTagName("task");
Console.WriteLine("Here is the list of catalogs\n\n");
for (int i = 0; i < xmlnode.Count; i++)
{
XmlAttributeCollection xmlattrc = xmlnode[i].Attributes; //HERE IS THE PROBLEM!!!
Console.Write(xmlnode[i].FirstChild.Name);
Console.WriteLine(":\t\t" + xmlnode[i].FirstChild.InnerText);
Console.Write(xmlnode[i].LastChild.Name);
Console.WriteLine(":\t" + xmlnode[i].LastChild.InnerText);
Console.WriteLine();
}
Console.WriteLine("Catalog Finished");
}
//end of class
}
This is the xml you linked to contins no attributes only nodes.
<?xml version="1.0" standalone="yes"?>
<Root>
<task>
<TaskId>1</TaskId>
<TaskDelegatorNote>Presentation</TaskDelegatorNote>
<StartTime>PT10H</StartTime>
<EndTime>PT13H</EndTime>
<TaskEndDate>2011-01-02T00:00:00+00:00</TaskEndDate>
<TaskContractorNote>Done</TaskContractorNote>
<TaskStatus>3</TaskStatus>
<LastModification>Me, 2003-05-15 13:48:59</LastModification>
</task>
<task>
<TaskId>2</TaskId>
<TaskDelegatorNote>It must be done.</TaskDelegatorNote>
<StartTime>PT10H</StartTime>
<EndTime>PT13H</EndTime>
<TaskEndDate>2011-01-02T00:00:00+00:00</TaskEndDate>
<TaskContractorNote />
<TaskStatus>2</TaskStatus>
<LastModification>Admin, 2009-08-04 10:30:49</LastModification>
</task>
</Root>
Here's an xml snippint with a TaskId attribute
<task TaskId = 1>
</task>
To fix this change
Console.Write(xmlattrc[0].Name);
Console.WriteLine(":\t\t" + xmlattrc[0].Value);
to
Console.Write(xmlnode[0].ChildNodes[0].Name);
Console.WriteLine(":\t\t" + xmlnode[0].ChildNodes[0].Value);
Your output would be
Here is the list of catalogs
TaskId:
TaskId: 1
LastModification: Me, 2003-05-15 13:48:59
TaskId:
TaskId: 2
LastModification: Admin, 2009-08-04 10:30:49
Catalog Finished
Press any key to continue . . .
Also you should look at LinqToXML for some other ways of doing projections of your xml nodes
Related
I have tried to setup a Dialogue tree within unity using XML (I have not used XML much before so am unsure if the way i am going is correct at all)
So I am trying to get the first text element from this dialogue tree but when i call the XML file and say where it is i am getting the everything stored in that branch.
Am i using the correct .XML to be able to do this also as i seen people say use .XML.LINQ or .XML.Serialization not just .XML is this correct for my case ??
Code:
using UnityEngine;
using System.Collections;
using System.IO;
using System.Xml;
using UnityEngine.UI;
using System.Collections.Generic;
public class DialogTree
{
public string text;
public List<string> dialogText;
public List<DialogTree> nodes;
public void parseXML(string xmlData)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new StringReader(xmlData));
XmlNode node = xmlDoc.SelectSingleNode("dialoguetree/dialoguebranch");
text = node.InnerXml;
XmlNodeList myNodeList = xmlDoc.SelectNodes("dialoguebranch/dialoguebranch");
foreach (XmlNode node1 in myNodeList)
{
if (node1.InnerXml.Length > 0)
{
DialogTree dialogtreenode = new DialogTree();
dialogtreenode.parseXML(node1.InnerXml);
nodes.Add(dialogtreenode);
}
}
}
}
And here is a picture of the XML.
So i am trying to grab the first element of text then late on there response it will go to branch 1 or 2
<?xml version='1.0'?>
<dialoguetree>
<dialoguebranch>
<text>Testing if the test prints</text>
<dialoguebranch>
<text>Branch 1</text>
<dialoguebranch>
<text>Branch 1a</text>
</dialoguebranch>
<dialoguebranch>
<text>Branch 1b</text>
</dialoguebranch>
</dialoguebranch>
<dialoguebranch>
<text>Branch 2</text>
</dialoguebranch>
</dialoguebranch>
</dialoguetree>
You're getting everything in that branch because XmlNode.InnerXML returns everything in that node. See the documentation for more information on that.
You should use the branch as the base for only looking at its children, instead of starting at xmlDoc every time. Also, you need an entry point to get inside of the first dialoguetree element and then ignore that. Finally, I would only create one XmlDocument and just pass around nodes in your recursion.
Altogether, this might look like this:
public class DialogTree
{
public string text;
public List<DialogTree> nodes = new List<DialogTree>();
public static DialogTree ParseXMLStart(string xmlData)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(new Stringreader(xmlData));
XmlNode rootNode = xmlDoc.SelectSingleNode("dialoguetree/dialoguebranch");
DialogTree dialogTree = new DialogTree();
dialogTree.ParseXML(rootNode);
return dialogTree;
}
public void ParseXML(XmlNode parentNode)
{
XmlNode textNode = parentNode.SelectSingleNode("text");
text = textNode.InnerText;
XmlNodeList myNodeList = parentNode.SelectNodes("dialoguebranch");
foreach (XmlNode curNode in myNodeList)
{
if (curNode.InnerXml.Length > 0)
{
DialogTree dialogTree = new DialogTree();
dialogTree.ParseXML(curNode);
nodes.Add(dialogTree);
}
}
}
}
And you could use it like so:
string xmlStringFromFile;
DialogTree dialogue = DialogTree.ParseXMLStart(xmlStringFromFile);
All of this code is untested but I hope the general idea is clear. Let me know if you find any errors in the comments below and I will try to fix them.
I wanted to load an XML and have it available to all events.
In the application below, the Button1 and Button3 events use the loaded XML, whereas Button2 won't and I had to load it within the event.
I am assuming that every time I load the file it is taking up more resources, which I am trying to avoid.
My questions are:
- Do I have to find a different way to populate the Datagridview ?
- Do I need to somehow unload the XML file if I need to load it somewhere else to save system resources.
I am new to programming and self taught so apologize in advance if terminology is not correct.
It is an application in a Windows form with:
Button1 generating a listBox in ListBox1;
Button2 populating dataGridView1 with 2 columns;
Button3 populating comboBox1 list
XML is as Follows:
<?xml version="1.0" encoding="utf-8" ?>
<Config>
<Categories>
<Category Name="OneChar">
<Entry>
<Name>a</Name>
<id>1</id>
</Entry>
<Entry>
<Name>b</Name>
<id>2</id>
</Entry>
<Entry>
<Name>c</Name>
<id>3</id>
</Entry>
</Category>
<Category Name="TwoChar">
<Entry>
<Name>aa</Name>
<id>11</id>
</Entry>
<Entry>
<Name>bb</Name>
<id>22</id>
</Entry>
<Entry>
<Name>cc</Name>
<id>33</id>
</Entry>
</Category>
</Categories>
<Schemes>
</Schemes>
</Config>
Code as Follows:
using System;
using System.Windows.Forms;
using System.Xml.Linq;
using System.Xml;
using System.Xml.XPath;
namespace List_box_multiple_query
{
public partial class Form1 : Form
{
XDocument xdoc = XDocument.Load("Config\\TestFile.xml");
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
var result = xdoc.XPathSelectElements("/Config/Categories/Category [#Name='TwoChar']/Entry/Name");
foreach (string entry in result)
{
listBox1.Items.Add(entry);
}
}
private void button2_Click(object sender, EventArgs e)
{
dataGridView1.Rows.Clear();
dataGridView1.Refresh();
XmlDocument doc = new XmlDocument();
doc.Load("Config\\testfile.xml");
XmlNodeList nodeList;
XmlNode root = doc.DocumentElement;
nodeList = root.SelectNodes("/Config/Categories/Category[#Name='OneChar']/Entry");
foreach (XmlNode entry in nodeList)
{
int n = dataGridView1.Rows.Add();
dataGridView1.Rows[n].Cells[0].Value = entry["Name"].InnerText.ToString();
dataGridView1.Rows[n].Cells[1].Value = entry["id"].InnerText.ToString();
}
}
private void button3_Click(object sender, EventArgs e)
{
var result = xdoc.XPathSelectElements("/Config/Categories/Category [#Name='TwoChar']/Entry/Name");
foreach (string entry in result)
{
comboBox1.Items.Add(entry);
}
}
}
}
First, you need to be consistently using XDocument or XmlDocument.
You can define a
private Lazy<XmlDocument> docLazy =
new Lazy<XmlDocument>(() =>
{
XmlDocument doc = new XmlDocument();
doc.Load("Config\\TestFile.xml");
return doc;
}
);
and then use it in all the handlers
var doc = docLazy.Value;
In this way it will be loaded from file only for the first call and then cached in memory.
My previous answer was similar but for XDocument.
reply to comments
Is there an easy way to select nodes in an XML and use their
contents...?
Yes, for example
var test_nodeList = xdoc.Descendants("Category")
.Where(x => x.Attribute("Name").Value.Equals("OneChar"))
.Descendants("Entry");
instead of
nodeList = root.SelectNodes("/Config/Categories/Category[#Name='OneChar']/Entry");
I have a xml file to which I want to add predefined namespeces.. Following is the code:
private const string uri = "http://www.w3.org/TR/html4/";
private static readonly List<string> namespaces = new List<string> { "lun" };
public static XElement AddNameSpaceAndLoadXml(string xmlFile) {
var nameSpaceManager = new XmlNamespaceManager(new NameTable());
// add custom namespace to the manager and take the prefix from the collection
namespaces.ToList().ForEach(name => {
nameSpaceManager.AddNamespace(name, string.Concat(uri, name));
});
XmlParserContext parserContext = new XmlParserContext(null, nameSpaceManager, null, XmlSpace.Default);
using (var reader = XmlReader.Create(#xmlFile, null, parserContext)) {
return XElement.Load(reader);
}
}
The problem is that the resulting xml in memory does not show the correct namespaces added. Also, they are not added at the root but are added next to the tag. Xml added below.
In the xml it is showing p3:read_data while should be lun:read_data.
How do i get to add the namespace on the root tag and not get the incorrect name.
Sample Input xml:
<config file-suffix="perf">
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters lun:read_data=""/>
</overview-graph>
</config>
Output xml expected:
<config file-suffix="perf" xmlns:lun="http://www.w3.org/TR/html4/lun">
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters lun:read_data="" />
</overview-graph>
</config>
Output that is coming using the above code:
<config file-suffix="perf" >
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters p3:read_data="" xmlns:p3="http://www.w3.org/TR/html4/lun"/>
</overview-graph>
</config>
I am not sure if there is a better way, but adding the namespace manually seems to work.
using (var reader = XmlReader.Create(#xmlFile, null, parserContext)) {
var newElement = XElement.Load(reader);
newElement.Add(new XAttribute(XNamespace.Xmlns + "lun", string.Concat(uri, "lun")));
return newElement;
}
I don't know offhand a way to generalize this however (obviously you can add the whole set by enumerating it, but outputting only used namespaces might be interesting).
Thank you very much for reading my question.
the bottom is the sample of my xml file.please refer that.
i did some xml files before, but by "CMarkXml". "IntoElement, OutofElement", is very clear.
but when C#...i was lost..
1: how to read & write my xml file without using the tag name. i see some articles about operation on xml file by c#, but all assumed that known the tag name.
2: if without tag name, it is very difficult or not recommend. then how to read & write my xml file by XmlDocument? (sorry, but no Ling please, i am very faint with that...).
3: my idear is, for the xml file, get out some section, we still could parse the section by xmldocument.
4: for the write/modify the xml file, of course, should contain delete some section, delete some "leaf", change the attributes...
Thank you very much for reading the long question, and any help i will very appreciate. If you have a good sample code but not continent paste them here, could you send it to "erlvde#gmail.com"?
<root>
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
....(other <bb>)
</b>
</root>
Read your xml into XmlDocument:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml("XML HERE");
Access child nodes:
xmlDocument.ChildNodes[1]
But it's also true that it's very error prone
You can also check if you have child nodes at all:
xmlDocument.HasChildNodes
And get number of child nodes:
xmlDocument.ChildNodes.Count
It looks to me like your elements names contain identifiers. If that is the case, and you have control over the XML schema, I would highly recommend changing your XML to contain elements and/or attributes indicating your identifiers and then use the built in XmlSerializer class for serializing to and from XML. It has many modifiers available, such as XmlElement and XmlAttribute among many others, for formatting the output.
Here is a tutorial to get you started.
If possible, change your XML to something like following which would make it far simpler to manipulate...again if changing the schema is a possibility.
<root>
<a>i belong to a</a>
<b>
<bb id="1">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb id="2">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
</b>
</root>
Edit this edit reflects the changes you made to your XML
Here is a simple console application which will serialize an object to an XML file and then rehydrate it.
Expected XML
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
</b>
</root>
Simple Console Application Demonstration
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var items = new root
{
a = "i belong to a",
b = new List<bb>
{
new bb
{
bbClassProperty = new List<int>
{
1,
2,
3,
4,
5
}
},
new bb
{
bbClassProperty= new List<int>
{
1,
2,
3,
4,
5
}
}
}
};
XmlSerializer serializer = new XmlSerializer(typeof(root));
using (var textWriter = new StreamWriter(#"C:\root.xml"))
{
serializer.Serialize(textWriter, items);
textWriter.Close();
}
using (var stream = new StreamReader(#"C:\root.xml"))
{
var yourObject = serializer.Deserialize(stream);
}
Console.Read();
}
}
#region [Classes]
public class root
{
public string a { get; set; }
public List<bb> b { get; set; }
}
public class bb
{
[XmlElement("bb")]
public List<int> bbClassProperty { get; set; }
}
#endregion
}
Look into the ChildNodes (and similar) properties and methods on your XmlElement object. These will let you iterate over the children of a node and you can then ask that node for its name.
If you have a XmlNode object, you can use XMLNode.FirstChild to get the child, if it has any. You can also use XMLNode.NextSibling to get the next Node of the same parent node.
Why can't you use the names of the nodes? It's the easiest and most common way. Especially if you use XPath or similar.
XPath is also the answer to your second question.
U can use the class XML reader, a simple example is given here.
using System;
using System.Xml;
class Program
{
static void Main()
{
// Create an XML reader for this file.
using (XmlReader reader = XmlReader.Create("perls.xml"))
{
while (reader.Read())
{
// Only detect start elements.
if (reader.IsStartElement())
{
// Get element name and switch on it.
switch (reader.Name)
{
case "perls":
// Detect this element.
Console.WriteLine("Start <perls> element.");
break;
case "article":
// Detect this article element.
Console.WriteLine("Start <article> element.");
// Search for the attribute name on this current node.
string attribute = reader["name"];
if (attribute != null)
{
Console.WriteLine(" Has attribute name: " + attribute);
}
// Next read will contain text.
if (reader.Read())
{
Console.WriteLine(" Text node: " + reader.Value.Trim());
}
break;
}
}
}
}
}
}
The input file text is:
<?xml version="1.0" encoding="utf-8" ?>
<perls>
<article name="backgroundworker">
Example text.
</article>
<article name="threadpool">
More text.
</article>
<article></article>
<article>Final text.</article>
</perls>
Output
Start element.
Start element.
Has attribute name: backgroundworker
Text node: Example text.
Start element.
Has attribute name: threadpool
Text node: More text.
Start element.
Text node:
Start element.
Text node: Final text.enter code here
You can use the following code to if the file does not contain the headers, in the example above.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
reader = XmlReader.Create(filePath, settings)
Would something like this help?
void Iterate(XmlNode parent) {
//do something with
//parent.Name
//parent.Value
//parent.Attributes
foreach(XmlNode child in parent.ChildNodes) {
Iterate(child);
}
}
XmlDocument document = new XmlDocument();
document.Load(filename);
XmlNode parent = document.DocumentElement;
Iterate(parent);
You could also store it like that (sorry for any syntactical error, didn't run it)
public class Document {
public Element DocumentElement { set; get; }
private void Load(string fileName) {
XmlDocument document = new XmlDocument();
document.Load(fileName);
DocumentElement = new Element(this, null);
DocumentElement.Load(document.DocumentElement);
}
}
public class Element {
public string Name { set; get; }
public string Value { set; get; }
//other attributes
private Document document = null;
private Element parent = null;
public Element Parent { get { return parent; } }
public List<Element> Children { set; get; }
private int order = 0;
public Element(Document document, Element parent) {
Name = "";
Value = "";
Children = new List<LayoutElement>();
this.document = document;
this.parent = parent;
order = parent != null ? parent.Children.Count + 1 : 1;
}
private Element GetSibling(bool left) {
if(parent == null) return null;
int add = left ? -1 : +1;
Element sibling = parent.Children.Find(child => child.order == order + add);
return sibling;
}
public Element GetLeftSibling() {
return GetSibling(true);
}
public Element GetRightSibling() {
return GetSibling(false);
}
public void Load(XmlNode node) {
Name = node.Name;
Value = node.Value;
//other attributes
foreach(XmlNode nodeChild in node.Children) {
Element child = new Element(document, this);
child.Load(nodeChild);
Children.Add(child);
}
}
}
Document document = new Document();
document.Load(fileName);
For changing/deleting right now you could iterate the tree and find elements by name, but since name is not unique, you would affect many elements at once. You could add an unique id in every tag like
<bb id="bb1"/>
Then read it in Load function like
id = ((XmlElement)node).GetAttribute("id");
and use this id to iterate through the tree. Sorry I don't have time right now to provide something more detailed.
I am trying to read an XML feed to get the last post date. My xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
<title>mysite</title>
<atom:link href="http://www.mysite.com/news/feed/" rel="self" type="application/rss+xml" />
<link>http://www.mysite.com/news</link>
<description>mysite</description>
<lastBuildDate>Tue, 22 Nov 2011 16:10:27 +0000</lastBuildDate>
<language>en</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>http://wordpress.org/?v=3.0.4</generator>
<item>
<title>My first post!</title>
<link>http://www.mysite.com/news/2011/11/22/docstore-v2-released/</link>
<comments>http://www.mysite.com/news/2011/11/22/docstore-v2-released/#comments</comments>
<pubDate>Tue, 22 Nov 2011 16:10:27 +0000</pubDate>
<dc:creator>mysite</dc:creator>
<category><![CDATA[News]]></category>
<category><![CDATA[Promotions]]></category>
<category><![CDATA[docstore]]></category>
I didn't show all of the xml since it is rather long.
My method, so far, looks like this:
private void button1_Click(object sender, EventArgs e)
{
var XmlDoc = new XmlDocument();
// setup the XML namespace manager
var mgr = new XmlNamespaceManager(XmlDoc.NameTable);
// add the relevant namespaces to the XML namespace manager
mgr.AddNamespace("ns", "http://purl.org/rss/1.0/modules/content/");
var webClient = new WebClient();
var stream = new MemoryStream(webClient.DownloadData("http://www.mysite.com/news/feed/"));
XmlDoc.Load(stream);
// **USE** the XML anemspace in your XPath !!
XmlElement NodePath = (XmlElement)XmlDoc.SelectSingleNode("/ns:Response");
while (NodePath != null)
{
foreach (XmlNode Xml_Node in NodePath)
{
Console.WriteLine(Xml_Node.Name + ": " + Xml_Node.InnerText);
}
}
}
I'm having a problem with it telling me:
Namespace Manager or XsltContext needed. This query has a prefix,
variable, or user-defined function.
All I want to pull out of this xml code is the 'lastBuildDate'. I'm going in circles trying to get this code right.
Can someone tell me what I am doing wrong here?
Thank you!
You're not using the namespace manager.
// **USE** the XML anemspace in your XPath !!
XmlElement NodePath = (XmlElement)XmlDoc.SelectSingleNode("/ns:Response", mgr);
There is only one of the element you are going after, you could go directly to it using the XPath. That element is also in the default namespace, so you do not need to do anything special to get to it. What about:
var XPATH_BUILD_DATE="/rss/channel/lastBuildDate";
private void button1_Click(object sender, EventArgs e){
var xmlDoc = new XmlDocument();
var webClient = new WebClient();
var stream = new MemoryStream(webClient.DownloadData("http://www.mysite.com/news/feed/"));
xmlDoc.Load(stream);
XmlElement xmlNode = (XmlElement)xmlDoc.SelectSingleNode(XPATH_BUILD_DATE);
Console.WriteLine(xmlNode.Name + ": " + xmlNode.InnerText);
}
If you did however need to dig into elements in a different namespace, you can do that also with the XPath (example, getting the dc:creator:
/rss/channel/item[1]/*[local-name() = 'creator']