Removing all child node elements from XML in C#? [duplicate] - c#

This question already has answers here:
how to remove all the childnodes of the specified xmlnode in C# 4.0?
(6 answers)
Closed 5 years ago.
I have the following XML structure:
XML
<?xml version="1.0" encoding="utf-8" ?>
<Users>
</Users>
At some point, the <Users> gets filled up with different users, but I want to be able to delete them all in a single, simple function.
Attempt
/// <summary>
/// Removes all nodes from XML creds file on application close
/// </summary>
public static void RemoveXMLData()
{
string xmlPath = Path.Combine(Environment.CurrentDirectory, #"Data\Credential.xml");
XDocument document = XDocument.Load(xmlPath);
document.RemoveNodes();
}
This doesn't seem to be working. I've done the following as well:
Attempt #2
string xmlPath = Path.Combine(Environment.CurrentDirectory, #"Data\Credential.xml");
XDocument document = XDocument.Load(xmlPath);
foreach (var node in document.Descendants("Users"))
{
node.Remove();
}
But this yields an exception error.
At the end of the day, I just want to get back to square one with the following file data:
<?xml version="1.0" encoding="utf-8" ?>
<Users>
</Users>

This should work fine:
XDocument document = XDocument.Load(xmlPath);
document.Descendants("Users").Elements().Remove();
As suggested by #Cory, Alternative and faster approach would be:
document.Root.Elements().Remove();

It probably blows up on the second attempt as you are iterating the collection you remove from. Try this.
string xmlPath = Path.Combine(Environment.CurrentDirectory, #"Data\Credential.xml");
XDocument document = XDocument.Load(xmlPath);
var nodes = document.Descendants("Users");
while(nodes.Count > 0)
{
nodes[0].Remove();
}

Related

How do I generate this XML in C#? [duplicate]

This question already has answers here:
Add Stylesheet reference to XML Document in Linq?
(1 answer)
Creating XDocument with xsi:schemaLocation namespace
(1 answer)
Closed last year.
I have an example XML file that I need to generate on the fly in a console application.
This is an example of the first part of the required XML document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="ABC123.xsl" ?>
<CPPData xsi:noNamespaceSchemaLocation="CPPData_V1.14.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Envelope>
<EnvelopeNode NumOrdre="1">
</EnvelopeNode>
</Envelope>
</CPPData>
I have a method creates and returns an XElement which contains all the data required by the body of the XML (e.g. everything inside the CPPData element).
However I can't figure out how to add the following:
<?xml-stylesheet type="text/xsl" href="ABC123.xsl" ?> to the XDocument
<CPPData xsi:noNamespaceSchemaLocation="CPPData_V1.14.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> to the XElement
var xml = new XDocument();
var xp = new XProcessingInstruction(target: "xml-stylesheet", data: #"type=""text/xsl"" href=""ABC123.xsl""");
xml.Add(xp);
XNamespace ns = "http://www.w3.org/2001/XMLSchema-instance";
xml.Add(new XElement("root",
new XAttribute(ns + "noNamespaceSchemaLocation", "CPPData_V1.14.xsd"),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance")
));

Why can I not read XML [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why XDocument can’t get element out of this wellform XML text?
I'm trying to read an xml using linq to xml, and i guess i'm understanding something wrong.
This is the start of the xml (it's long so i'm not posting it all)
<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
<Body>
<ReportItems>
<Tablix Name="Tablix12">
......
......
</Tablix>
This xml could have a few of "Tablix" elements, and might have 1 or none, for each one of these i want to read whats inside this tag and i'm having difficulty to start.
I have tried a few ways to get the "Tablix" elements, or any other element.
In this code i get a result only for the "var root", the rest of them are always null and i don't understand what i'm doing wrong.
public ReadTablixResponse ReadTablixAdvanced(string rdl)
{
XDocument xml = XDocument.Parse(rdl);
var root = xml.Root;
var Body = xml.Root.Element("Body");
var report = xml.Root.Element("Report");
var aa = xml.Element("Report");
var bb = xml.Element("Body");
var test = xml.Elements("Tablix");
One thing i noticed, is that you used the method Element("name"). which will always try to retrun the first (in document order) direct child element with the specified XName . and that is probebly why you got null.
if you want to return deeper elements(from where you looking). you need to use the Descendants("name") method, which will return a collection of all descendants elements . no matter how deep they are (relative to your chosen anchor)...
for example:
XNamespace xNameSpace = "http://schemas.micro.....";
// ...
var tablixes= xml.Descendants(xNameSpace + "Tablix");
which you can then wolk through:
foreach (var tablix in tablixes)
{
var name=(string)tablix.Attribute("Name");
var age=(int)tablix.Element("age");
...
}
XDocument xDocument = XDocument.Parse(rdl);
XNamespace xNameSpace = "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition";
var tablixes= from o in xDocument.Descendants(xNameSpace + "Tablix")
select o.Value;

Split XML document apart creating multiple output files from repeating elements

I need to take an XML file and create multiple output xml files from the repeating nodes of the input file. The source file "AnimalBatch.xml" looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Animals>
<Animal id="1001">
<Quantity>One</Quantity>
<Adjective>Red</Adjective>
<Name>Rooster</Name>
</Animal>
<Animal id="1002">
<Quantity>Two</Quantity>
<Adjective>Stubborn</Adjective>
<Name>Donkeys</Name>
</Animal>
<Animal id="1003">
<Quantity>Three</Quantity>
<Color>Blind</Color>
<Name>Mice</Name>
</Animal>
</Animals>
The program needs to split the repeating "Animal" and produce 3 files named: Animal_1001.xml, Animal_1002.xml, and Animal_1003.xml
Each output file should contain just their respective element (which will be the root). The id attribute from AnimalsBatch.xml will supply the sequence number for the Animal_xxxx.xml filenames. The id attribute does not need to be in the output files.
Animal_1001.xml:
<?xml version="1.0" encoding="utf-8"?>
<Animal>
<Quantity>One</Quantity>
<Adjective>Red</Adjective>
<Name>Rooster</Name>
</Animal>
Animal_1002.xml
<?xml version="1.0" encoding="utf-8"?>
<Animal>
<Quantity>Two</Quantity>
<Adjective>Stubborn</Adjective>
<Name>Donkeys</Name>
</Animal>
Animal_1003.xml>
<?xml version="1.0" encoding="utf-8"?>
<Animal>
<Quantity>Three</Quantity>
<Adjective>Blind</Adjective>
<Name>Mice</Name>
</Animal>
I want to do this with XmlDocument, since it needs to be able to run on .Net 2.0.
My program looks like this:
static void Main(string[] args)
{
string strFileName;
string strSeq;
XmlDocument doc = new XmlDocument();
doc.Load("D:\\Rick\\Computer\\XML\\AnimalBatch.xml");
XmlNodeList nl = doc.DocumentElement.SelectNodes("Animal");
foreach (XmlNode n in nl)
{
strSeq = n.Attributes["id"].Value;
XmlDocument outdoc = new XmlDocument();
XmlNode rootnode = outdoc.CreateNode("element", "Animal", "");
outdoc.AppendChild(rootnode); // Put the wrapper element into outdoc
outdoc.ImportNode(n, true); // place the node n into outdoc
outdoc.AppendChild(n); // This statement errors:
// "The node to be inserted is from a different document context."
strFileName = "Animal_" + strSeq + ".xml";
outdoc.Save(Console.Out);
Console.WriteLine();
}
Console.WriteLine("END OF PROGRAM: Press <ENTER>");
Console.ReadLine();
}
I think I have 2 problems.
A) After doing the ImportNode on node n into outdoc, I call outdoc.AppendChild(n) which complains: "The node to be inserted is from a different document context." I do not know if this is a scope issue referencing node n within the ForEach loop - or if I am somehow not using ImportNode() or AppendChild properly. 2nd argument on ImportNode() is set to true, because I want the child elements of Animal (3 fields arbitrarily named Quantity, Adjective, and Name) to end up in the destination file.
B) Second problem is getting the Animal element into outdoc. I'm getting '' but I need ' ' so I can place node n inside it. I think my problem is how I am doing: outdoc.AppendChild(rootnode);
To show the xml, I'm doing: outdoc.Save(Console.Out); I do have the code to save() to an output file - which does work, as long as I can get outdoc assembled properly.
There is a similar question at: Split XML in Multiple XML files, but I don't understand the solution code yet. I think I'm pretty close on this approach, and will appreciate any help you can provide.
I'm going to be doing this same task using XmlReader, since I'm going to need to be able to handle large input files, and I understand that XmlDocument reads the whole thing in and can cause memory issues.
That's a simple method that seems what you are looking for
public void test_xml_split()
{
XmlDocument doc = new XmlDocument();
doc.Load("C:\\animals.xml");
XmlDocument newXmlDoc = null;
foreach (XmlNode animalNode in doc.SelectNodes("//Animals/Animal"))
{
newXmlDoc = new XmlDocument();
var targetNode = newXmlDoc.ImportNode(animalNode, true);
newXmlDoc.AppendChild(targetNode);
newXmlDoc.Save(Console.Out);
Console.WriteLine();
}
}
This approach seems to work without using the "var targetnode" statement. It creates an XmlNode object called targetNode from outdoc's "Animal" element in the ForEach loop. I think the main things that were problems in my original code were: A) I was getting nodelist nl incorrectly. And B) I couldn't "Import" node n, I think because it was associated specifically with doc. It had to be created as its own Node.
The problem with the prior proposed solution was the use of the "var" keyword. My program has to assume 2.0 and that came in with v3.0. I like Rogers solution, in that it is concise. For me - I wanted to do each thing as a separate statement.
static void SplitXMLDocument()
{
string strFileName;
string strSeq;
XmlDocument doc = new XmlDocument(); // The input file
doc.Load("D:\\Rick\\Computer\\XML\\AnimalBatch.xml");
XmlNodeList nl = doc.DocumentElement.SelectNodes("//Animals/Animal");
foreach (XmlNode n in nl)
{
strSeq = n.Attributes["id"].Value; // Animal nodes have an id attribute
XmlDocument outdoc = new XmlDocument(); // Create the outdoc xml document
XmlNode targetNode = outdoc.CreateElement("Animal"); // Create a separate node to hold the Animal element
targetNode = outdoc.ImportNode(n, true); // Bring over that Animal
targetNode.Attributes.RemoveAll(); // Remove the id attribute in <Animal id="1001">
outdoc.ImportNode(targetNode, true); // place the node n into outdoc
outdoc.AppendChild(targetNode); // AppendChild to make it stick
strFileName = "Animal_" + strSeq + ".xml";
outdoc.Save(Console.Out); Console.WriteLine();
outdoc.Save("D:\\Rick\\Computer\\XML\\" + strFileName);
Console.WriteLine();
}
}

Regarding C# Xml Reading

I'm currently doing a XML file that includes the "name" of the city, the "region", "lat" latitude and "lng".
Here is my Code:
XmlDocument XmlFile = new XmlDocument();
try {
XmlFile.Load("..\\..\\liste.xml");
}
catch (Exception ex)
{
Console.WriteLine("Erreur" + ex.Message);
};
XmlNodeList MyNodeXML = XmlFile.GetElementsByTagName("city");
foreach (XmlNode unNode in MyNodeXML)
{
string nomVille = unNode.Attributes[0].Value;
string lat = unNode.Attributes[1].Value;
string lng = unNode.Attributes[2].Value;
listeCooVilles.Add(nomVille, new PointF(float.Parse(lat), float.Parse(lng)));
}
Where listeCooVilles is a Dictionnary.
Here is my XML: I did a sample for test:
<?xml version="1.0" encoding="UTF-8"?>
<cities>
<city>
<name>Abercorn</name>
<region>Montérégie</region>
<lat>45.032999</lat>
<lng>-72.663057</lng>
</city>
<cities>
I saw many post doing the same as above in StackOverflow, but I still get an IndexOutOfRange Exception on the line
string nomVille = unNode.Attributes[0].Value;
Can someone help? Thanks!
The element has no attributes - only sub-elements. Attributes are name=value pairs at the same level as the element. E.g.
<?xml version="1.0" encoding="UTF-8"?>
<cities>
<city name="Abercorn" region="Montérégie" lat="45.032999" lng="-72.663057" />
<city name="Granby" region="Montérégie" lat="45.4" lng="-72.733333" />
</cites>
Nesting elements (as you have done originally) and using attributes (as you've coded for) are both equally valid ways of structuring your XML document.
As pointed our those are elements not attributes. Your code needs to change to this:
nomVille = unNode.Item["name"].Value
region = unNode.Item["region"].Value
lat = unNode.Item["lat"].Value
lng = unNode.Item["lng"].Value
None of the nodes in your XML sample have attributes, which is why the collection has null elements in it.
Try changing it to:
<?xml version="1.0" encoding="UTF-8"?>
<cities>
<city testAttr = "hello!">
<name>Abercorn</name>
<region>Montérégie</region>
<lat>45.032999</lat>
<lng>-72.663057</lng>
</city>
<cities>
The addition of the testAttr should provide a valid collection in unNode.Attributes.
You are using attributes in city tag but I think you should be using xml elements.

How to correctly parse an XML document with arbitrary namespaces

I am trying to parse somewhat standard XML documents that use a schema called MARCXML from various sources.
Here are the first few lines of an example XML file that needs to be handled...
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<marc:collection xmlns:marc="http://www.loc.gov/MARC21/slim" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd">
<marc:record>
<marc:leader>00925njm 22002777a 4500</marc:leader>
and one without namespace prefixes...
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<collection xmlns="http://www.loc.gov/MARC21/slim">
<record>
<leader>01142cam 2200301 a 4500</leader>
Key point: in order to get the XPaths to resolve further along in the program I have to go through a regex routine to add the namespaces to the NameTable (which doesn't add them by default). This seems unnecessary to me.
Regex xmlNamespace = new Regex("xmlns:(?<PREFIX>[^=]+)=\"(?<URI>[^\"]+)\"", RegexOptions.Compiled);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlRecord);
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
MatchCollection namespaces = xmlNamespace.Matches(xmlRecord);
foreach (Match n in namespaces)
{
nsMgr.AddNamespace(n.Groups["PREFIX"].ToString(), n.Groups["URI"].ToString());
}
The XPath call looks something like this...
XmlNode leaderNode = xmlDoc.SelectSingleNode(".//" + LeaderNode, nsMgr);
Where LeaderNode is a configurable value and would equal "marc:leader" in the first example and "leader" in the second example.
Is there a better, more efficient way to do this? Note: suggestions for solving this using LINQ are welcome, but I would mainly like to know how to solve this using XmlDocument.
EDIT: I took GrayWizardx's advice and now have the following code...
if (LeaderNode.Contains(":"))
{
string prefix = LeaderNode.Substring(0, LeaderNode.IndexOf(':'));
XmlNode root = xmlDoc.FirstChild;
string nameSpace = root.GetNamespaceOfPrefix(prefix);
nsMgr.AddNamespace(prefix, nameSpace);
}
Now there's no more dependency on Regex!
If you know there is going to be a given element in the document (for instance the root element) you could try using GetNamespaceOfPrefix.

Categories

Resources