I'm trying to output a specific node to output to a text file(output to console fine) but I keep getting an error message: 'System.Xml.XmlNodeList' to 'string[]' on this line:
string[] lines = elemList;
Here is some more code:
namespace countC
{
class Program
{
static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load("list.xml");
XmlElement root = doc.DocumentElement;
XmlNodeList elemList = root.GetElementsByTagName("version");
for (int i = 0; i < elemList.Count; i++)
{
Console.WriteLine(elemList[i].InnerXml);
string[] lines = elemList;
System.IO.File.WriteAllLines(#"C:\VBtest\STIGapp.txt", lines);
}
Console.ReadKey();
}
}
}
The error is because you are trying to assign an object of type XmlNodeList to a variable of type string[] - the two are incompatible and you can't assign one from the other.
If you do this instead then it will at least compile:
string line = elemList[i].InnerXml;
System.IO.File.WriteAllText(#"C:\VBtest\STIGapp.txt", line);
Although I'm not sure that it will do what you want (if elemList contains more that one element the above will keep overwriting the given file).
elemList is an XmlNodeList, you can't implicitly cast it to a string array.
You could try this
string line = elemList[i].InnerText;
System.IO.File.WriteAllLines(#"C:\VBtest\STIGapp.txt", line);
but this of course, depends on your data.
Related
Recently I have built a small converter that converts txt data to xml in a certain structure , I choose a folder and the program loops through all the files in that folder and write in a XML format all together in one xml document.
In the folder I have data names like:
Data.0001.txt
Data.0002.txt
Data.0003.txt
Data.0004.txt
Data.txt
and so on
I want only the files that dose NOT contain zeros in them because ones with zeros are just a back up copy for the others , and i have over 6000 file i can't filter them manually
Here is my code so far
static void Main(string[] args)
{
FolderBrowserDialog SelectFolder = new FolderBrowserDialog();
String path = #"C:\newpages";
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("Pages");
if (SelectFolder.ShowDialog() == DialogResult.OK)
{
var txt = string.Empty;
string[] Files = Directory.GetFiles((SelectFolder.SelectedPath));
int i = 1;
foreach (string path1 in Files)
{
String filename = Path.GetFileNameWithoutExtension((path1));
using (StreamReader sr = new StreamReader(path1))
{
txt = sr.ReadToEnd();
XmlElement id = doc.CreateElement("Page.id");
id.SetAttribute("Page.Nr", i.ToString());
id.SetAttribute("Pagetitle", filename);
XmlElement name = doc.CreateElement("PageContent");
XmlCDataSection cdata = doc.CreateCDataSection(txt);
name.AppendChild(cdata);
id.AppendChild(name); // page id appenndchild
root.AppendChild(id); // roots appenedchild
doc.AppendChild(root); //Main root
}
i++;
}
}
Console.WriteLine("finished");
Console.ReadKey();
doc.Save(Path.ChangeExtension(path, ".xml"));
}
}
Any help would be really nice guys
GetFiles returns the name of a file in a specified directory. Its return type is string[] so you can easily apply a Where to filter the file names as follow:-
var files = Directory.GetFiles("PathToYourDirec").Where(name => !name.Contains("0"));
On the string filename you could make sure it doesn't contain "0"
if(!filename.Contains("0"))
{
}
On the Files variable you can use a regex to filter out the filenames which contains only letters
var reg = new Regex(#"^([^0-9]*)$");
var files = Directory.GetFiles("path-to-folder")
.Where(path => reg.IsMatch(path))
.ToList();
The whole code can be largely simplified while solving this issue. You don't need a StreamReader just to read the whole file, and you may as well get the filename early and filter instead of going into the foreach and filtering :
static void Main(string[] args)
{
FolderBrowserDialog SelectFolder = new FolderBrowserDialog();
String path = #"C:\newpages";
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("Pages");
if (SelectFolder.ShowDialog() == DialogResult.OK)
{
// Don't declare txt here, you're overwriting and only using it in a nested loop, declare it as you use it there
// var txt = string.Empty;
//string[] Files = Directory.GetFiles((SelectFolder.SelectedPath));
// Change to getting FileInfos
var Files = new DirectoryInfo(SelectFolder.SelectedPath).GetFiles()
// Only keep those who don't contain a zero in file name
.Where(f=>!f.Name.Contains("0"));
int i = 1;
foreach (var file in Files)
{
//String filename = Path.GetFileNameWithoutExtension((path1));
// Don't need a StreamReader not a using block, just read the whole file at once with File.ReadAllText
//using (StreamReader sr = new StreamReader(path1))
//{
//txt = sr.ReadToEnd();
var txt = File.ReadAllText(file.FullName);
XmlElement id = doc.CreateElement("Page.id");
id.SetAttribute("Page.Nr", i.ToString());
id.SetAttribute("Pagetitle", file.FullName);
XmlElement name = doc.CreateElement("PageContent");
XmlCDataSection cdata = doc.CreateCDataSection(txt);
name.AppendChild(cdata);
id.AppendChild(name); // page id appenndchild
root.AppendChild(id); // roots appenedchild
doc.AppendChild(root); //Main root
//}
i++;
}
}
Console.WriteLine("finished");
Console.ReadKey();
doc.Save(Path.ChangeExtension(path, ".xml"));
}
I would also advise not working with the XML api you're using but with the more recent and simpler linq to XML one as that would simplify creating your elements too, see bellow a very simplified version of the whole code as i'd have writen it with LINQ and XElements
static void Main(string[] args)
{
FolderBrowserDialog SelectFolder = new FolderBrowserDialog();
String path = #"C:\newpages";
var root = new XElement("Pages");
if (SelectFolder.ShowDialog() == DialogResult.OK)
{
var FilesXML = new DirectoryInfo(SelectFolder.SelectedPath).GetFiles()
.Where(f => !f.Name.Contains("0"))
// Note that the index is 0 based, if you want to start with 1 just replace index by index+1 in Page.Nr
.Select((file, index) =>
new XElement("Page.id",
new XAttribute("Page.Nr",index),
new XAttribute("Pagetitle",file.FullName),
new XElement("PageContent",
new XCData(File.ReadAllText(file.FullName))
)));
// Here we already have all your XML ready, just need to add it to the root
root.Add(FilesXML);
}
Console.WriteLine("finished");
Console.ReadKey();
root.Save(Path.ChangeExtension(path, ".xml"));
}
You can try this, but I would suggest if you change the logic of creating name of backup files. It should not depend on "0" as character in it but instead of that firm text like "backup" should be mentioned in file name.
static void Main(string[] args)
{
FolderBrowserDialog SelectFolder = new FolderBrowserDialog();
String path = #"C:\newpages";
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("Pages");
if (SelectFolder.ShowDialog() == DialogResult.OK)
{
var txt = string.Empty;
string[] Files = Directory.GetFiles((SelectFolder.SelectedPath));
int i = 1;
foreach (string path1 in Files)
{
String filename = Path.GetFileNameWithoutExtension((path1));
if (!filename.Contains(".0"))
{
using (StreamReader sr = new StreamReader(path1))
{
txt = sr.ReadToEnd();
XmlElement id = doc.CreateElement("Page.id");
id.SetAttribute("Page.Nr", i.ToString());
id.SetAttribute("Pagetitle", filename);
XmlElement name = doc.CreateElement("PageContent");
XmlCDataSection cdata = doc.CreateCDataSection(txt);
name.AppendChild(cdata);
id.AppendChild(name); // page id appenndchild
root.AppendChild(id); // roots appenedchild
doc.AppendChild(root); //Main root
}
}
i++;
}
}
Console.WriteLine("finished");
Console.ReadKey();
doc.Save(Path.ChangeExtension(path, ".xml"));
}
I have XML which is in project folder. I am loading the contents using this
XmlDocument doc = new XmlDocument();
string testFilesLocation = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string dataSource;
string xmlFileName = "Claim.txt";
if (System.IO.Directory.Exists(testFilesLocation + #"\Resources"))
{
dataSource = testFilesLocation + #"\Resources\" + xmlFileName;
}
else
{
dataSource = testFilesLocation + #"\" + xmlFileName;
}
doc.Load(dataSource);
XML has following nodes
<ClaimKeyInfo>
<CompanyID>XXXX</CompanyID>
<ClaimNum>XX-XXXXX-XX<ClaimNum>
</ClaimKeyInfo>
<ClaimInfo>
<ClaimNum>XX-XXXXX-XX</ClaimNum>
<ClaimSensitivityInd>N</ClaimSensitivityInd>
<ClaimStatus>Open</ClaimStatus>
<ClaimInfo>
I am doing this to get ClaimNum elements
XmlElement root = doc.DocumentElement;
XmlNodeList elemList = root.GetElementsByTagName("ClaimNum");
for (int i = 0; i< elemList.Count; i++)
{
elemList[i].InnerXml = "YY-YYYYY-YY";
doc.Save(dataSource);
}
I do get both the elements in elemList but I am not able to change the values inside it.
Any help would be appreciated.
You can use LINQ to XML for that.
var xDoc = XDocument.Load("filePath");
var numbers = xDoc.Descendants("ClaimNum");
foreach(var number in numbers)
{
// do your work with numbers
number.Value = "123456";
}
xDoc.Save("filePath");
Here, the numbers are contains XElements which is your ClaimNums.You can change the Value property and save the XML file.If you want to get specific numbers you can use Where extension method.If you want more details see these documentations:
How to: Find an Element with a Specific Attribute
How to: Find an Element with a Specific Child Element
I have two XML files that I need to compare for differences, the XML is very simple:
File 1:
<?xml version="1.0" encoding="utf-8"?>
<Feeds zone="my zone">
<Feed name="attribDump.json">ac1f07edc491a3d237cdfb1a17fc4551</Feed>
<Feed name="focus_GroupsKV.txt">0f9e0a14a4ffce6ff5065b6e088c1f84</Feed>
<Feed name="NAM_FORMATTED.csv">9e875496cdb072b5e54318d51295fdba</Feed>
<Feed name="BNP\activityTitles.txt">2d27c0f19b71b4b411bcb00011d3f8b0</Feed>
</Feeds>
and File 2:
<?xml version="1.0" encoding="utf-8"?>
<FeedsRequest version="1">
<Feeds zone="my zone">
<Feed name="attribDump.json">ac1f07edc491a3d237cdfb1a17fc4551</Feed>
<Feed name="focus_GroupsKV.txt">0f9e0a14a4ffce6ff5065b6e088c1f84</Feed>
<Feed name="BNP\activityTitles.txt">e54c5b851ee3ff3f43b10d24f2316431</Feed>
</Feeds>
</FeedsRequest>
File 1 is an inventory list of files on our file share and File 2 is used by a disconnected device that will need to be refreshed from File 1. The checks I need to make are 1) make sure all of the feeds in File 1 are in File 2 and 2) make sure any feeds that are found have the same hashCode(the long character string). Once the checks have been completed, I need to create a response file that has a list of all of the feeds and then an attribute on each one that designates ok (file was found and matched), missing (file wasn't found), or updated (file was found but it was an older version).
So basically the result file would look as such:
<?xml version="1.0" encoding="utf-8"?>
<FeedsResponse version="1">
<Feeds zone="my zone">
<Feed name="attribDump.json" status="ok">ac1f07edc491a3d237cdfb1a17fc4551</Feed>
<Feed name="focus_GroupsKV.txt" status="ok">0f9e0a14a4ffce6ff5065b6e088c1f84</Feed>
<Feed name="NAM_FORMATTED.csv" status="missing">afd2c620053ed4f85ab02b4cc5f7a2b2</Feed>
<Feed name="BNP\activityTitles.txt" status="updated">90805b851ee3ff3f43b10d24f2316431</Feed>
What I'm doing currently is looping through all of the files in File 1, then checking them against File 2 for differences. Where I'm stuck, been a while since I've worked with XML, is how to build out the response document.
FileInfo feedList = new FileInfo(_feedList);
FileInfo feedRequest = new FileInfo(_feedRequest);
// Load the documents
XmlDocument feedListXmlDoc = new XmlDocument();
feedListXmlDoc.Load(_feedList);
// Load the documents
XmlDocument feedRequestXmlDoc = new XmlDocument();
feedRequestXmlDoc.Load(_feedRequest);
//create response doc
XmlDocument feedResponseXmlDoc = new XmlDocument();
// Define a single node
XmlNode feedListNode;
XmlNode feedRequestNode;
// Get the root Xml element
XmlElement feedListRoot = feedListXmlDoc.DocumentElement;
XmlElement feedRequestRoot = feedRequestXmlDoc.DocumentElement;
// Get a list of all player names
XmlNodeList feedListXml = feedListRoot.GetElementsByTagName("Feed");
XmlNodeList feedRequestXml = feedRequestRoot.GetElementsByTagName("Feed");
// Create an XmlWriterSettings object with the correct options.
XmlWriter writer = null;
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = (" ");
settings.OmitXmlDeclaration = false;
// Create the XmlWriter object and write some content.
writer = XmlWriter.Create(_resultPath, settings);
writer.WriteStartElement("FeedsDiff");
// The compare algorithm
bool feedMatch = false;
int j = 0;
try
{
// loop through list of current feeds
for (int i = 0; i < feedListXml.Count; i++)
{
feedListNode = feedListXml.Item(i);
string feedListName = feedListNode.Attributes["name"].Value.ToString();
string feedListHash = feedListXml.Item(i).InnerText.ToString();
//check feed request list for a match
while (j < feedRequestXml.Count && feedMatch == false)
{
feedRequestNode = feedRequestXml.Item(j);
string feedRequestName = feedRequestNode.Attributes["name"].Value.ToString();
//checks to see if feed names match
if (feedListName == feedRequestName)
{
feedMatch = true;
string feedRequestHash = feedRequestXml.Item(j).InnerText.ToString();
//since we found the node, we can remove it from the request list
XmlNode node = feedRequestNode.ParentNode;
node.RemoveChild(feedRequestNode);
//checks to see if hash codes match
if (feedListHash == feedRequestHash)
{
//if name and code match, move to the next one
feedMatch = true;
//add 'status="ok"' attribute to the node
//feedResponseXmlDoc.ImportNode(feedRequestNode,false);
Debug.WriteLine(feedListName + " name and hash match");
j = 0;
}
else
{
feedMatch = true;
//feed has been updated since last device sync
//need to add status='update' attribute and append file to response
Debug.WriteLine(feedListName + " name matched but hash did not");
}
}
else
{
//names didn't match
//add status="missing" to the node
j++;
}
}
feedMatch = false;
}
// end Xml document
writer.WriteEndElement();
writer.Flush();
}
finally
{
if (writer != null)
writer.Close();
}
Right now I'm trying to instantiate the response doc before the loop and then just add the elements as they are found but I'm having a hard time finding a concise way to do it. Any help is appreciated.
Take a look at differ, from my open-source project CodeBlocks over on CodePlex, it was designed for situations such as this one. It is also available on Nuget as "differ"
I figured it out:
public void CompareXml(string _feedList, string _feedRequest, string _resultPath)
{
FileInfo feedList = new FileInfo(_feedList);
FileInfo feedRequest = new FileInfo(_feedRequest);
// Load the documents
XmlDocument feedListXmlDoc = new XmlDocument();
feedListXmlDoc.Load(_feedList);
// Load the documents
XmlDocument feedRequestXmlDoc = new XmlDocument();
feedRequestXmlDoc.Load(_feedRequest);
// Define a single node
XmlNode feedListNode;
XmlNode feedRequestNode;
// Get the root Xml element
XmlElement feedListRoot = feedListXmlDoc.DocumentElement;
XmlElement feedRequestRoot = feedRequestXmlDoc.DocumentElement;
// Get a list of feeds for the stored list and the request
XmlNodeList feedListXml = feedListRoot.GetElementsByTagName("Feed");
XmlNodeList feedRequestXml = feedRequestRoot.GetElementsByTagName("Feed");
bool feedLocated = false;
int j = 0;
try
{
// loop through list of current feeds
for (int i = 0; i < feedListXml.Count; i++)
{
feedListNode = feedListXml.Item(i);
//create status attribute
XmlAttribute attr = feedListXmlDoc.CreateAttribute("status");
string feedListName = feedListNode.Attributes["name"].Value.ToString();
string feedListHash = feedListXml.Item(i).InnerText.ToString();
//check feed request list for a match
while (j < feedRequestXml.Count && feedLocated == false)
{
feedRequestNode = feedRequestXml.Item(j);
string feedRequestName = feedRequestNode.Attributes["name"].Value.ToString();
//checks to see if feed names match
if (feedRequestName == feedListName)
{
string feedRequestHash = feedRequestXml.Item(j).InnerText.ToString();
//checks to see if hashCodes match
if (feedListHash == feedRequestHash)
{
//if name and code match, set status to ok
attr.Value = "ok";
Debug.WriteLine(feedListName + " name and hash match. Status: 'ok'");
}
else
{
//if hashCodes don't match, set status attribute to updated
attr.Value = "updated";
Debug.WriteLine(feedListName + " name matched but hash did not. Status: 'updated'");
}
feedListNode.Attributes.Append(attr);
feedLocated = true;
}
else
{
//names didn't match, checking to see if we're at the end of the request list
if (j + 1 == feedRequestXml.Count)
{
//file name wasn't found in the request list, set status attribute to missing
attr.Value = "missing";
feedListNode.Attributes.Append(attr);
feedLocated = true;
j = 0;
Debug.WriteLine("Reached the end of the file request list without a match. Status: 'missing'");
}
//file name wasn't located on this pass, move to next record
j++;
}
}
feedLocated = false;
}
}
finally
{
Debug.WriteLine("Result file has been written out at " + _resultPath);
}
feedListXmlDoc.Save(_resultPath);
}
I'd like to create a simple XMLreader which reads a complete node (including subnodes) as text:
string TXML = #"<xml><text>hallöle</text></xml>";
XmlReader r = XmlReader.Create(new StringReader(TXML));
r.Read(); r.Read();
string o = r.ReadOuterXml();
ReadOuterXml does the job but it unescapes the already escaped signs:
"<text>hallöle</text>"
I whish to have the result:
"<text>hallöle</text>"
How can I ommit that 'unescaping'. I want to store this fragments to a db and do need that escaping. Furthermore I dont want to parse and recreate the fragments.
I had a similar problem, I wanted to keep the escaped characters when reading from xml, but in may case when calling ReadOuterXml(), only some of characters were kept and at least oane was transformed (I had " instead of ")
My solution was the following:
string TXML = #"<xml><text>hallöle</text></xml>";
TXML = TXML.Replace("&", "&");
XmlTextReader r = new XmlTextReader(new StringReader(TXML));
r.Read(); r.Read();
// now we are at the text element
r.ReadStartElement()
var content = SecurityElement.Escape(r.ReadContentAsString())
r.ReadEndElement()
I found two solutions. Both not very nice, but maybe you can tell me which has less drawbacks.
Both solutions rely on direcly using the ´XmlTextReader´ instead of ´XmlReader´. It comes with the property ´LinePosition' which lead me to the first solution and with the method ´ReadChars´ as basis for the second one.
Solution (1), get data from original string via indices
Problems:
doesn't work on stream inputs
doesn't work if xml has several lines
Code
string TXML = #"<xml><data></data><rawnode at=""10 4""><text>hallöle</text><z d=""2"">3</z></rawnode><data></data></xml>";
//XmlReader r = XmlReader.Create(new StringReader(TXML));
XmlTextReader r = new XmlTextReader(new StringReader(TXML));
// read to node which shall be retrived "raw"
while ( r.Read() )
{
if ( r.Name.Equals("rawnode") )
break;
}
// here we start
int Begin = r.LinePosition;
r.Skip();
int End = r.LinePosition;
// get it out
string output=TXML.Substring(Begin - 2, End - Begin);
Solution (2), get data with ´ReadChars´
Problems:
I have to parse and recreate the 'outer' markup of my tag which I'd like to read.
This might cost performance.
I might introduce errors.
Code:
// ... again create XmlTextReader and read to rawnode, then:
// here we start
int buflen = 15;
char[] buf = new char[buflen];
StringBuilder sb= new StringBuilder("<",20);
//get start tag and attributes
string tagname=r.Name;
sb.Append(tagname);
bool hasAttributes = r.MoveToFirstAttribute();
while (hasAttributes)
{
sb.Append(" " + r.Name + #"=""" + r.Value + #"""");
hasAttributes = r.MoveToNextAttribute();
}
sb.Append(#">");
r.MoveToContent();
//get raw inner data
int cnt;
while ((cnt = r.ReadChars(buf, 0, buflen)) > 0)
{
if ( cnt<buflen )
buf[cnt]=(char)0;
sb.Append(buf);
}
//append end tag
sb.Append("</" + tagname + ">");
// get it out
string output = sb.ToString();
Have a look on you xml header and verify that it contains something like this: <?xml version="1.0" encoding="ISO-8859-9"?>
For escaping and unescaping you could use the c# functions InnerXml and InnerText :
public static string XmlEscape(string unescaped)
{
XmlDocument doc = new XmlDocument();
var node = doc.CreateElement("root");
node.InnerText = unescaped;
return node.InnerXml;
}
public static string XmlUnescape(string escaped)
{
XmlDocument doc = new XmlDocument();
var node = doc.CreateElement("root");
node.InnerXml = escaped;
return node.InnerText;
}
I understand your desire to not have to parse and recreate the escaped characters, but I can't find a way not to unless you go full on customized with it. Perhaps this isn't so bad?
string TXML = #"<xml><text>hallöle</text></xml>";
TXML = TXML.Replace("&", "&");
XmlTextReader r = new XmlTextReader(new StringReader(TXML));
r.Read(); r.Read();
string o = r.ReadOuterXml();
o = o.Replace("&", "&");
Thank you very much for reading my question.
and this is my xml file. (for node Songs, many childNodes named Song)
<?xml version="1.0" encoding="utf-8" ?>
<xmlData>
<version>1.0</version>
<Songs>
<Song>
<artist>mic</artist>
<track>2</track>
<column>happy</column>
<date>14</date>
</Song>
<Song>
<artist>cool</artist>
<track>2</track>
<column>work</column>
<date>4</date>
</Song>
</Songs>
</xmlData>
reading xml, i use the following code:
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);
XmlNode versionNode = doc.SelectSingleNode(#"/xmlData/version");
Console.WriteLine(versionNode.Name + ":\t" + versionNode.InnerText);
XmlNode SongsNode = doc.SelectSingleNode(#"/xmlData/Songs");
Console.WriteLine(SongsNode.Name + "\n");
XmlDocument docSub = new XmlDocument();
docSub.LoadXml(SongsNode.OuterXml);
XmlNodeList SongList = docSub.SelectNodes(#"/Songs/Song");
if (SongList != null)
{
foreach (XmlNode SongNode in SongList)
{
XmlNode artistDetail = SongNode.SelectSingleNode("artist");
Console.WriteLine(artistDetail.Name + "\t: " + artistDetail.InnerText);
XmlNode trackDetail = SongNode.SelectSingleNode("track");
Console.WriteLine(trackDetail.Name + "\t: " + trackDetail.InnerText);
XmlNode columnDetail = SongNode.SelectSingleNode("column");
Console.WriteLine(columnDetail.Name + "\t: " + columnDetail.InnerText);
XmlNode dateDetail = SongNode.SelectSingleNode("date");
Console.WriteLine(dateDetail.Name + "\t: " + dateDetail.InnerText + "\n");
}
}
it seems working.
but how can i write the change to xml file?
maybe, i will change some childNode in Song, and may delete the whole chindNode by artist keyword.
is it possible such as this function
bool DeleteSongByArtist(string sArtist);
bool ChangeNodeInSong(string sArtist, string sNodeName, string value);
because the "Reading solution is "XmlDucoment", so it is better if "changing solution" by using "XmlDocument"
but, if you have better idea to read and change the xml file, please give me the sample code... and please don't write a name of solution such as "Ling to xml"...acutally, i do many testes, but failed.
Welcome to Stackoverflow!
You can change the nodes simply by setting a new .Value or in your case .InnerText.
Sample
// change the node
trackDetail.InnerText = "NewValue"
// save the document
doc.Save(xmlFilePath);
More Information
How To: Modify an Existing Xml File
MSDN - XmlDocument.Save Method
You need to use an XmlWriter. The easiest way to do it would be something like this...
using(XmlWriter writer = new XmlWriter(textWriter))
{
doc.WriteTo(writer);
}
Where textWriter is your initialized Text Writer.
Actually, forget that... the easiest way is to call...
doc.Save(xmlFilePath);
To delete an artist by artist name add the following method:
bool DeleteSongByArtist(XmlDocument doc, string artistName)
{
XmlNodeList SongList = doc.SelectNodes(#"/Songs/Song");
if (SongList != null)
{
for (int i = SongList.Count - 1; i >= 0; i--)
{
if (SongList[i]["artist"].InnerText == artistName && SongList[i].ParentNode != null)
{
SongList[i].ParentNode.RemoveChild(SongList[i]);
}
}
}
}
You probably want to clean it up a bit more to be more resilient. When you call it, change your initial code to be like this. Don't create the subDocument as you want to work with the entire XmlDocument.
XmlDocument doc = new XmlDocument();
doc.Load(xmlFilePath);
XmlNode versionNode = doc.SelectSingleNode(#"/xmlData/version");
Console.WriteLine(versionNode.Name + ":\t" + versionNode.InnerText);
XmlNode SongsNode = doc.SelectSingleNode(#"/xmlData/Songs");
Console.WriteLine(SongsNode.Name + "\n");
XmlNodeList SongList = doc.SelectNodes(#"/Songs/Song");
if (SongList != null)
{
foreach (XmlNode SongNode in SongList)
{
XmlNode artistDetail = SongNode.SelectSingleNode("artist");
Console.WriteLine(artistDetail.Name + "\t: " + artistDetail.InnerText);
XmlNode trackDetail = SongNode.SelectSingleNode("track");
Console.WriteLine(trackDetail.Name + "\t: " + trackDetail.InnerText);
XmlNode columnDetail = SongNode.SelectSingleNode("column");
Console.WriteLine(columnDetail.Name + "\t: " + columnDetail.InnerText);
XmlNode dateDetail = SongNode.SelectSingleNode("date");
Console.WriteLine(dateDetail.Name + "\t: " + dateDetail.InnerText + "\n");
}
}
You aren't able to save your changes because you made changes to an entirely new document!
You likely meant to do the following:
XmlNode SongsNode = doc.SelectSingleNode(#"/xmlData/Songs");
Console.WriteLine(SongsNode.Name + "\n");
// Don't make a new XmlDocument here! Use your existing one
XmlNodeList SongList = SongsNode.SelectNodes(#"/Song");
At this point SongList is still living inside doc. Now when you call:
doc.Save(xmlFilePath);
Your changes will be saved as you intended.
If you're looking to delete nodes that match certain criteria:
// Use XPath to find the matching node
XmlNode song = SongsNode.SelectSingleNode(#"/Song[artist='" + artist + "']");
// Remove it from its Parent
SongsNode.RemoveChild(song);
If you're looking to add a new node:
// Create the new nodes using doc
XmlNode newSong = doc.CreateElement("Song");
XmlNode artist = doc.CreateElement("artist");
artist.InnerText = "Hello";
// Begin the painstaking process of creation/appending
newSong.AppendChild(artist);
// rinse...repeat...
// Finally add the new song to the SongsNode
SongsNode.AppendChild(newSong);
You could do
XmlNodeList SongList = doc.SelectNodes(#"//Songs/Song");
The // tells it to select the Songs node anywhere in document. This is better than
doc.SelectNodes(#"/document/level1/music/Songs")
Note that the above statement is oviously not for your xml, but to prove a point about //
Using // removes the need for your docSub document and SongsNode element.
To add then delete a song, just use the following
XmlDocument doc = new XmlDocument();
XmlElement ea = doc.SelectSingleNode("//songs");
XmlElement el = doc.CreateElement("song");
XmlElement er;
ea.AppendChild(el);
//doing my work with ea
//you could use innerxml.
el.InnerXml = "<artist>Judas Priest</artist><track>7</track><column>good</column><date>1</date>";
//or you can treat each node as above
er = doc.CreateElement("Name");
el.AppendChild(er);
er.InnerText = "The Ripper";
//but you don't nead this song any more?
ea.RemoveChild(el);
//so it's gone.
And thats all there is to it.
please don't write a name of solution such as "Ling to xml"...acutally, i do many testes, but failed.
Still I think, this is a very good time to start to use Linq2Xml. If you don't like it, just ignore.
XDocument xDoc = XDocument.Load(new StringReader(xml));
//Load Songs
var songs = xDoc.Descendants("Song")
.Select(s => new
{
Artist = s.Element("artist").Value,
Track = s.Element("track").Value,
Column = s.Element("column").Value,
Date = s.Element("date").Value,
})
.ToArray();
//Delete Songs
string songByArtist="mic";
xDoc.Descendants("Song")
.Where(s => s.Element("artist").Value == songByArtist)
.Remove();
string newXml = xDoc.ToString();