How can I remove multiple XML nodes at once? - c#

I can remove an xml node using
XmlNode node = newsItem.SelectSingleNode("XYZ");
node.ParentNode.RemoveChild(node);
But what if I want to remove multiple nodes at once, for example XYZ,ABC,PQR?
Is there any way to remove all of these nodes at once or do I have to remove them one by one?
NOTE: XYZ,ABC,PQR being at the same level(i.e they all have same parent)

Nothing is inbuilt when using the XmlDocument API, but you could write a utility extension method, for example:
public static void Remove(this XmlNode node, string xpath)
{
var nodes = node.SelectNodes(xpath);
foreach (XmlNode match in nodes)
{
match.ParentNode.RemoveChild(match);
}
}
then call:
newsItem.Remove("XYZ|ABC|PQR");
If you can change to the XDocument API, then things may be different.

think you could do something like that using linq to xml.
var listOfNodesToRemove = new[]{"XYZ", "ABC", "PQR"};
var document = XDocument.Load(<pathtoyourfile>);
document.Descendants
.Where(m => listOfNodesToRemove.Contains(m.Name.ToString())
.Nodes()
.Remove();

That would depend very much on the structure (nesting) etc.
But basically yes, for a handful of unrelated elements, select and remove them one at a time.
You could combine them to some extent:
List<string> RemoveNames = ...
var toBeRemoved = doc.Descendants().Where(d => RemoveNames.Contains(d.name));
foreach (var element in toBeRemoved.ToList()) ...

Related

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

Relative references in an XML document

I am trying to pull out data from an XML document that seems to use relative references like this:
<action>
<topic reference="../../action[110]/topic"/>
<context reference="../../../../../../../../../../../../../contexts/items/context[2]"/>
</action>
Two questions:
Is this normal or common?
Is there a way to handle this with linq to XML / XDocument or would I need to manually traverse the document tree?
Edit:
To clarify, the references are to other nodes within the same XML document. The context node above references a list of contexts, and says to get the one at index 2.
The topic node worries me more because it's referencing a certain other action's topic, which could in turn reference a list of topics. If that wasn't happening I would have just loaded the lists of contexts and topics in a cache and looked them up that way.
You can use XPATH Query to extract the nodes and it is very efficient.
Step1: Load the XML into XMLDocument
Step2: use node.SelectNodes("//*[reference]")
Step3: After that you can loop through the XML nodes.
I ended up manually traversing the tree. But with extension methods it's all nice and out of the way. In case it might help anyone in the future, this is what I threw together for my use-case:
public static XElement GetRelativeNode(this XAttribute attribute)
{
return attribute.Parent.GetRelativeNode(attribute.Value);
}
public static string GetRelativeNode(this XElement node, string pathReference)
{
if (!pathReference.Contains("..")) return node; // Not relative reference
var parts = pathReference.Split(new string[] { "/"}, StringSplitOptions.RemoveEmptyEntries);
XElement current = node;
foreach (var part in parts)
{
if (string.IsNullOrEmpty(part)) continue;
if (part == "..")
{
current = current.Parent;
}
else
{
if (part.Contains("["))
{
var opening = part.IndexOf("[");
var targetNodeName = part.Substring(0, opening);
var ending = part.IndexOf("]");
var nodeIndex = int.Parse(part.Substring(opening + 1, ending - opening - 1));
current = current.Descendants(targetNodeName).Skip(nodeIndex-1).First();
}
else
{
current = current.Element(part);
}
}
}
return current;
}
And then you'd use it like this (item is an XElement):
item.Element("topic").Attribute("reference").GetRelativeNode().Value

Finding element in XDocument?

I have a simple XML
<AllBands>
<Band>
<Beatles ID="1234" started="1962">greatest Band<![CDATA[lalala]]></Beatles>
<Last>1</Last>
<Salary>2</Salary>
</Band>
<Band>
<Doors ID="222" started="1968">regular Band<![CDATA[lalala]]></Doors>
<Last>1</Last>
<Salary>2</Salary>
</Band>
</AllBands>
However ,
when I want to reach the "Doors band" and to change its ID :
using (var stream = new StringReader(result))
{
XDocument xmlFile = XDocument.Load(stream);
var query = from c in xmlFile.Elements("Band")
select c;
...
query has no results
But
If I write xmlFile.Elements().Elements("Band") so it Does find it.
What is the problem ?
Is the full path from the Root needed ?
And if so , Why did it work without specify AllBands ?
Does the XDocument Navigation require me to know the full level structure down to the required element ?
Elements() will only check direct children - which in the first case is the root element, in the second case children of the root element, hence you get a match in the second case. If you just want any matching descendant use Descendants() instead:
var query = from c in xmlFile.Descendants("Band") select c;
Also I would suggest you re-structure your Xml: The band name should be an attribute or element value, not the element name itself - this makes querying (and schema validation for that matter) much harder, i.e. something like this:
<Band>
<BandProperties Name ="Doors" ID="222" started="1968" />
<Description>regular Band<![CDATA[lalala]]></Description>
<Last>1</Last>
<Salary>2</Salary>
</Band>
You can do it this way:
xml.Descendants().SingleOrDefault(p => p.Name.LocalName == "Name of the node to find")
where xml is a XDocument.
Be aware that the property Name returns an object that has a LocalName and a Namespace. That's why you have to use Name.LocalName if you want to compare by name.
You should use Root to refer to the root element:
xmlFile.Root.Elements("Band")
If you want to find elements anywhere in the document use Descendants instead:
xmlFile.Descendants("Band")
The problem is that Elements only takes the direct child elements of whatever you call it on. If you want all descendants, use the Descendants method:
var query = from c in xmlFile.Descendants("Band")
My experience when working with large & complicated XML files is that sometimes neither Elements nor Descendants seem to work in retrieving a specific Element (and I still do not know why).
In such cases, I found that a much safer option is to manually search for the Element, as described by the following MSDN post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/3d457c3b-292c-49e1-9fd4-9b6a950f9010/how-to-get-tag-name-of-xml-by-using-xdocument?forum=csharpgeneral
In short, you can create a GetElement function:
private XElement GetElement(XDocument doc,string elementName)
{
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
return element;
}
}
return null;
}
Which you can then call like this:
XElement element = GetElement(doc,"Band");
Note that this will return null if no matching element is found.
The Elements() method returns an IEnumerable<XElement> containing all child elements of the current node. For an XDocument, that collection only contains the Root element. Therefore the following is required:
var query = from c in xmlFile.Root.Elements("Band")
select c;
Sebastian's answer was the only answer that worked for me while examining a xaml document. If, like me, you'd like a list of all the elements then the method would look a lot like Sebastian's answer above but just returning a list...
private static List<XElement> GetElements(XDocument doc, string elementName)
{
List<XElement> elements = new List<XElement>();
foreach (XNode node in doc.DescendantNodes())
{
if (node is XElement)
{
XElement element = (XElement)node;
if (element.Name.LocalName.Equals(elementName))
elements.Add(element);
}
}
return elements;
}
Call it thus:
var elements = GetElements(xamlFile, "Band");
or in the case of my xaml doc where I wanted all the TextBlocks, call it thus:
var elements = GetElements(xamlFile, "TextBlock");

How to rewrite code that navigates node list

I have just written some code, which as i was writing i thought, this is going to be a nice generic method for searching for a particular node. When i finished i actually realised it was a mess :D
public String sqlReading(String fileName, String path, String nodeId )
{
XmlDocument doc = new XmlDocument();
doc.Load(fileName);
XmlNodeList names = doc.SelectNodes(path);
foreach (XmlNode xmlDocSearchTerm in names)
{
//if the attribute of the node i start at is the same as where i am now
if (xmlDocSearchTerm.Attributes.Item(0).Value.ToString().Equals(nodeId))
{
//get a list of all of its child nodes
XmlNodeList childNodes = xmlDocSearchTerm.ChildNodes;
foreach (XmlNode node in childNodes)
{
//if there is a node in here called gui display, go inside
if (node.Name.Equals("GUIDisplay"))
{
XmlNodeList list = node.ChildNodes;
//find the sqlsearchstring tag inside of here
foreach (XmlNode finalNode in list)
{
if (finalNode.Name.Equals("sqlSearchString"))
{
return node.InnerText;
}
}
}
}
}
}
return "";
}
What i intended to do was based on a path - i would start and check to see if the element had the id i was looking for, if it did then i wanted to get inside there and not stop going until i got to the sqlsearchstring tag which was buried two levels deeper. I have managed that, but the issue here is that now i seem to have almost hardcoded a path to the tag opposed to looping there. How could i change my code to stop me from doing this?
Its from the second foreach where its going wrong imo.
Thanks
Haven't tested it but I believe something like this would work, by using a xpath. However I'm not sure the name of the attribute, or is it always the first attribute?
public String sqlReading(String fileName, String path, String nodeId)
{
XmlDocument doc = new XmlDocument();
doc.Load(fileName);
XmlNode foundNode = doc.SelectNodes(path).SelectSingleNode("*[#id='" + nodeId + "']/GUIDisplay/sqlSearchString");
if (foundNode != null)
return foundNode.InnerText;
return string.Empty;
}
Im not sure if this is exaclty right (as I dont have an XML document to try it with, but something similar should work
var innerTexts = XDocument.Load(fileName)
.Elements(path)
.Where(n => n.Attributes().ElementAt(0).Value == nodeId)
.SelectMany(n => n.Elements())
.Where(n => n.Name == "GUIDisplay")
.SelectMany(n => n.Elements())
.Where(n => n.Name == "sqlSearchString")
.Select(n => n.ToString());
I would say recursion is a safe bet (for iterating through nested child nodes) Though, from what I gather, the structure remains the same. And with that in mind, why not use [XmlDocumentObj].SelectSingleNode("/[nodeId='"+nodeId+"']") (or some facsimile) instead? This has the ability to search by attribute name, unless the XML structure is always changed and you never have constant tag (in which case XPath is probably a good idea).

How to read XML attributes in C#?

I have several XML files that I wish to read attributes from. My main objective is to apply syntax highlighting to rich text box.
For example in one of my XML docs I have: <Keyword name="using">[..] All the files have the same element: Keyword.
So, how can I get the value for the attribute name and put them in a collection of strings for each XML file.
I am using Visual C# 2008.
The other answers will do the job - but the syntax highlighting thingy and the several xml files you say you have makes me thinks you need something faster, why not use a lean and mean XmlReader?
private string[] getNames(string fileName)
{
XmlReader xmlReader = XmlReader.Create(fileName);
List<string> names = new List<string>();
while (xmlReader.Read())
{
//keep reading until we see your element
if (xmlReader.Name.Equals("Keyword") && (xmlReader.NodeType == XmlNodeType.Element))
{
// get attribute from the Xml element here
string name = xmlReader.GetAttribute("name");
// --> now **add to collection** - or whatever
names.Add(name);
}
}
return names.ToArray();
}
Another good option would be the XPathNavigator class - which is faster than XmlDoc and you can use XPath.
Also I would suggest to go with this approach only IFF after you try with the straightforward options you're not happy with performance.
You could use XPath to get all the elements, then a LINQ query to get the values on all the name atttributes you find:
XDocument doc = yourDocument;
var nodes = from element in doc.XPathSelectElements("//Keyword")
let att = element.Attribute("name")
where att != null
select att.Value;
string[] names = nodes.ToArray();
The //Keyword XPath expression means, "all elements in the document, named "Keyword".
Edit: Just saw that you only want elements named Keyword. Updated the code sample.
Like others, I would suggest using LINQ to XML - but I don't think there's much need to use XPath here. Here's a simple method to return all the keyword names within a file:
static IEnumerable<string> GetKeywordNames(string file)
{
return XDocument.Load(file)
.Descendants("Keyword")
.Attributes("name")
.Select(attr => attr.Value);
}
Nice and declarative :)
Note that if you're going to want to use the result more than once, you should call ToList() or ToArray() on it, otherwise it'll reload the file each time. Of course you could change the method to return List<string> or string[] by -adding the relevant call to the end of the chain of method calls, e.g.
static List<string> GetKeywordNames(string file)
{
return XDocument.Load(file)
.Descendants("Keyword")
.Attributes("name")
.Select(attr => attr.Value)
.ToList();
}
Also note that this just gives you the names - I would have expected you to want the other details of the elements, in which case you'd probably want something slightly different. If it turns out you need more, please let us know.
You could use LINQ to XML.
Example:
var xmlFile = XDocument.Load(someFile);
var query = from item in xmlFile.Descendants("childobject")
where !String.IsNullOrEmpty(item.Attribute("using")
select new
{
AttributeValue = item.Attribute("using").Value
};
You'll likely want to use XPath. //Keyword/#name should get you all of the keyword names.
Here's a good introduction: .Net and XML XPath Queries
**<Countries>
<Country name ="ANDORRA">
<state>Andorra (general)</state>
<state>Andorra</state>
</Country>
<Country name ="United Arab Emirates">
<state>Abu Z¸aby</state>
<state>Umm al Qaywayn</state>
</Country>**
public void datass(string file)
{
string file = HttpContext.Current.Server.MapPath("~/App_Data/CS.xml");
XmlDocument doc = new XmlDocument();
if (System.IO.File.Exists(file))
{
//Load the XML File
doc.Load(file);
}
//Get the root element
XmlElement root = doc.DocumentElement;
XmlNodeList subroot = root.SelectNodes("Country");
for (int i = 0; i < subroot.Count; i++)
{
XmlNode elem = subroot.Item(i);
string attrVal = elem.Attributes["name"].Value;
Response.Write(attrVal);
XmlNodeList sub = elem.SelectNodes("state");
for (int j = 0; j < sub.Count; j++)
{
XmlNode elem1 = sub.Item(j);
Response.Write(elem1.InnerText);
}
}
}

Categories