3 Attempts to delete XML node using C# - c#

I've tried deleting a node from my XML file 3 different ways; and each way I've come up empty. I am querying a SQL database and grabbing a filename, I want to delete the entire node were the file name in the XML document is = to the SQL database result.
I'm not sure what's wrong in my code:
Background Information
fn44 is the Filename grabbed from a SQL database (all my info is in a
SQL table, I need an XML file for use with JavaScript)
XML:
<?xml version="1.0" encoding="utf-8"?>
<xml>
<bannerMain>
<department>main</department>
<filename>resdrop.png</filename>
<title>This is a Title</title>
<text><![CDATA[caption text]]></text>
</bannerMain>
</xml>
Attempt 1 (I know that I'm not getting to the child correctly, can't seem to figure out how to fix it):
XDocument doc = XDocument.Load(Server.MapPath("~/uploads/banners.xml"));
var q = from node in doc.Descendants("bannerMain")
let fina = node.Descendants("filename")/*PROBLEM LINE*/
where fina != null && fina == myReader[0]/*Gets filename from SQL database*/
select node;
q.ToList().ForEach(x => x.Remove());
doc.Save(Server.MapPath("~/uploads/banners.xml"));
Attempt 2 (should work in my mind but doesn't)
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(Server.MapPath("~/uploads/banners.xml"));
XmlNode nodeToDelete = xmlDoc.SelectSingleNode("/xml/bannerMain[#filename="
+ fn44 + "]");
if (nodeToDelete != null)
{
nodeToDelete.ParentNode.RemoveChild(nodeToDelete);
}
xmlDoc.Save(Server.MapPath("~/uploads/banners.xml"));
Attempt 3 (similar to attempt 2)
string nodeToDelete = fn44;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(Server.MapPath("~/uploads/banners.xml"));
XmlNode node = xmlDoc.SelectSingleNode(string.Format("//*[filename=\"{0}\"]"
, nodeToDelete));
if (node != null)
xmlDoc.SelectSingleNode("xml/bannersMain").RemoveChild(node);
xmlDoc.Save(Server.MapPath("~/uploads/banners.xml"));
I want to delete the whole node where the filename is = to the filename that is grabbed from the SQL database. Any help/resources is much appreciated.
SOLVED:
There's a few different options in the below answers that work out well.
Solution 1:
var xDoc = XDocument.Load(Server.MapPath("~/uploads/banners.xml"));
string fileName = fn44; //Use whatever value you found in SQL DB...
xDoc.Descendants("filename").Where(c => c.Value == fileName).Select(x => x.Parent).Remove();
xDoc.Save(Server.MapPath("~/uploads/banners.xml"));
Solution 2:
XDocument doc = XDocument.Load(Server.MapPath("~/uploads/banners.xml"));
var q = from node in doc.Descendants("bannerMain")
let fina = node.Element("filename")
where fina != null && fina.Value == fn44
select node;
q.Remove();
doc.Save(Server.MapPath("~/uploads/banners.xml"));

That seems to work for me:
string xmlfile = Server.MapPath("~/uploads/banners.xml");
var xDoc = XDocument.Load(xmlfile);
string fileName = "resdrop.png"; // Value from SQL DB
xDoc.Descendants("filename")
.Where(c => c.Value == fileName)
.Select(x => x.Parent)
.Remove();
xDoc.Save(xmlfile);

Your problem with attempt #1 is that you are trying to compare an IEnumerable<XElement> to your reader value, this should work (assuming each bannerMain only has a single filename element):
var q = from node in doc.Descendants("bannerMain")
let fina = node.Element("filename")//only single filename, so get just that XElement
where fina != null && fina.Value == reader[0]//assumes reader[0] is a string value
select node;
To remove them just do this:
q.Remove();
doc.Save(Server.MapPath("~/uploads/banners.xml"));
I ran this through LINQPad and after doing q.Remove();, here were the contents of doc: <xml />.

It's a little verbose but here is a non-linq snippet:
void DeleteNode(string fileName)
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/uploads/banners.xml"));
//Get all the bannerMain nodes.
XmlNodeList nodelist = doc.SelectNodes("/xml//bannerMain");
if (nodelist != null)
{
foreach (XmlNode node in nodelist)
{
//Look for then filename child. If it contains desired value
//delete the entire bannerMain node. Assumes order of child nodes
//may not be a constant.
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name == "filename" && child.InnerText == name)
{
node.ParentNode.RemoveChild(node);
}
}
}
doc.Save(Server.MapPath("~/uploads/banners.xml"));
}
}

For Attempt #2, remove the # sign for the filename. The # symbol represents an Attribute, but the filename is a child-node.
If your phrase doesn't work, I'd rephrase it a little from:
"/xml/bannerMain[filename=
to
"//bannerMain[filename=

Related

Search an XmlNodeList for a specific string

Is there a way I can reduce the foreach code below so I don't have to use a foreach loop to iterate over the xml nodes?
I just want to look and see if an item is present in the xml file
XmlDocument doc = new XmlDocument();
doc.Load("MyList.xml");
XmlNodeList list = doc.SelectNodes("/MyList/item");
foreach( XmlNode item in list)
{
string name = item.InnerText;
if(name == "blah blah")
{
//do something
}
}
The above works but I just want a smaller cooler way of doing it :)
If all you want to do is check whether a certain node exists, use SelectSingleNode with a filtered XPath:
XmlNode node = doc.SelectSingleNode("/MyList/item[. = 'blah blah']");
if (node != null)
{
// do something
}
One issue here is that if the value you want to match on is a dynamic value, you should not build up the XPath by concatenating strings together. That would create an invalid XPath.
In that case, you can either use LINQ on an XmlNodeList:
var found = doc.SelectSingleNode("/MyList/item")
.Cast<XmlNode>()
.Any(n => n.InnerText == "blah blah");
or go ahead and use LINQ-to-XML:
XDocument doc = XDocument.Load("MyList.xml");
bool itemFound = doc.Element("MyList")
.Elements("item")
.Any(e => (string) e == "blah blah");
You can filter element by inner text directly in the XPath expression like so :
XmlNodeList list = doc.SelectNodes("/MyList/item[.='blah blah']");

How to iterate a xml file with XmlReader class

my xml stored in xml file which look like as below
<?xml version="1.0" encoding="utf-8"?>
<metroStyleManager>
<Style>Blue</Style>
<Theme>Dark</Theme>
<Owner>CSRAssistant.Form1, Text: CSR Assistant</Owner>
<Site>System.ComponentModel.Container+Site</Site>
<Container>System.ComponentModel.Container</Container>
</metroStyleManager>
this way i am iterating but some glitch is there
XmlReader rdr = XmlReader.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml");
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
string xx1= rdr.LocalName;
string xx = rdr.Value;
}
}
it is always getting empty string xx = rdr.Value;
when element is style then value should be Blue as in the file but i am getting always empty....can u say why?
another requirement is i want to iterate always within <metroStyleManager></metroStyleManager>
can anyone help for the above two points. thanks
Blue is the value of Text node, not of Element node. You either need to add another if to get value of text nodes, or you can read inner xml of current element node:
rdr.MoveToContent();
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
string name = rdr.LocalName;
string value = rdr.ReadInnerXml();
}
}
You can also use Linq to Xml to get names and values of root children:
var xdoc = XDocument.Load(path_to_xml);
var query = from e in xdoc.Root.Elements()
select new {
e.Name.LocalName,
Value = (string)e
};
You can use the XmlDocument class for this.
XmlDocument doc = new XmlDocument.Load(filename);
foreach (XmlNode node in doc.ChildNodes)
{
if (node.ElementName == "metroStyleManager")
{
foreach (XmlNode subNode in node.ChildNodes)
{
string key = subNode.LocalName; // Style, Theme, etc.
string value = subNode.Value; // Blue, Dark, etc.
}
}
else
{
...
}
}
you can user XDocument xDoc = XDocument.Load(strFilePath) to load XML file.
then you can use
foreach (XElement xeNode in xDoc.Element("metroStyleManager").Elements())
{
//Check if node exist
if (!xeNode.Elements("Style").Any()
//If yes then
xeNode.Value
}
Hope it Helps...
BTW, its from System.XML.Linq.XDocument

How to Select XML node, given only the attribute name

I have an XML file as follow
<NODE1 attribute1 = "SomeValue" attribute2 = "SomeOtherValue" />
<NODE2 attribute3 = "SomeValue" attribute4 = "SomeOtherValue" />
Now I am given only the attribute name say "attribute3". How can I get the name of node?
Add the following namespace at the top of your file:
using System.Xml.Linq;
And try this (assuming that input.xml is the path to your XML file):
var xml = XDocument.Load("input.xml");
string nodeName;
var node = xml.Descendants()
.FirstOrDefault(e => e.Attribute("attribute3") != null);
if (node != null)
nodeName = node.Name.LocalName;
With LINQ to XML:
XDocument xdoc = XDocument.Load(path_to_xml);
var nodes = xdoc.Descendants().Where(e => e.Attribute("attribute3") != null);
Or with XPath (as Marvin suggested):
var nodes = xdoc.XPathSelectElements("//*[#attribute3]");
Both queries will return collection of XElement nodes which have attribute attribute3 defined. You can get first of them with FirstOrDefault. If you want to get just name, use node.Name.LocalName.
UPDATE: I do not recommend you to use XmlDocument, but if you already manipulating this xml document, then loading it second time with XDocument could be inefficient. So, you can select nodes with XPathNavigator:
var doc = new XmlDocument();
doc.Load(path_to_xml);
var naviagator = doc.CreateNavigator();
var nodeIterator = naviagator.Select("//*[#attribute3]");
try in this way
string nodeName;
if(Node.Attributes.Cast<XmlAttribute>().Any(x => x.Name == "attribute3"))
{
nodeName=Node.Name;
}

Locating XML node by child node value inside of it, and changing another value

Three part question.
Is it possible to locate a specific XML node by a child inside of it to retrieve other children of the parent? Example:
<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>F2039-39</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>
So for this, I would like to locate the appropriate Appliance node by searching for "Refrigerator" or "Toaster", and retrieve the brand from it.
The second part of this question is this: Is this a stupid way to do it? Would using an attribute in the Appliance tag make this a lot easier? If so, how would I locate it that way?
As for the third part, once I locate the Appliance, how would I go about changing say, the Model, of that particular appliance?
Using XLinq, you can perform this query fairly naturally:
// Given:
// var xdoc = XDocument.Load(...);
// string applianceName = "Toaster";
// Find the appliance node who has a sub-element <Name> matching the appliance
var app = xdoc.Root
.Descendants("Appliance")
.SingleOrDefault(e => (string)e.Element("Name") == applianceName);
// If we've found one and it matches, make a change
if (app != null)
{
if (((string)app.Element("Model")).StartsWith("B8d-k30"))
{
app.Element("Model").Value = "B8d-k30 Mark II";
}
}
xdoc.Save(#"output.xml"); // save changes back to the document
Well if you are using XmlDocument
foreach(XmlNode applianceNode in
myDocument.DocumentElement.SelectNodes("Kitchen/Applicance[Name='Refrigerator']")
{
XmlNode modelNode = applicianceNode.SelectSingleNode("Model").InnerText = SomeOtherValue;
}
if you made the name tag an attribute (applicanceName) it would make little difference to this.
foreach(XmlNode applianceNode in
myDocument.DocumentElement.SelectNodes("Kitchen/Applicance[#applianceName='Refrigerator']")
{
// ...
}
string xml = #"<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>F2039-39</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>";
XDocument xdoc = XDocument.Parse(xml);
string newModel = "B8d-k45";
var matchingElement = (from appliance in xdoc.Descendants("Appliance")
where appliance.Element("Name").Value == "Toaster"
select appliance).FirstOrDefault();
if (matchingElement != null)
{
matchingElement.Element("Model").Value = newModel;
}
Console.WriteLine(xdoc.ToString());
Necromancing.
Yes, it's even simpler with XPath, and works completely without Linq:
Just use .. to get to the parent node (on the second thought, Linq will be easier when using "ordinalignorecase")
public static void CreateNewHouse()
{
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.XmlResolver = null;
doc.Load(#"d:\House.xml");
foreach (System.Xml.XmlNode modelNode in doc.DocumentElement
.SelectNodes("/House/Kitchen/Appliance/Name[text()='Refrigerator']/../Model"))
{
modelNode.InnerText = "A New Value";
}
doc.Save(#"d:\MyHouse.xml");
}
MyHouse.xml:
<House>
<Kitchen>
<Appliance>
<Name>Refrigerator</Name>
<Brand>Maytag</Brand>
<Model>A New Value</Model>
</Appliance>
<Appliance>
<Name>Toaster</Name>
<Brand>Black and Decker</Brand>
<Model>B8d-k30</Model>
</Appliance>
</Kitchen>
</House>
If you need it case-insensitive, replace text() with this:
translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
(for ASCII/english-only) and of course change "Refrigerator" to lowercase ("refrigerator")
If the XML-document has a default-namespace, you need to supply it in Select*Node, e.g.
xnImageTag.SelectSingleNode("./dft:Source", nsmgr);
where
System.Xml.XmlNamespaceManager nsmgr = GetReportNamespaceManager(doc);
public static System.Xml.XmlNamespaceManager GetReportNamespaceManager(System.Xml.XmlDocument doc)
{
if (doc == null)
throw new ArgumentNullException("doc");
System.Xml.XmlNamespaceManager nsmgr = new System.Xml.XmlNamespaceManager(doc.NameTable);
// <Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
if (doc.DocumentElement != null)
{
string strNamespace = doc.DocumentElement.NamespaceURI;
System.Console.WriteLine(strNamespace);
nsmgr.AddNamespace("dft", strNamespace);
return nsmgr;
}
nsmgr.AddNamespace("dft", "http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition");
// nsmgr.AddNamespace("dft", "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition");
return nsmgr;
} // End Function GetReportNamespaceManager

How can I get the href attribute value out of an <?xml-stylesheet> node?

We are getting an XML document from a vendor that we need to perform an XSL transform on using their stylesheet so that we can convert the resulting HTML to a PDF. The actual stylesheet is referenced in an href attribute of the ?xml-stylesheet definition in the XML document. Is there any way that I can get that URL out using C#? I don't trust the vendor not to change the URL and obviously don't want to hardcode it.
The start of the XML file with the full ?xml-stylesheet element looks like this:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="http://www.fakeurl.com/StyleSheet.xsl"?>
As a processing instruction can have any contents it formally does not have any attributes. But if you know there are "pseudo" attributes, like in the case of an xml-stylesheet processing instruction, then you can of course use the value of the processing instruction to construct the markup of a single element and parse that with the XML parser:
XmlDocument doc = new XmlDocument();
doc.Load(#"file.xml");
XmlNode pi = doc.SelectSingleNode("processing-instruction('xml-stylesheet')");
if (pi != null)
{
XmlElement piEl = (XmlElement)doc.ReadNode(XmlReader.Create(new StringReader("<pi " + pi.Value + "/>")));
string href = piEl.GetAttribute("href");
Console.WriteLine(href);
}
else
{
Console.WriteLine("No pi found.");
}
Linq to xml code:
XDocument xDoc = ...;
var cssUrlQuery = from node in xDoc.Nodes()
where node.NodeType == XmlNodeType.ProcessingInstruction
select Regex.Match(((XProcessingInstruction)node).Data, "href=\"(?<url>.*?)\"").Groups["url"].Value;
or linq to objects
var cssUrls = (from XmlNode childNode in doc.ChildNodes
where childNode.NodeType == XmlNodeType.ProcessingInstruction && childNode.Name == "xml-stylesheet"
select (XmlProcessingInstruction) childNode
into procNode select Regex.Match(procNode.Data, "href=\"(?<url>.*?)\"").Groups["url"].Value).ToList();
xDoc.XPathSelectElement() will not work since it for some reasone cannot cast an XElement to XProcessingInstruction.
You can also use XPath. Given an XmlDocument loaded with your source:
XmlProcessingInstruction instruction = doc.SelectSingleNode("//processing-instruction(\"xml-stylesheet\")") as XmlProcessingInstruction;
if (instruction != null) {
Console.WriteLine(instruction.InnerText);
}
Then just parse InnerText with Regex.
To find the value using a proper XML parser you could write something like this:
using(var xr = XmlReader.Create(input))
{
while(xr.Read())
{
if(xr.NodeType == XmlNodeType.ProcessingInstruction && xr.Name == "xml-stylesheet")
{
string s = xr.Value;
int i = s.IndexOf("href=\"") + 6;
s = s.Substring(i, s.IndexOf('\"', i) - i);
Console.WriteLine(s);
break;
}
}
}
private string _GetTemplateUrl(XDocument formXmlData)
{
var infopathInstruction = (XProcessingInstruction)formXmlData.Nodes().First(node => node.NodeType == XmlNodeType.ProcessingInstruction && ((XProcessingInstruction)node).Target == "mso-infoPathSolution");
var instructionValueAsDoc = XDocument.Parse("<n " + infopathInstruction.Data + " />");
return instructionValueAsDoc.Root.Attribute("href").Value;
}
XmlProcessingInstruction stylesheet = doc.SelectSingleNode("processing-instruction('xml-stylesheet')") as XmlProcessingInstruction;

Categories

Resources