Background
This is my small XML file I made online.
<?xml version="1.0"?>
<movement>
<skill id = "2">
<cooldown>5</cooldown>
</skill>
<skill id = "3">
<cooldown>10</cooldown>
</skill>
</movement>
This is some code I have so far to try to parse it.
string dataPath = Application.dataPath + "/Resources/XML/Skills/";
DirectoryInfo xmlFolder = new DirectoryInfo (dataPath);
FileInfo[] files = xmlFolder.GetFiles ("*.xml");
// Loops through each XML file
foreach (FileInfo file in files) {
XmlDocument xdoc = new XmlDocument ();
xdoc.Load (file.ToString ());
XmlNodeList nodes = xdoc.DocumentElement.SelectNodes ("/movement");
foreach (XmlNode node in nodes) { // Movement Layer
foreach (XmlNode skillNode in node.ChildNodes) {
print (skillNode.Value);
}
}
}
Problem
I am able to access the 5 and 10 values for cooldown, but cannot get the "id" value of the skill. The reason I'm trying to do this is to read the skill IDs into my game and store the information. I pretty much exhausted almost all the methods denoting from XmlNode, such as value and name, but it only returns "skill", and not the value of skill, such as 2 or 3. I feel like I'm missing something really simple here, but I'm having difficulty finding the correct terminology or phrasing for this issue.
LINQ To XML would make this parsing simpler...
static void Main(string[] args)
{
var doc =
#"<?xml version=""1.0""?>
<movement>
<skill id = ""2"" >
<cooldown> 5 </cooldown>
</skill>
<skill id = ""3"" >
<cooldown> 10 </cooldown>
</skill>
</movement> ";
var root = XDocument.Parse(doc);
foreach (var skill in root.Descendants("skill"))
{
Console.WriteLine("Skill: {0} \t CoolDOwn: {1}",
(int)skill.Attribute("id"),
skill.Element("cooldown").Value);
}
Console.ReadLine();
}
Using xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
var results = doc.Descendants("skill").Select(x => new {
id = (int)x.Attribute("id"),
coolDown = (int)x.Element("cooldown")
}).ToList();
}
}
}
I would not use XMLDocument for querying purposes IMHO. I find XDocument to be an easier extension off of System.Xml.Linq;
EG:
using System;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleTester
{
class Program
{
static void Main(string[] args)
{
//Here I am mocking your file statically, you could iterate here through a file system.
var xml =
"<?xml version=\"1.0\"?><movement><skill id = \"2\"><cooldown>5</cooldown></skill><skill id = \"3\"><cooldown>10</cooldown></skill></movement>";
//Just parse the file from the text obtained from a StreamReader or similar.
var xdoc = XDocument.Parse(xml);
//Chain the events of finding the node(s) you want with 'Elements' then continuing on to more, then you want an Attribute and not a node. Then select it's value.
xdoc.Elements("movement").Elements("skill").Attributes("id")
.Select(x => x.Value)
.ToList()
.ForEach(x => Console.WriteLine(x));
Console.ReadLine();
}
}
}
Related
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.
I am parsing below XML. I need value 'x2#email.com'.I am able to get the list of nodes successfully, but the problem is that with each iteration I am still getting 'x1#email.com' from the first group of 'Info' element.
XML:
<ClaimAdminContact xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Services.Models">
<claimAdminID>T1</claimAdminID>
<contactInfo>
<Info>
<desc>Level 1 Notifications</desc>
<emailAddress>x1#email.com</emailAddress>
<orgNum>1234</orgNum>
<type>T2</type>
</Info>
<Info>
<desc>Level 2 Notifications</desc>
<emailAddress>x2#email.com</emailAddress>
<orgNum i:nil="true"/>
<type>T2</type>
</Info>
<Info>
<desc>Level 3 Notifications</desc>
<emailAddress>x3#email.com</emailAddress>
<orgNum i:nil="true"/>
<type>T2</type>
</Info>
</contactInfo>
</ClaimAdminContact>
I have tried full xpath but still not able to get the next set of values. Below is the code that I am using to parse xml.
Code:
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlEmail.NameTable);
nsmgr.AddNamespace("MsBuild", xmlns);
var contactInfo = xmlEmail.SelectNodes("/MsBuild:ClaimAdminContact/MsBuild:contactInfo/*", nsmgr);
foreach (XmlNode item in contactInfo)
{
_notificationDesc = item.SelectSingleNode("//MsBuild:desc", nsmgr).InnerText;
_reviewEmail = item.SelectSingleNode("//MsBuild:emailAddress", nsmgr).InnerText;
_orgNum = item.SelectSingleNode("//MsBuild:orgNum", nsmgr).InnerText;
}
Please
Use xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XNamespace ns = doc.Root.GetDefaultNamespace();
var results = doc.Descendants(ns + "Info").Select(x => new
{
desc = (string)x.Element(ns + "desc"),
email = (string)x.Element(ns + "emailAddress"),
orgNum = (string)x.Element(ns + "orgNum"),
type = (string)x.Element(ns + "type")
}).ToList();
}
}
}
Using System.Xml.Linq:
var xmlFile = #"myxml.xml";
var xDoc = XDocument.Load(xmlFile);
var infos = xDoc.Descendants("Info");
foreach (var info in infos)
{
var email = info.Element("emailAddress").Value;
}
EDIT: How to work with namespaces
If you have multiple namespaces and want to work with that, then you must specify the namespaces like below. Removing namespaces from the file is hardly a good idea.
var xmlFile = #"C:\Users\gurudeniyas\Desktop\myxml.xml";
XNamespace ns = "http://schemas.datacontract.org/2004/07/Services.Models";
XNamespace nsi = "http://www.w3.org/2001/XMLSchema-instance";
var xDoc = XDocument.Load(xmlFile);
var infos = xDoc.Descendants(ns + "Info");
foreach (var info in infos)
{
var email = info.Descendants(ns + "emailAddress").FirstOrDefault().Value;
Console.WriteLine(email);
}
The main challenge I was facing in parsing xml was the namespace attribute in the root element. It was preventing my code to parse the usual way and that was the reason why I tried using 'XmlNamespaceManager'. I decided to remove the namespace from the xml.
I used below recursive method to remove namespace from xml and everything worked!! I am not sure if this is the optimal way, but I was able to achieve what I wanted.
public XElement RemoveAllNamespaces(XElement root)
{
return new XElement(
root.Name.LocalName,
root.HasElements ?
root.Elements().Select(x => RemoveAllNamespaces(x)) :
(object)root.Value
);
}
Calling Code:
XElement noNsDoc = RemoveAllNamespaces(XElement.Parse(xmlString));
var xDoc = XDocument.Parse(noNsDoc.ToString());
I have xml stored in string variable. from that xml I need to filter data based on StandardValue. I want to extract only those records whose StandardValue is not null & not empty. I tried but my code did not work.
string xmldoc= #"<?xml version=""1.0"" encoding=""utf-8""?>
<TickerBrokerStandardDateLineitem>
<Ticker />
<TickerID />
<TickerBrokerStandardDateLineitemValues>
<TickerBrokerStandardDateLineitemValue>
<TabName>Consensus Model</TabName>
<StandardDate>1Q 2010</StandardDate>
<BRTab>Income Statement</BRTab>
<BRLineItem>NET REVENUES</BRLineItem>
<Action>Extracted</Action>
<StandardLineItem>Net Revenue</StandardLineItem>
<StandardValue>329.623</StandardValue>
</TickerBrokerStandardDateLineitemValue>
<TickerBrokerStandardDateLineitemValue>
<TabName>Consensus Model</TabName>
<StandardDate>2Q 2010</StandardDate>
<BRTab>Income Statement</BRTab>
<BRLineItem>NET REVENUES</BRLineItem>
<Action>Extracted</Action>
<StandardLineItem>Net Revenue</StandardLineItem>
<StandardValue></StandardValue>
</TickerBrokerStandardDateLineitemValue>
<TickerBrokerStandardDateLineitemValue>
<TabName>Consensus Model</TabName>
<StandardDate>2Q 2010</StandardDate>
<BRTab>Income Statement</BRTab>
<BRLineItem>NET REVENUES</BRLineItem>
<Action>Extracted</Action>
<StandardLineItem>Net Revenue</StandardLineItem>
<StandardValue/>
</TickerBrokerStandardDateLineitemValue>
</TickerBrokerStandardDateLineitemValues>
</TickerBrokerStandardDateLineitem>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmldoc);
XmlNodeList nodeList = doc.GetElementsByTagName("TickerBrokerStandardDateLineitemValue");
List<string> list = new List<string>();
foreach (XmlNode item in nodeList)
{
foreach (XmlElement i in item)
{
if (i.Name == "StandardValue")
{
if (i.InnerText == string.Empty)
{
list.Add(item.OuterXml);
}
}
}
}
string a = string.Empty;
foreach (var item in list)
{
a = doc.InnerXml.Replace(item, "");
}
string str1 = doc.OuterXml;
My above code does not work. basically how to filter with xpath that return only those records whose StandardValue is not null & not empty.
How to achieve it with XmlDocument class instead of xdocument.
At end I have to stored filtered record's xml into string. I know XmlDocument class has outer xml property which return full xml.
Give me sample code which will return filter records & stored filter records xml into string.
Use xml linq which is the newer version of the Net xml library :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
XDocument doc = XDocument.Parse(xml);
List<XElement> tickerBrokerStandardDateLineitemValues = doc.Descendants("TickerBrokerStandardDateLineitemValue")
.Where(x => (x.Element("StandardValue") != null) && ((string)x.Element("StandardValue") != string.Empty))
.ToList();
}
}
}
Assuming the input string your provided in the question.
This will select all TickerBrokerStandardDateLineitemValue values that has StandardValue element and it's not empty or white space (normalize-space).
normalize-space:
strips leading and trailing white-space from a string, replaces
sequences of whitespace characters by a single space, and returns the
resulting string.
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(xmlStr); // <input from the question>
var nodes = xmlDoc.SelectNodes("//TickerBrokerStandardDateLineitemValue[StandardValue and string-length(normalize-space(StandardValue))]");
I'm trying to get the 3rd level names from a XML.
I found this but it gives me also the 4th level, which i don't want.
How should i do it?
XDocument xdoc = XDocument.Load(path + #"\Pages\Results\Target_XML.xml");
foreach (var name in xdoc.Root.Element("Veg").DescendantNodesAndSelf().OfType<XElement>().Select(x => x.Name).Distinct())
{
Console.WriteLine(name);
}
Example (I want just the Tom and Car as strings, without Name and Cal) -
This is the XML:
<DEV>
<Veg>
<Tom>
<Name>aa</Name>
<Cal>99</Cal>
</Tom>
<Car>
<Name>aa</Name>
<Cal>99</Cal>
</Car>
</Veg>
<Fru>
<Ban>
<Name>aa</Name>
<Cal>99</Cal>
</Ban>
</Fru>
</DEV>
Using xml linq :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Runtime.InteropServices;
namespace ConsoleApplication23
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<string> strings = doc.Elements().Elements().Elements().Select(x => x.Name.LocalName).ToList();
}
}
}
You can reference the child nodes with XElement's ChildNodes property. Like this:
XmlNodeList childNodes = xdoc.Root.Element("Veg").ChildNodes;
In this case, the childNodes list would contain the 3rd level nodes you want.
var l_RootElement = XElement.Load(path + #"\Pages\Results\Target_XML.xml");
foreach (var l_VegElement in l_RootElement.Elements("Veg").Elements()) {
Console.WriteLine(l_VegElement.Name);
}
I have an xml file called portfolio that I am passing the location of as a string.
Read a list of filenames from the portfolio file, under the element. In the xml file I have an element called that I need to read the 4 values in the price data and store it to a List of strings. I don't know if I am doing this correctly. I don't know what my parameters should be for the foreach loop.
XML file:
<priceData>
<file name="ibmx.xml"/>
<file name="msft.xml"/>
<file name="ulti.xml"/>
<file name="goog.xml"/>
</priceData>
Here is my function for C#
public static void readPortfolio(string filename)
{
XmlTextReader reader = new XmlTextReader(filename);
reader.Read();
List<string> priceDataFile = new List <string> ();
foreach(var file in reader) //Don't know what the parameters should be.
{
priceDataFile.Add(reader.Value); //Not sure if I am passing what I want
}
}
Using XDocument Class is a good way to solve it.But if you want to use XmlTextReader Class, the code has been listed as follow. Then you will get the result which contains a XML file list. On the onther way, name is an attribute in your example code. So you should use reader.GetAttribute("name") to get value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace XmlReaderTest
{
class Program
{
static void Main(string[] args)
{
XmlTextReader reader = new XmlTextReader("../../Portfolio.xml");
reader.WhitespaceHandling = WhitespaceHandling.None;
List<string> priceDataFile = new List<string>();
while (reader.Read())
{
if (reader.Name == "file")
{
priceDataFile.Add(reader.GetAttribute("name"));
}
else
continue;
}
reader.Close();
foreach (string file in priceDataFile)
{
Console.WriteLine(file);
}
Console.ReadLine();
}
}
}
Use LINQ to XML instead (.NET 3.0+):
XDocument doc = XDocument.Load(path);
List<string> list = doc.Root
.Elements("file")
.Select(f => (string)f.Atribute("name"))
.ToList();
You can do this . the following will add the filename of each attribute to the list
replace where I have declared a copy of your .XML file with your path location.
XDocument document = XDocument.Load(#"C:\Sample_Xml\PriceData.xml");
List<string> priceDataFile = new List<string>();
var priceData = (from pd in document.Descendants("priceData")
select pd);
foreach (XElement priceValue in priceData.Elements())
{
priceDataFile.Add(priceValue.FirstAttribute.Value.ToString());
}
this is what your priseDataFile List Contents will look like
viewing it in the QuickWatch
[0] "ibmx.xml"
[1] "msft.xml"
[2] "ulti.xml"
[3] "goog.xml"