I have an XML file as something similar to what follows:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
<PropertyGroup>
<Property1>DummyProperty</Property1>
</PropertyGroup>
<Reference>
<HintPath>C:\$(Property1)\DummyFile.txt</HintPath>
</Reference>
</Project>
Now when I try to parse this XML in C# (using XElement and XDocument), I need to expand $(Property1) so that the final string that I get for this path is:
C:\DummyProperty\DummyFile.txt
but I am unable to achieve this and continue to get
C:\$(Property1)\DummyFile.txt
What is the right way? Is there a different library I should use?
Thank you!
try following :
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);
XElement project = doc.Root;
XNamespace ns = project.GetDefaultNamespace();
string property1 = (string)project.Descendants(ns + "Property1").FirstOrDefault();
string hintPath = (string)project.Descendants(ns + "HintPath").FirstOrDefault();
}
}
}
You should do replacing of $(Property1) to DummyProperty by yourself.
var doc = XDocument.Load(pathToTheProjectFile);
var ns = doc.Root.GetDefaultNamespace();
var properties = doc.Descendants(ns + "PropertyGroup")
.SelectMany(property => property.Elements())
.Select(element => new
{
Old = $"$({element.Name.LocalName})",
New = element.Value
});
var hintPathCollection =
doc.Descendants(ns + "HintPath")
.Select(element => properties.Aggregate(element.Value,
(path, values) => path.Replace(values.Old, values.New)));
Related
My program could be given two different kinds of xml files. The only way to tell the difference is by seeing what device it came from. How would I get the device name from this xml document?
<?xml version="1.0" encoding="UTF-8"?>
<DataFileSetup>
<System Name="Local">
<SysInfo>
<Devices>
<RealMeasurement>
<Hardware></Hardware>
<Device Type="MultiDevice">
<DriverBuffSizeInSec>5</DriverBuffSizeInSec>
<Card Index="0">
<DeviceName>SIRIUSi</DeviceName>
<DeviceSerialNumber>D017F09216</DeviceSerialNumber>
<FirmwareVersion>7.3.45.75</FirmwareVersion>
<VCXOValue>8802</VCXOValue>
</Card>
</Device>
</RealMeasurement>
</Devices>
</SysInfo>
</System>
</DataFileSetup>
The simple
var deviceType = xdoc.Element("DeviceName").Value;
either errors because there is nothing there or if i delete .Value it is just null.
Is there a simple way to get this value?
Please try the following.
c#
void Main()
{
const string fileName = #"e:\temp\device.xml";
XDocument xdoc = XDocument.Load(fileName);
Console.WriteLine(xdoc.Descendants("DeviceName").FirstOrDefault()?.Value);
}
Output
SIRIUSi
I like using a Dictionary in this case :
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);
Dictionary<string, XElement> dict = doc.Descendants("Device")
.GroupBy(x => (string)x.Descendants("DeviceName").FirstOrDefault(), y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
I am trying to parse the XML from this url, sampled below, in C#:
<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time='2020-01-16'>
<Cube currency='USD' rate='1.1169'/>
<Cube currency='JPY' rate='122.80'/>
<Cube currency='BGN' rate='1.9558'/>
</Cube>
</Cube>
</gesmes:Envelope>
This is the code I am using to get currencies:
xml.Load(#"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
XmlNamespaceManager ns = new XmlNamespaceManager(xml.NameTable);
ns.AddNamespace("gesmes", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
XmlNodeList nodes = xml.DocumentElement.SelectNodes("/gesmes:Envelope/Cube/Cube/Cube", ns);
foreach (XmlNode node in nodes)
{
// some code here
}
However, nodes is always null. I have tried a lot of options, and the unique option it worked for me was removing namespace from the original XML. But I would like to parse the source directly without modifications.
There are three issues to correct:
You misdefine the namespace associated with gesmes.
Change
ns.AddNamespace("gesmes", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
to
ns.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
Your XPath doesn't take into account that Cube and its descendants are in the default namespace.
Create a prefix for the default namespace:
ns.AddNamespace("eu", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
Update your XPath with the namespace prefix from #2:
/gesmes:Envelope/eu:Cube/eu:Cube/eu:Cube
^^^ ^^^ ^^^
(Cube cubed? 🙂 )
After fixing the above issues, your code should work as expected.
Linq XML has always given me less headaches:
var doc = XDocument.Load("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
string ns = "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";
var outerCube = doc.Root.Element(XName.Get("Cube", ns));
var timeCube = outerCube.Element(XName.Get("Cube", ns));
Console.WriteLine("Time: " + timeCube.Attribute("time").Value);
foreach (var cube in timeCube.Elements())
{
Console.WriteLine(cube.Attribute("currency").Value + " => " + cube.Attribute("rate"));
}
I just wrote the same code for somebody last month. Use a dictionary
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 URL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(URL);
XNamespace ns = doc.Root.GetDefaultNamespace();
Dictionary<string, decimal> dict = doc.Descendants(ns + "Cube").Where(x => x.Attribute("currency") != null)
.GroupBy(x => (string)x.Attribute("currency"), y => (decimal)y.Attribute("rate"))
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}
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());
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();
}
}
}
Sample XML:
<Response xmlns="http://tempuri.org/">
<Result xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:string>18c03787-9222-4c9b-8f39-44c2b39c788e</a:string>
<a:string>774d38d2-a350-4711-8674-b69404283448</a:string>
</Result>
</Response>
When I attempt to parse this code I'm getting back null, i.e:
XNamespace temp = "http://tempuri.org/";
XDocument xdoc = XDocument.Parse(xml);
All the following situations return null:
xdoc.Descendants(temp + "Result")
xdoc.Descendants();
xdoc.Element(temp + "Result");
What am I misunderstanding?
****** EDIT **********
Sorry for wasting everyone's time.
It appears I was using http://www.tempuri.org instead of http://tempuri.org and incorrectly listed the proper one in my question.
Here is how you can pull out the values in a couple of clear steps:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace WaitForIt
{
class Program
{
static void Main(string[] args)
{
string thexml = #"<Response xmlns=""http://tempuri.org/""><Result xmlns:a=""http://schemas.microsoft.com/2003/10/Serialization/Arrays"" xmlns:i=""http://www.w3.org/2001/XMLSchema-instance""><a:string>18c03787-9222-4c9b-8f39-44c2b39c788e</a:string><a:string>774d38d2-a350-4711-8674-b69404283448</a:string></Result></Response>";
XDocument doc = XDocument.Parse(thexml);
XNamespace ns = "http://tempuri.org/";
var result = doc.Descendants(ns + "Result");
var resultStrings = result.Elements();
foreach (var el in resultStrings)
{
Debug.WriteLine(el.Value);
}
// output:
// 18c03787-9222-4c9b-8f39-44c2b39c788e
// 774d38d2-a350-4711-8674-b69404283448
}
}
}