Parse Xml attributes via XmlDocument - c#

Good day!
I try to parse Xml document:
structure:
<root>
<SelectedConfig name="configuration1"/>
<config name="configuration1">
<Column Id="0" name="111"/>
...
</root>
I try to parse it like this:
XmlNode configNameNode = _doc.SelectSingleNode("//SelectedConfig");
if (configNameNode != null)
{
string configName = configNameNode.Attributes["name"].Value;
var config = _doc.SelectNodes("//config");
XmlNodeList columnNodes = _doc.SelectNodes("//config/Column");
foreach (XmlNode node in columnNodes)
{
var id = node.Attributes["Id"].Value;
var name = node.Attributes["name"].Value;
Tuple<string, String> numberOfColumnToColumnName = new Tuple<string, string>(id, name);
numberOfColumnToColumnNameTupleColl.Add(numberOfColumnToColumnName);
}
}
But, if i add:
<config name="configuration2">
I get N*2 nodes of //config/column.
How to select only one set of xml nodes- from configuration1 or 2?
Thank you!

You have multiple nodes "config", each with multiple child nodes "column".
Because all these nodes have the same name and only differ in their attributes, the function XmlNodeList columnNodes = _doc.SelectNodes("//config/Column"); will collect all "column" nodes of all "config"s.
Solution 1: rename config nodes to config_xxx, e.g. config_configuration1. But I think that's not what you want.
Solution 2: config = _doc.SelectNodes("//config"); already gets you a node list. Run a for each cfgEntry in config over the list, calling cfgEntry.SelectNodes("column") on each node. Then you will get only the column nodes of that single config node.

If you are trying to get the selected configuration, you can try the following code.
string xPath = string.format("//config[#name='{0}']/Column", configName);
var config = _doc.SelectNodes(xPath);

Related

How to get nodes from XML file without its attribute and put to a List of string

I would like to display the tag names of child nodes without its attributes. Then those tag names (nodes) should be put in a List of string. Here's example of my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<ROOT>
<CAR>
<ID>21</ID>
<MANUFACTURER>Ford</MANUFACTURER>
<MODEL>Fiesta</MODEL>
</CAR>
<CAR>
<ID>22</ID>
<MANUFACTURER>Peugeot</MANUFACTURER>
<MODEL>508</MODEL>
</CAR>
</ROOT>
So, the effect I want to get in a console output is shown below:
ID
MANUFACTURER
MODEL
Then I would like to store that ID, MANUFACTURER and MODEL tag names in a List of strings.
This is the code that I tried so far:
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
try
{
xmlDocument.Load("XMLFile.xml");
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine(xmlDocument.OuterXml);
XmlNodeList nodeList = xmlDocument.SelectNodes("ROOT/CAR");
foreach(XmlNode node in nodeList)
{
Console.WriteLine(node.ChildNodes);
xmlNodes.Add(node.ChildNodes.ToString());
}
The problem is that it's not displaying the way I want to. As a result I only get two System.Xml.XmlChildNodes which seems to be corresponding to two <CAR> nodes, instead of its three child nodes, such as ID, MANUFACTURER and MODEL.
System.Xml.XmlChildNodes
System.Xml.XmlChildNodes
Adding items to a List basically adds the same thing as shown above.
What am I doing wrong?
If you have to use XmlDocument, then you can -
List<string> elements = new List<string>();
XmlNodeList CarNodes = xml.SelectNodes("Root/Car");
foreach(XmlNode c in CarNodes)
{
foreach(XmlNode n in c.ChildNodes)
{
if (!elements.Contains(n.Name))
{
elements.Add(n.Name);
}
}
}
But I find XDocument to be much simpler and better readability.
XDocument xdoc = XDocument.Parse(yourXmlString);
List<string> elements = xdoc.Descendants("Car")
.DescendantNodes().OfType<XElement>()
.Select(x => x.Name).Distinct().ToList();
And thats all you'll need. Easy to read as well, get all the descendants of "Car" Node and get all distinct names of XElements within it.
Another way to do it -
List<string> elements = xdoc.Descendants("Car").First()
.DescendantNodes().OfType<XElement>()
.Select(x => x.Name).ToList();
In this case I have removed the "distinct" and rather got just the first Car node ONLY. You can see the difference - if by any case some other Car node has an extra element, you'll miss getting that information by doing it this way.
You could loop through for children nodes:
1- You can define xmlNodes like a HashSet to avoid multiple tags like :
HashSet<string> xmlNodes = new HashSet<string>();
2 - Change little the code like :
....
XmlNodeList nodeList = xmlDocument.SelectNodes("ROOT/CAR");
foreach (XmlNode node in nodeList)
{
foreach(XmlNode element in node.ChildNodes)
{
if (element.NodeType == XmlNodeType.Element)
xmlNodes.Add(element.Name);
}
}
Demo
Console.WriteLine(string.Join(", ", xmlNodes));
Result
ID, MANUFACTURER, MODEL
I hope you find this helpful.

Reading XML file results in index out of range exception

Using C# I have an XML file like
<?xml version="1.0" encoding="utf-8"?>
<root>
<Account>
<name>Jani</name>
</Account>
</root>
and I also have a function to read the name node as:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("lib//user.xml");
XmlNode node;
node = xmlDoc.DocumentElement;
string name = node.Attributes[0].Value;
label1.Text = name.ToString();
but I am getting index out of range error as:
Why is this happening?
node = xmlDoc.DocumentElement;
string name = node.Attributes[0].Value;
node is your root node. Which looks like this:
<root>
How many attributes does it have? None, as it turns out. An attribute in XML is one of these bar="baz" things:
<foo bar="baz">
node.Attributes[0] refers to the first attribute. There is no first attribute, there's no second attribute -- you didn't use attributes in this XML at all. Hence, that's out of range. There's no first item in an empty collection.
What you want is an element named name, which is farther down inside your XML tree.
Probably this:
var node = xmlDoc.DocumentElement.SelectSingleNode("/root/Account/name");
And then you'll want to look at node.InnerText to get "Jani" out of it.
You are trying to read node.Attributes[0].Value but there is no attribtues in your sample XML file. Not sure of the exact syntax but it should probably be closer to node.Value
As mentioned by other answers, your current XML does not have attributes.
private void DoIt()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(#"M:\StackOverflowQuestionsAndAnswers\38924171\38924171\data.xml");
XmlNode node;
node = xmlDoc.DocumentElement;
//string name = node.Attributes[0].Value;
string name = node["Account"].InnerText;
}
If your XML did have attributes
<?xml version="1.0" encoding="utf-8"?>
<root>
<Account name="Jani" />
</root>
Then you could do this:
private void DoItAgain()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(#"M:\StackOverflowQuestionsAndAnswers\38924171\38924171\data2.xml");
XmlNode node;
node = xmlDoc.DocumentElement;
string name = node["Account"].Attributes[0].Value;
}

Select xml node by its element contnt

I have an xml file like this one:
<root>
<sub>
<name>Al</name>
<code>001</code>
</sub>
<sub>
<name>John</name>
<code>002</code>
</sub>
and a List<string> lName= new List<string>(new string[] { "Jack", "John"});
I want to edit "John" node, add "Jack" node and leave "Al" node as it is. What I did is:
System.Xml.XmlNodeList nodeNameList = FileXml.SelectNodes(#"//*[contains(name(),'name')]");
foreach (string name in lName)
{
System.Xml.XmlNode nodeName=FileXml.SelectSingleNode("//root/sub/name");
if (nodeName.InnerText == name)
{
//add
}
else
{
//edit
}
But it doesn't work. I thought that XmlNodeList works as a normal list but it doesn't. Can someone help me?
YOu can load your xml into the Document and then use Descendants with node name and then apply the Where filter on it with Contins. You can try something like this,
XDocument xmlDocument = XDocument.Load(#"yourxmlpath.xml");
var result = xmlDocument.Descendants("sub")
.Where(x => lName.Contains(x.Element("name").Value)).ToList();
You've got a couple problems here.
First, you're comparing an XmlNode to a string with this code:
if (nodeName == name)
Which will never evaluate as true. You want something like
if (nodeName.InnerText == name)
For your add logic, you'll need to create a new XmlNode and append it (see https://msdn.microsoft.com/en-us/library/system.xml.xmlnode.appendchild%28v=vs.110%29.aspx). For your update logic, just set the InnerText property on the node.

Reading all root node names in an XML document

I'm sure all the pros out there would find this very trivial but I need a quick solution for this in C#
I'm retrieving an xml schema of a view in share point which looks like this:
<FieldRef Name="LinkTitle"/><FieldRef Name="Author0"/><FieldRef Name="ID"/>
I want to parse this and only retrieve the Name's of each root element in this schema.
currently this is the code I'm working on , need some help with it
String fieldvals = view.ViewFields.SchemaXml.ToString();
XmlDocument reader = new XmlDocument(); ;
reader.LoadXml(fieldvals);
String xpath = "/";
var nodes = reader.SelectNodes(xpath);
foreach (XmlNode childrenNode in nodes)
{
Console.WriteLine(childrenNode.SelectSingleNode("//field1").Value);
}
Apparently, when this piece of code executes, I get an exception saying that more than one root node is present which is true of course .. but I'm not able to figure out the correct code to access every root node and extract it's name!
Wrap your xml fragment within a root node and then you can use linq to xml to retrieve a string array of those names like this:
var xml = XElement.Parse(xmlString);
var names=xml.Elements().Attributes(#"Name").Select(attrib => attrib.Value);
You should wrap your xml in some root node as an XML can have only one Root Node as below :
<FieldRefs>
<FieldRef Name="LinkTitle"/>
<FieldRef Name="Author0"/>
<FieldRef Name="ID"/>
</FieldRefs>
And then your code will execute fine.
String fieldvals = view.ViewFields.SchemaXml.ToString();
XmlDocument reader = new XmlDocument(); ;
reader.LoadXml(fieldvals);
String xpath = "/FieldRefs/FieldRef";
var nodes = reader.SelectNodes(xpath);
foreach (XmlNode childrenNode in nodes)
{
/*Process here*/
}

Deleting node from string with xml structure

I have an string parameter with xml content in it. Basically the string have an XML inside.
string S = funcThatReturnsXML (parameters);
S have the next text:
<?xml version="1.0" encoding="utf-8" ?>
<tagA>
<tagB>
<tagBB>
..
.
.
</tagBB>
.
.
</tagB>
<tagC>
..
..
.
</tagC>
</tagA>
The funcThatReturnsXML (parameters) creates an XmlDocument object but the return it as a string, I cant change this function, to much stuff works with it.
Tried to create XmlDocument objetc but the SelectSingleNode return null.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(S);
XmlNode root = xmlDoc.SelectSingleNode("tagB");
How can I delete from string S (not XML Object) specific node, for example <tagB>
EDIT: this is the XML I tested with:
<?xml version="1.0" ?>
- <Request xmlns:xsi="http://www.mysite.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- <info xmlns="http://www.mysite.com">
<RequestTR>54</RequestTR>
<time>2013-12-22</time>
</info>
- <Parameters xmlns="http://www.mysite.com">
<id>3</id>
<name>2</name>
</Parameters>
<title>Request</title>
</Request>
Try this:
string S = funcThatReturnsXML(parameters);
var doc = XDocument.Parse(S);
var nodeToRemove = doc.Descendants("tagB");
nodeToRemove.Remove();
That will remove all nodes named "tagB" from string S which contains xml.
UPDATE 1:
Sorry, i missed to include one more line:
S = doc.ToString();
My first code above removed "tagB" from doc but didnt save it back to S variable.
UPDATE 2:
I tested with following xml which contain attribute:
<tagA attribute="value">
<tagB>
<tagBB>
</tagBB>
</tagB>
<tagC></tagC>
</tagA>
and the output of Console.WriteLine(S):
<tagA attribute="value">
<tagC></tagC>
</tagA>
UPDATE 3:
Given your updated xml format, I know why my previous code didn't work for you. That was because your xml have namespace (xmlns) declared. The solution is to use LocalName when searching for the node to be removed, that will search for node name while ignoring its namespace. The follwoing example shows how to remove all "info" node:
var doc = XDocument.Parse(S);
var nodeToRemove = doc.Descendants().Where(o => o.Name.LocalName == "info");
nodeToRemove.Remove();
S = doc.ToString();
If you can determine the particular outer element to remove from the returned XML, you could use LINQ to XML:
var returnedXml = funcThatReturnsXML(parameters);
var xmlElementToRemove = funcThatReturnsOuterElement(returnedXml);
var xelement = XElement.Load("XmlDoc.txt");
xelement.Elements().Where(e => e.Name == xmlElementToRemove).Remove();
For example:
using System.Linq;
using System.Xml.Linq;
class Program
{
static void Main(string[] args)
{
// pretend this is the funThatReturnsXML return value
var returnedXml = "<tagB><tagBB></tagBB></tagB>";
// get the outer XML element name
var xmlElementToRemove = GetOuterXmlElement(returnedXml);
// load XML from where ever
var xelement = XElement.Load("XmlDoc.txt");
// remove the outer element and all subsequent elements
xelement.Elements().Where(e => e.Name == xmlElementToRemove).Remove();
}
static string GetOuterXmlElement(string xml)
{
var index = xml.IndexOf('>');
return xml.Substring(1, index - 1);
}
}
Note that the above is a "greedy" removal method, if there is more than once element with the name returned via the GetOuterXmlElemet method they will all be removed. If you want a specific instance to be removed then you will require something more sophisticated.
Building on your edit:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(S);
var nodeA = xmlDoc.SelectSingleNode("/tagA");
var nodeB = nodeA.SelectSingleNode("tagB");
nodeA.RemoveChild(nodeB);
To remove (possibly) multiple tagB nodes in unknown positions, you may try:
var bees = xmlDoc.SelectNodes("//tagB");
foreach (XmlNode bee in bees) {
var parent = bee.ParentNode;
parent.RemoveChild(bee);
}

Categories