C# reading XML , how to get the value - c#

I am new to XML. Need some help.
I can get pro NAME fine but
How do I get the value of scode? JDK...blah
<pro NAME="JK1233k">
<scode ID="A">JDK-ORPLL-PDILL</scode>
</pro>
XmlReader reader = XmlReader.Create("file.xml");
while (reader.Read())
{
if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "pro"))
{
Console.WriteLine(reader["NAME"]);
}
else if((reader.NodeType == XmlNodeType.Element) && (reader.Name == "scode"))
{
Console.WriteLine(reader["ID"]);
//what do I put here to get the value????
}
}
reader.Close();

What you're looking for is:
Console.WriteLine(reader.ReadInnerXml());
I personally prefer LINQ to XML. If you haven't looked into it, you should. You can achieve the same thing in a cleaner manner.
at the start of your c# file put the following:
using System.Linq;
using System.Xml.Linq; // loads the linq to xml part
Most XML files are much bigger than just one element. So for that, your code would be something like this:
// Load XML file as an IEnumerable. This allows you to query it.
var xmlDoc = XDocument.Load(file)
.Elements("pro")
.Select(pro => new
{
Name = pro.Attribute("NAME").Value,
Scode = pro.Elements("scode").Select(scode => new
{
ID = scode.Attribute("ID").Value,
Val = scode.Value
})
});
// loop through each <pro> element
foreach (var pro in xmlDoc)
{
// Get Pro Name
Console.WriteLine(pro.Name);
// loop through each <scode> element inside <pro>
foreach(var scode in pro.Scode)
{
// Get Scode ID:
Console.WriteLine(scode.ID);
// Get Scode Value:
Console.WriteLine(scode.Val);
}
}
If your XML is only a SINGLE element, you can do this:
// Load XML file:
var pro = XElement.Load("file.xml");
// Get Pro Name
pro.Attribute("NAME").Value;
// Get Scode ID:
pro.Element("scode").Attribute("ID").Value;
// Get Scode Value:
pro.Element("scode").Value;

Consider the following code snippet...
XDocument doc = XDocument.Load("file.xml");
foreach (XElement element in doc.Descendants("pro"))
{
Console.WriteLine(element.Attribute("NAME").Value);
}
foreach (XElement element in doc.Descendants("scode"))
{
Console.WriteLine(element.Value);
}
Good Luck!

Related

how to search for a xml node value, then create new attribute for that element in c#

I am using c# .net 4.6 xpath to search for node with id value and when found create a new attribute for the parent element in place. I have a list of such id values that I need to iterate over and create attributes to produce a new xml document. I have attempted the following but does not work. The XPathNavigator.MoveTo method appears to replace the source navigator with the moved to element thereby loosing all other content. Is this not the right way to achieve this ? Could you please advice ?
See code snippet below:
publicationDoc.LoadXml(publicationXPathNav.OuterXml);
XPathNavigator publicationNav = publicationDoc.CreateNavigator();
foreach (IListBlobItem item in contentDirectory.ListBlobs())
{
var blob = (CloudBlob)item;
string contentId = blob.Name;
XPathNavigator contentRefNav = publicationNav.SelectSingleNode($#"//releaseItem/contentRef[id = {"'" + contentId + "'"}]/..");
if (contentRefNav != null)
{
publicationNav.MoveTo(contentRefNav); // here publicationNav gets replaced by contentRefNav
publicationNav.CreateAttribute("", "fileName", "", contentFileName);
}
}
// once finished with the foreach I was hoping to be able to save the publicationNav.OuterXml to a new file with the newly added attributes.
Here is a small cut down sample source xml data :
<publicationsRoot>
<publication>
<urn>iso:pub:std:FDIS:74824</urn>
<releaseItems>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92764155</id>
</contentRef>
</releaseItem>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92802320</id>
</contentRef>
</releaseItem>
<releaseItem>
<languageNeutral>false</languageNeutral>
<type>STANDARD</type>
<contentRef>
<id>92801989</id>
</contentRef>
</releaseItem>
<releaseItems>
</publication>
</publicationsRoot>
Try xml linq with a dictionary
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication167
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, XElement> dict = doc.Descendants("id")
.GroupBy(x => (string)x, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
string id = "92764155";
string filename = "filename";
if (dict.ContainsKey(id))
{
dict[id].SetAttributeValue("filename", filename);
}
}
}
}
I managed to resolve this by not using XPathNavigator and relying only on XMLDocuent. It appears that XPathNavigator is more suitable for relative paths whereas my requirement was to search specific nodes and update the xml document in place.
publicationDoc.LoadXml(publicationXPathNav.OuterXml);
foreach (IListBlobItem item in contentDirectory.ListBlobs())
{
var blob = (CloudBlob)item;
string contentId = blob.Name;
XmlNode contentRefNode = publicationDoc.SelectSingleNode($#"//releaseItem/contentRef[id = {"'" + contentId + "'"}]/..");
if (contentRefNode != null)
{
XmlAttribute fileName = publicationDoc.CreateAttribute("fileName");
fileName.Value = contentFileName + contentFileExt;
contentRefNode.Attributes.SetNamedItem(fileName);
}
}
// once finished with the foreach I was hoping to be able to save the publicationNav.OuterXml to a new file with the newly added attributes.
Thanks for all the answers. I will certainly take those on board.

Change XML node with same name?

everyone!
I have an XML file and need to change the value of a node, specifically the indicated line. The problem i have is that as you can see, there are many nodes.
How can i change this line? This XML file could be much larger, so i am looking for a solution that would take different amounts of 'launch.file' nodes into account.
The node that will need to be set to True will be identified by the corresponding NAME tag. So if i typed in ULTII, the DISABLED node for that block will be set to True. If i typed in Catl, then the DISABLED node for that block would be changed.
<?xml version="1.0" encoding="windows-1252"?>
<SBase.Doc Type="Launch" version="1,0">
<Descr>Launch</Descr>
<Filename>run.xml</Filename>
<Disabled>False</Disabled>
<Launch.ManualLoad>False</Launch.ManualLoad>
<Launch.File>
<Name>Catl</Name>
<Disabled>False</Disabled>
<ManualLoad>False</ManualLoad>
<Path>ft\catl\catl.exe</Path>
</Launch.File>
<Launch.File>
<Disabled>False</Disabled> <!-- change to True -->
<ManualLoad>False</ManualLoad>
<Name>ULTII</Name>
<Path>F:\ULTII.exe</Path>
<NewConsole>True</NewConsole>
</Launch.File>
<Launch.File>
<Name>ECA</Name>
<Disabled>False</Disabled>
<Path>C:\ECA.exe</Path>
</Launch.File>
</SBase.Doc>
I am using Visual Studio 2012, should you need to know.
Thank you to anyone who can help me out on this, i really appreciate it.
Heres my method to do what you want
private void DisableLaunchFile(string xmlfile, string launchFileName){
XDocument doc = XDocument.Load(xmlfile);
var launchFileElement = doc.Descendants("Launch.File").Where (d => d.Element("Name").Value == lauchFileName);
launchFileElement.Elements("Disabled").First().Value = true.ToString();
doc.Save(xmlfile);
}
Use it like:
string pathToXmlFile = //assign ;
DisableLaunchFile(pathToXmlFile, "Catl");
DisableLaunchFile(pathToXmlFile, "ULTII");
This can be achieved by using LINQ to XML (see XDocument Class).
Assuming that there is the single Launch.File element with Name element with value "ULTII":
var document = XDocument.Load(...);
var ultiiElement = document
.Descendants("Launch.File")
.Single(fileElement => fileElement.Element("Name").Value == "ULTII");
ultiiElement.Element("Disabled").Value = "True"; // or true.ToString()
document.Save(...);
This method will do the trick:
public void ChangeNode(string name, string filePath)
{
XDocument xDocument;
using (var streamReader = new StreamReader(filePath))
{
xDocument = XDocument.Parse(streamReader.ReadToEnd());
}
var nodes = xDocument.Descendants("Launch.File");
foreach (var node in nodes)
{
var nameNode = node.Descendants("Name").FirstOrDefault();
if (nameNode != null && nameNode.Value == name)
{
var disabledNode = node.Descendants("Disabled").FirstOrDefault();
if (disabledNode != null)
{
disabledNode.SetValue("True");
}
}
}
using (var streamWriter = new StreamWriter(filePath))
{
xDocument.Save(streamWriter);
}
}
The name you want to pass in is the name of the node that you want to change and the path is the file path to the xml file. So you might call it like:
ChangeNode("ULTII", "C:\\output.xml");
You may need to tidy this up a bit like matching the node name invariant of case or culture but it should get you started.

Converting XML nodes into attributes using C#

I have an XML string that is loaded into an XMLDocument, similar to the one listed below:
<note>
<to>You</to>
<from>Me</from>
<heading>TEST</heading>
<body>This is a test.</body>
</note>
I would like to convert the text nodes to attributes (using C#), so it looks like this:
<note to="You" from="Me" heading="TEST" body="This is a test." />
Any info would be greatly appreciated.
Linq to XML is great for this kind of stuff. You could probably achieve it in one line if you'd want to. Just grab the child node names and their respective value and add all those 'key value pairs' as attributes instead.
MSDN docs here: http://msdn.microsoft.com/en-us/library/bb387098.aspx
Like seldon suggests, LINQ to XML would be a much better fit for this sort of task.
But here's a way to do it with XmlDocument. Not claiming this is fool-proof (haven't tested it), but it does seem to work for your sample.
XmlDocument input = ...
// Create output document.
XmlDocument output = new XmlDocument();
// Create output root element: <note>...</note>
var root = output.CreateElement(input.DocumentElement.Name);
// Append attributes to the output root element
// from elements of the input document.
foreach (var child in input.DocumentElement.ChildNodes.OfType<XmlElement>())
{
var attribute = output.CreateAttribute(child.Name); // to
attribute.Value = child.InnerXml; // to = "You"
root.Attributes.Append(attribute); // <note to = "You">
}
// Make <note> the root element of the output document.
output.AppendChild(root);
Following converts any simple XML leaf node into an attribute of its parent. It is implemented as a unit test. Encapsulate your XML content into an input.xml file, and check it saved as output.xml.
using System;
using System.Xml;
using System.Linq;
using NUnit.Framework;
[TestFixture]
public class XmlConvert
{
[TestCase("input.xml", "output.xml")]
public void LeafsToAttributes(string inputxml, string outputxml)
{
var doc = new XmlDocument();
doc.Load(inputxml);
ParseLeafs(doc.DocumentElement);
doc.Save(outputxml);
}
private void ParseLeafs(XmlNode parent)
{
var children = parent.ChildNodes.Cast<XmlNode>().ToArray();
foreach (XmlNode child in children)
if (child.NodeType == XmlNodeType.Element
&& child.Attributes.Count == 0
&& child.ChildNodes.Count == 1
&& child.ChildNodes[0].NodeType == XmlNodeType.Text
&& parent.Attributes[child.Name] == null)
{
AddAttribute(parent, child.Name, child.InnerXml);
parent.RemoveChild(child);
}
else ParseLeafs(child);
// show no closing tag, if not necessary
if (parent.NodeType == XmlNodeType.Element
&& parent.ChildNodes.Count == 0)
(parent as XmlElement).IsEmpty = true;
}
private XmlAttribute AddAttribute(XmlNode node, string name, string value)
{
var attr = node.OwnerDocument.CreateAttribute(name);
attr.Value = value;
node.Attributes.Append(attr);
return attr;
}
}

How to load and merge a dataset of XML docs

I would like to consume a dataset of XML documents, and merge them into a single document containing only distinct elements.
To illustrate, I have a dataset as:
r, x
-- -------------------------------
1, <root><a>111</a></root>
2, <root><a>222</a><b>222</b></root>
3, <root><c>333</c></root>
would result in:
<a>111</a><b>222</b><c>333</c>
The <a> element from r=2 is not merged since we already have an element = <a> from r=1. I need only merge new elements, starting with r=1 going forward.
I am able to iterate over the list, but having difficulty comparing and merging. The code below fails to identify <a>222</a> as a duplicate. Is it possibly comparing the element values as well?
using (SqlDataReader dsReader = cmd.ExecuteReader())
{
XDocument baseDoc = new XDocument();
XDocument childDoc = new XDocument();
while (dsReader.Read())
{
// this is the base doc, merge forward from here
if (dsReader["r"].ToString() == "1")
{
baseDoc = XDocument.Parse(dsReader["x"].ToString());
SqlContext.Pipe.Send("start:" + baseDoc.ToString());
}
// this is a child doc, do merge operation
else
{
childDoc = XDocument.Parse(dsReader["x"].ToString());
// find elements only present in child
var childOnly = (childDoc.Descendants("root").Elements()).Except(baseDoc.Descendants("root").Elements());
foreach (var e in childOnly)
{
baseDoc.Root.Add(e);
}
}
}
}
I am bit confused about baseDoc and childDoc usage in your code. I hope I correctly understood your question. Here is my proposal:
using (SqlDataReader dsReader = cmd.ExecuteReader())
{
XElement result = new XElement("root");
while (dsReader.Read())
{
// Read source
XDocument srcDoc = XDocument.Parse(dsReader["x"].ToString());
// Construct result element
foreach (XElement baseElement in srcDoc.Descendants("root").Elements())
if (result.Element(baseElement.Name) == null) // skip already added nodes
result.Add(new XElement(baseElement.Name, baseElement.Value));
}
// Construct result string from sub-elements (to avoid "<root>..</root>" in output)
string str = "";
foreach (XElement element in result.Elements())
str += element.ToString();
// send the result
SqlContext.Pipe.Send("start:" + str);
}
Note that my code ignores r-numbering. I use order as it comes from sql data reader. If rows are not sorted by "r", then additional sort is required before my code.

How to select XML node by attribute and use it's child nodes data?

Here is my XML file
<?xml version="1.0" encoding="utf-8" ?>
<storage>
<Save Name ="Lifeline">
<Seconds>12</Seconds>
<Minutes>24</Minutes>
<Hours>9</Hours>
<Days>25</Days>
<Months>8</Months>
<Years>2010</Years>
<Health>90</Health>
<Mood>100</Mood>
</Save>
<Save Name ="Hellcode">
<Seconds>24</Seconds>
<Minutes>48</Minutes>
<Hours>18</Hours>
<Days>15</Days>
<Months>4</Months>
<Years>1995</Years>
<Health>50</Health>
<Mood>50</Mood>
</Save>
Here is a code which get's data from XML and loads it into application.
System.IO.StreamReader sr = new System.IO.StreamReader(#"Saves.xml");
System.Xml.XmlTextReader xr = new System.Xml.XmlTextReader(sr);
System.Xml.XmlDocument save = new System.Xml.XmlDocument();
save.Load(xr);
XmlNodeList saveItems = save.SelectNodes("Storage/Save");
XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");
sec = Int32.Parse(seconds.InnerText);
XmlNode minutes = saveItems.Item(0).SelectSingleNode("Minutes");
min = Int32.Parse(minutes.InnerText);
XmlNode hours = saveItems.Item(0).SelectSingleNode("Hours");
hour = Int32.Parse(hours.InnerText);
XmlNode days = saveItems.Item(0).SelectSingleNode("Days");
day = Int32.Parse(days.InnerText);
XmlNode months = saveItems.Item(0).SelectSingleNode("Months");
month = Int32.Parse(months.InnerText);
XmlNode years = saveItems.Item(0).SelectSingleNode("Years");
year = Int32.Parse(years.InnerText);
XmlNode health_ = saveItems.Item(0).SelectSingleNode("Health");
health = Int32.Parse(health_.InnerText);
XmlNode mood_ = saveItems.Item(0).SelectSingleNode("Mood");
mood = Int32.Parse(mood_.InnerText);
The problem is that this code loads data inly from "Lifeline" node. I would like to use a listbox and be able to choose from which node to load data.
I've tried to take string from listbox item content and then use such a line
XmlNodeList saveItems = save.SelectNodes(string.Format("storage/Save[#Name = '{0}']", name));
variable "name" is a string from listboxe's item. While compiled this code gives exception.
Do somebody knows a way how to select by attribute and load nedeed data from that XML?
If you can use XElement:
XElement xml = XElement.Load(file);
XElement storage = xml.Element("storage");
XElement save = storage.Elements("Save").FirstOrDefault(e => ((string)e.Attribute("Name")) == nameWeWant);
if(null != save)
{
// do something with it
}
Personally I like classes that have properties that convert to and from the XElement to hide that detail from the main program. IE say the Save class takes an XElement node in the constructor, saves it internally globally, and the properties read/write to it.
Example class:
public class MyClass
{
XElement self;
public MyClass(XElement self)
{
this.self = self;
}
public string Name
{
get { return (string)(self.Attribute("Name") ?? "some default value/null"); }
set
{
XAttribute x = source.Attribute("Name");
if(null == x)
source.Add(new XAttribute("Name", value));
else
x.ReplaceWith(new XAttribute("Name", value));
}
}
}
Then you can change the search to something like:
XElement save = storage.Elements("Save")
.FirstOrDefault(e => new MyClass(e).Name == NameWeWant);
Since it is not that much data, I'd suggest loading all information to a list of saves(constructor) and then drawing from there which one the user would like to use...
As for things not working, I personally use a lower level approach to get my data and it is not error prone. Remodeling it to fit your problem a bit:
int saves = 0;
List<Saves> saveGames = new List<Saves>();
saveGames.Add(new Saves());
while (textReader.Read())
{
if (textReader.NodeType == XmlNodeType.Element)
whatsNext = textReader.Name;
else if (textReader.NodeType == XmlNodeType.Text)
{
if (whatsNext == "name")
saveGames[saves].name = Convert.ToString(textReader.Value);
//else if statements for the rest of your attributes
else if (whatsNext == "Save")
{
saveGames.Add(new Saves());
saves++;
}
}
else if (textReader.NodeType == XmlNodeType.EndElement)
whatsNext = "";
}
Basically throw everything in the xml file into a list of objects and manipulate that list to fill the listbox. Instead of having Saves name = "...", have a name attribute as the first attribute in the save.
Code tags hate me. Why they break so easily ( ._.)
The select nodes is returning two XmlNode objects.
XmlNodeList saveItems = save.SelectNodes("Storage/Save");
Later in your code you seem to be selecting the first one and with saveItems.Item(0) and getting values from it which in this case would be the save node with the Name="LifeLine". So if you were to do saveItems.Item(1) and select nodes and its values then you would get the other set of nodes.

Categories