Select xml node by its element contnt - c#

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.

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.

Parse Xml attributes via XmlDocument

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);

How to find particular node in XML and all of its child nodes?

This is my XML:
<?xml version="1.0"?>
<formatlist>
<format>
<formatName>WHC format</formatName>
<delCol>ID</delCol>
<delCol>CDRID</delCol>
<delCol>TGIN</delCol>
<delCol>IPIn</delCol>
<delCol>TGOUT</delCol>
<delCol>IPOut</delCol>
<srcNum>SRCNum</srcNum>
<distNum>DSTNum</distNum>
<connectTime>ConnectTime</connectTime>
<duration>Duration</duration>
</format>
<format>
<formatName existCombineCol="1">Umobile format</formatName> //this format
<delCol>billing_operator</delCol>
<hideCol>event_start_date</hideCol>
<hideCol>event_start_time</hideCol>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>ConnectdateTimeAFcombine</name>
<combineDate>event_start_date</combineDate>
<combineTime>event_start_time</combineTime>
</afCombineName>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>aaa</name>
<combineDate>bbb</combineDate>
<combineTime>ccc</combineTime>
</afCombineName>
<modifyPerfixCol action="add" perfix="60">bnum</modifyPerfixCol>
<srcNum>anum</srcNum>
<distNum>bnum</distNum>
<connectTime>ConnectdateTimeAFcombine</connectTime>
<duration>event_duration</duration>
</format>
</formatlist>
I want to find format with Umobile format then iterate over those two nodes.
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>ConnectdateTimeAFcombine</name>
<combineDate>event_start_date</combineDate>
<combineTime>event_start_time</combineTime>
</afCombineName>
<afCombineName dateType="DateTime" format="dd/MM/yyyy HH:mm:ss"> //node i want
<name>aaa</name>
<combineDate>bbb</combineDate>
<combineTime>ccc</combineTime>
</afCombineName>
and list all the two node's child nodes. The result should like this:
ConnectdateTimeAFcombine,event_start_date,event_start_time.
aaa,bbb,ccc
How can I do this?
foreach(var children in format.Descendants())
{
//Do something with the child nodes of format.
}
For all XML related traversing, you should get used to using XPath expressions. It is very useful. Even if you could perhaps do something easier in your specific case, it is good practice to use XPath. This way, if your scheme changes at some point, you just update your XPath expression and your code will be up and running.
For a complete example, you can have a look at this article.
You can use the System.Xml namespace APIs along with System.Xml.XPath namespace API. Here is a quick algorithm that will help you do your task:
Fetch the text node containing the string Umobile format using the below XPATH:
XmlNode umobileFormatNameNode = document.SelectSingleNode("//formatName[text()='Umobile format']");
Now the parent of umobileFormatNameNode will be the node that you are interested in:
XmlNode formatNode = umobileFormatNameNode.ParentNode;
Now get the children for this node:
XmlNodeList afCombineFormatNodes = formatNode.SelectNodes("afCombineName");
You can now process the list of afCombineFormatNodes
for(XmlNode xmlNode in afCombineNameFormtNodes)
{
//process nodes
}
This way you can access those elements:
var doc = System.Xml.Linq.XDocument.Load("PATH TO YOUR XML FILE");
var result = doc.Descendants("format")
.Where(x => (string)x.Element("formatName") == "Umobile format")
.Select(x => x.Element("afCombineName"));
Then you can iterate the result this way:
foreach (var item in result)
{
string format = item.Attribute("format").Value.ToString();
string name = item.Element("name").Value.ToString();
string combineDate = item.Element("combineDate").Value.ToString();
string combineTime = item.Element("combineTime").Value.ToString();
}

Get values from XML, that are noted as "Value=xx" not boxed in

I have a XML file, that looks like this inside:
<Data>
<INFO>
..
...
<JOB_NAME value="filename.pdf"/>
<useless_info value="some_info"/>
<TIMESTAMP value="20120210075304"/>
<more_useless_info value="012345"/>
...
..
</INFO>
<INFO>
..
...
<JOB_NAME value="filename2.pdf"/>
<useless_info value="some_info"/>
<TIMESTAMP value="20120210073487"/>
<more_useless_info value="012345"/>
...
..
</INFO>
</Data>
What i want to do, is write specific info to strings, so that later on i can write these to a text file or new XML file.
I found this example here: http://www.csharp-examples.net/xml-nodes-by-name/
And i have this code a little bit working.
Not fully, because it it not getting the values.
my code looks like this:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
XmlNodeList xnList = xmlDoc.SelectNodes("/Data/INFO");
foreach (XmlNode xn in xnList)
{
string jobName = xn["JOB_NAME"].InnerText;
string timeStamp = xn["TIMESTAMP"].InnerText;
MessageBox.Show(timeStamp + jobName); //for testing
}
I think this has to do with the fact that the info that i want to get, is not boxed in like <box>info</box>
What i cant find is how i could get the info in my caseout of the xml file now.
Could someone lent me a hand?
thank you!
You need to select the JOB_NAME and TIMESTAMP child nodes of each INFO node, then get their attributes, then get the Value of the "value" attribute.
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
XmlNodeList xnList = xmlDoc.SelectNodes("/Data/INFO");
foreach (XmlNode xn in xnList)
{
string jobName = xn.SelectSingleNode("JOB_NAME").Attributes["value"].Value;
string timeStamp = xn.SelectSingleNode("TIMESTAMP").Attributes["value"].Value;
MessageBox.Show(timeStamp + jobName); //for testing
}
Be careful with this though as you are likely to get a NullReferenceException if any of the INFO nodes don't contain both a JOB_NAME and TIMESTAMP Node, and also if either of those do not have an attribute "value".
To answer your comment below:
string vendorName = xn.SelectSingleNode("JOB_NAME").Attributes["vendor-name"].Value;
string mediaName = xn.SelectSingleNode("JOB_NAME").Attributes["media-name"].Value;
You're correct in that innerText won't work because in your example 'value' is an attribute.
string jobName = xn["JOB_NAME"].Attributes["value"].Value;
string timeStamp = xn["TIMESTAMP"].Attributes["value"].Value;
Corrected and tested. This method or the SelectSingleNode should work fine.
If you want to iterate over all INFO elements and read the contained JOB_NAME and TIMESTAMPs valueattributes then Tobsey's answer is correct and will give you the desired result.
If you intend to query specific values from your Xml document you should use XPath queries instead of iterating over a set of nodes:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(FileNameTextBox.Text);
string jobName = xmlDoc.SelectSingleNode("//JOB_NAME/#value").Value;
string timeStamp = xmlDoc.SelectSingleNode("//TIMESTAMP/#value").Value;
Update
If you have repeating INFO nodes but just are interested in the first one you could change your query to just look at the first one:
string jobName = xmlDoc.SelectSingleNode("//INFO[1]/JOB_NAME/#value").Value;
string timeStamp = xmlDoc.SelectSingleNode("//INFO[1]/TIMESTAMP/#value").Value;
The Xpath query //INFO[1]/JOB_NAME/#value is a shorthand expression for //INFO[position() = 1]/JOB_NAME/#value and will examine the position of the node in your Xml document.
Beware! The simplified example above will fail if .SelectSingleNode returns null!
You should get the Value property of the attribute named "value" in your nodes:
string jobName = xn.SelectSingleNode("JOB_NAME").Attributes["value"].Value;

XElement value in C#

How to get a value of XElement without getting child elements?
An example:
<?xml version="1.0" ?>
<someNode>
someValue
<child>1</child>
<child>2</child>
</someNode>
If i use XElement.Value for <someNode> I get "somevalue<child>1</child><child>2<child>" string but I want to get only "somevalue" without "<child>1</child><child>2<child>" substring.
You can do it slightly more simply than using Descendants - the Nodes method only returns the direct child nodes:
XElement element = XElement.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
Note that this will work even in the case where the child elements came before the text node, like this:
XElement element = XElement.Parse(
#"<someNode><child>1</child><child>2</child>some value</someNode>");
var firstTextValue = element.Nodes().OfType<XText>().First().Value;
There is no direct way. You'll have to iterate and select. For instance:
var doc = XDocument.Parse(
#"<someNode>somevalue<child>1</child><child>2</child></someNode>");
var textNodes = from node in doc.DescendantNodes()
where node is XText
select (XText)node;
foreach (var textNode in textNodes)
{
Console.WriteLine(textNode.Value);
}
I think what you want would be the first descendant node, so something like:
var value = XElement.Descendents.First().Value;
Where XElement is the element representing your <someNode> element.
You can specifically ask for the first text element (which is "somevalue"), so you could also do:
var value = XElement.Descendents.OfType<XText>().First().Value;

Categories