C# Edit XML, I am totally lost - c#

I have this code to Load xml files that I am not sure of if it is complete. This is my code.
public void updateXML(string xmlFile, string chooseNode, string chooseSingleNode, string newNode, string selectedCategory)
{
XmlDocument xml = new XmlDocument();
xml.Load(xmlFile);
foreach (XmlElement element in xml.SelectNodes(chooseNode))
{
foreach (XmlElement element1 in element)
{
if (element.SelectSingleNode(chooseNode).InnerText == selectedCategory)
{
XmlNode newvalue = xml.CreateElement(newNode);
newvalue.InnerText = "MODIFIED";
element.ReplaceChild(newvalue, element1);
xml.Save(xmlFile);
}
}
}
Below is the method that I use in the end, where I set xmlfile and such. (the updateXML method is in "data.cs", which is called on from the repository.
public void editCategory(string newNode)
{
string xmlFile = "Category.xml";
string chooseNodes = "ArrayOfCategory/Category";
string chooseSingleNode = "//Name";
string selectedCategory = "News";
repository.Update(xmlFile, chooseNodes, newNode, chooseSingleNode, selectedCategory);
}
I am unsure of what to put in the diffrent Nodes etc, the code above I found here on Stackoverflow.
- Below is my XMLfile that I want to edit.
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCategory xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Category>
<Id>6b30511d-2cd1-4325-ad73-7b905f76ffc0</Id>
<Name>News</Name>
</Category>
<Category>
<Id>516401f4-b45c-46ef-b8f4-9d05021ae794</Id>
<Name>Pods</Name>
</Category>
<Category>
<Id>0c9cd216-86cf-4a62-884c-1b428150ebac</Id>
<Name>Pods</Name>
</Category>
</ArrayOfCategory>
I would really appreciate your help.

if (element.SelectSingleNode(chooseNode).InnerText == selectedCategory)
ChooseNode = "ArrayOfCategory/Category"
selectedCategory = "News";
So the innertext of chooseNode will never be "News" because "News" is under <Name>

There's something wrong in your second foreach : did you forget to put element.SelectNodes or something ?
Next thing: you can modify an XmlElement directly, no need to create a new one. You create (and add it) only if it's not there.
I strongly recommend you have a look at the MSDN documentation of XmlDocument, more specifically CreateElement and this simple example following the SelectNodes presentation.
Moreover, you may want to consider putting an # in front of your strings :
see What's the # in front of a string in C#?

Related

Select single node

From the following xml:
<response>
<content>
<Result xmlns="http://www.test.com/nav/webservices/types">
<Name>Test</Name>
</Result>
</content>
<status>ok</status>
</response>
I am trying to get the value of the Name element the following way but that does not work:
private static void Main()
{
var response = new XmlDocument();
response.Load("Response.xml");
var namespaceManager = new XmlNamespaceManager(response.NameTable);
namespaceManager.AddNamespace("ns", "http://www.test.com/nav/webservices/types");
Console.WriteLine(response.SelectSingleNode("/response/content/Result/Name", namespaceManager).InnerXml);
}
How can I select the Name element?
Your code would have worked just fineif the Xml had defined the namespace with a "ns:" prefix.
But in this case, the namespace is given without any prefix, which sets the default namespace for everything in the Result tag to ".../webservice/types".
To reflect this, you need to modify the Xpath, and tell the XmlDocument that the nodes you are looking for under Resultare in the webservice/types namespace. So your query will look like this:
Console.WriteLine(response.SelectSingleNode(#"/response/content/ns:Result/ns:Name", namespaceManager).InnerXml);
For getting directly the text value of a node there is a text() function, if used in the query it would look like:
/response/content/Result/Name/text()
Try this:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.InnerXml = "<response><content><Result xmlns=\"http://www.test.com/nav/webservices/types\"><Name>Test</Name></Result></content><status>ok</status>";
string elementValue = String.Empty;
if (xmlDoc != null)
{
xNode = xmlDoc.SelectSingleNode("/Result");
xNodeList = xNode.ChildNodes;
foreach (XmlNode node in xNodeList)
{
elementValue = node.InnerText;
}
}

How to check XMLNode is exist or not in Windows Phone

I am writing Windows Phone 8 application, and I am getting XML data from web service,in some response, in my XML document I am getting some "Tags" and in other response I do not getting those tags, so how do I check that XNode is exist or not?
See my XML document below:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Group>
<Id>205647</Id>
<Name>Docs</Name>
</Group>
<Group>
<Id>205648</Id>
<Name>Photos</Name>
</Group>
</root>
Now, in above document, the Descendant "GROUP" is exist in some results and doesn't exist in other results,How do I check that?
Create an extension method like this :
public static string TryGetElementValue(this XElement parentEl, string elementName, string defaultValue = null)
{
var foundEl = parentEl.Element(elementName);
if(foundEl != null)
{
return foundEl.Value;
}
else
{
return defaultValue;
}
}
This approach allows you to keep a clean code with isolating the check of element presence. It also allow you to define a default value, which can be helpful
You could go through all nodes with the XmlTextReader and look for a specific XmlNode Name.
http://www.w3schools.com/xpath/xpath_syntax.asp
Try this snippet with your xml:
XmlDocument doc = new XmlDocument();
doc.Load("your.xml");
//Select the book node with the matching attribute value.
XmlNode nodeToFind;
XmlElement root = doc.DocumentElement;
// Selects all the title elements that have an attribute named group
nodeToFind = root.SelectSingleNode("//title[#group]");
if( nodeToFind != null )
{
// It was found, manipulate it.
}
else
{
// It was not found.
}
Have a look at this too. updating an existing xml file in Windows Phone
Hope it helps!

Process IMSManifest.xml with XPath and C#

I am unfamiliar with XPath and I'm looking for guidance on simply selecting three values from a file: schemaversion, title and description.
The Xpath expression //title/langstring only matches the element value when I strip out the name spacing information from <manifest> and <lom>.
What is the correct way to search the contents for these values?
Unit test:
[Test]
public void TitleIsNotNull()
{
var manifestManager = new ManifestManager("imsmanifest.xml");
// Code which initializes object and calls GetTitle() is encapsulated.
Assert.IsNotNullOrEmpty(manifestManager.Title);
}
System Under Test:
private string GetTitle()
{
var document = XElement.Parse(_contents);
const string XpathExpression = "//title/langstring";
return (string)document.XPathSelectElement(XpathExpression);
}
_contents (excerpted):
<?xml version="1.0" encoding="utf-8"?>
<manifest xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2
imscp_rootv1p1p2.xsd
http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd
http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
version="1.0"
identifier="ExampleIdGoesHere">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
<lom xsi:schemaLocation="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1
imsmd_rootv1p2p1.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1">
<general>
<title>
<langstring xml:lang="x-none">Example title goes here.</langstring>
</title>
<description>
<langstring xml:lang="x-none">Example description goes here.</langstring>
</description>
</general>
</lom>
</metadata>
Tweaked Code Based on Steven Doggart's solution
//Revised
private string GetTitle()
{
var xmlReader = GetXmlReader();
var document = XElement.Load(xmlReader);
var xmlNamespaceManager = GetXmlNamespaceManager(xmlReader);
const string XpathExpression = "//y:title/y:langstring";
return (string)document.XPathSelectElement(XpathExpression, xmlNamespaceManager);
}
//Private Helpers
private XmlReader GetXmlReader()
{
var contents = new StringReader(_contents);
var xmlReader = XmlReader.Create(contents);
return xmlReader;
}
private XmlNamespaceManager GetXmlNamespaceManager(XmlReader xmlReader)
{
if (xmlReader.NameTable != null)
{
var xmlNamespaceManager = new XmlNamespaceManager(xmlReader.NameTable);
xmlNamespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
xmlNamespaceManager.AddNamespace("y", "http://www.imsglobal.org/xsd/imsmd_rootv1p2p1");
return xmlNamespaceManager;
}
return null;
}
The problem you are having is that the element you are trying to select actually belongs to a certain namespace, but you are not specifying the namespace when you select it. The title and langstring elements both belong to the default namespace. In the XML document, the default namespace is defined as "http://www.imsproject.org/xsd/imscp_rootv1p1p2". With XPath, there is no way to specify a default namespace. If you do not provide a namespace, it always assumes you mean no namespace at all. Therefore, to select that element, you will have to explicitly provide the namespace, like this:
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
namespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
const string XpathExpression = "//x:title/x:langstring";
return (string)document.XPathSelectElement(XpathExpression, namespaceManager);
The trick, however, is getting the XmlNameTable to give to the XmlNamespaceManager. Unfortunately, the XElement class does not provide a way to get the XmlNameTable for the document, so your best bet would be to load it via an XmlReader, which can provide that, like this:
XmlReader reader = XmlReader.Create(new StringReader(_contents));
XElement document = XElement.Load(reader);
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(reader.NameTable);
namespaceManager.AddNamespace("x", "http://www.imsproject.org/xsd/imscp_rootv1p1p2");
const string XpathExpression = "//x:title/x:langstring";
return (string)document.XPathSelectElement(XpathExpression, namespaceManager);
Alternatively, you could use XmlDocument which is slightly easier when dealing with namespaces. Or, you could also choose to use LINQ to select the element instead of XPath.

Need advice on removing xml row

I'm using a CMS and found a function to generate a rss feed from content within folders. However I would like one of the rows removing from the list. I've done my research and I 'think' I should be using XmlDocument class to help me remove the row I don't want. I've used Firebug and FirePath to get the XPath - but I cant seem to figure out how to apply it appropriately. I am also uncertain of whether I should be using .Load or .LoadXml - I've used the latter seing as though the feed displays fine. However I have had to convert ToString() to get rid of that overloaded match error....
The row I want removing is called "Archived Planes"
The XPath I get for FirePath is ".//*[#id='feedContent']/xhtml:div[11]/xhtml:h3/xhtml:a"
I am also assuming that .RemoveChild(node); will remove it out of rssData before I Response.Write. Thanks
Object rssData = new object();
Cms.UI.CommonUI.ApplicationAPI AppAPI = new Cms.UI.CommonUI.ApplicationAPI();
rssData = AppAPI.ecmRssSummary(50, true, "DateCreated", 0, "");
Response.ContentType = "text/xml";
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rssData.ToString());
XmlNode node = xmlDocument.SelectSingleNode(#"xhtml:div/xhtml:h3/xhtml[a = 'Archived Planes']");
if (node != null)
{
node.ParentNode.RemoveChild(node);
}
Response.Write(rssData);
Edited to include output below
This is the what the response.write from rssData is pumping out:
<?xml version="1.0" ?>
<rss xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
<channel>
<title>Plane feed</title>
<link>http://www.domain.rss1.aspx</link>
<description></description>
<item>
<title>New Planes</title>
<link>http://www.domainx1.aspx</link>
<description>
This is the description
</description>
<author>Andrew</author>
<pubDate>Thu, 16 Aug 2012 15:55:53 GMT</pubDate>
</item>
<item>
<title>Archived Planes</title>
<link>http://www.domain23.aspx</link>
<description>
Description of Archived Planes
</description>
<author>Jan</author>
<pubDate>Wed, 15 Aug 2012 10:34:23 GMT</pubDate>
</item>
</channel>
</rss>
I suspect your xpath is incorrect, it looks like some funky dom element that you are referencing and not the xml element... e.g. for the following xml
<?xml version="1.0" encoding="UTF-16" standalone="yes"?>
<NewDataSet>
<userinfo>
<username>pqr2</username>
<pass>abc</pass>
<addr>abc</addr>
</userinfo>
<userinfo>
<username>pqr1</username>
<pass>pqr2</pass>
<addr>pqr3</addr>
</userinfo>
</NewDataSet>
This code will remove the userinfo node with an username element of pqr1
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(#"file.xml");
XmlNode node = xmlDocument.SelectSingleNode(#"NewDataSet/userinfo[username = 'pqr1']");
if (node != null) {
node.ParentNode.RemoveChild(node);
xmlDocument.Save(#"file.xml");
}
Thought I would post the answer, although I'll mark Pauls as the answer, as his code/advice was the basis of this and my further research. Still don't know what the '#' in SelectSingleNode is and whether I should really have it - will do more research.
Object rssData = new object();
Cms.UI.CommonUI.ApplicationAPI AppAPI = new Cms.UI.CommonUI.ApplicationAPI();
rssData = AppAPI.ecmRssSummary(50, true, "DateCreated", 0, "");
Response.ContentType = "text/xml";
Response.ContentEncoding = System.Text.Encoding.UTF8;
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(rssData.ToString());
XmlNode node = xmlDocument.SelectSingleNode("rss/channel/item[title = 'Archived Planes']");
if (node != null)
try
{
node.ParentNode.RemoveChild(node);
xmlDocument.Save(Response.Output);
}
catch { }
else { Response.Write(rssData); }
}
The # symbol is simply to denote a verbatim string literal (allows you to have funky characters in the string compared to the normal string declaration) e.g.
string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me
string f = #"Joe said ""Hello"" to me"; // Joe said "Hello" to me
See this msdn link for more info

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

Categories

Resources