C# get values from xml attributes - c#

How to get attribute "action" and "filename" values in a right way using C#?
XML:
<?xml version="1.0" encoding="utf-8" ?>
<Config version="1.0.1.1" >
<Items>
<Item action="Create" filename="newtest.xml"/>
<Item action="Update" filename="oldtest.xml"/>
</Items>
</Config>
C#: i cannot get attribute values as well as how to get values in foreach loops? How to solve this?
var doc = new XmlDocument();
doc.Load(#newFile);
var element = ((XmlElement)doc.GetElementsByTagName("Config/Items/Item")[0]); //null
var xmlActions = element.GetAttribute("action"); //cannot get values
var xmlFileNames= element.GetAttribute("filename"); //cannot get values
foreach (var action in xmlActions)
{
//not working
}
foreach (var file in xmlFileNames)
{
//not working
}
Your code example means alot to me. Thanks!

You can use LINQ to XML. Following query returns strongly typed collection of items with Action and FileName properties:
var xdoc = XDocument.Load(#newFile);
var items = from i in xdoc.Descendants("Item")
select new {
Action = (string)i.Attribute("action"),
FileName = (string)i.Attribute("fileName")
};
foreach (var item in items)
{
// use item.Action or item.FileName
}

GetElementsByTagName will find you only direct descendants. The argument is supposed to be just a tag name, not a whole path of elements.
If you want to search across the document while supplying an XPath expression, use SelectNodes instead.
For your document, it should look like this:
var element = (XmlElement)doc.SelectNodes("/Config/Items/Item")[0];

You can achieve what you're asking with LINQ to XML:
// For each element that is a child of your Items element that is named Item
foreach (var item in XElement.Load("file.xml").Descendants("Items").Elements("Item"))
{
// If the element does not have any attributes
if (!item.Attributes().Any())
{
// Lets skip it
continue;
}
// Obtain the value of your action attribute - Possible null reference exception here that should be handled
var action = item.Attribute("action").Value;
// Obtain the value of your filename attribute - Possible null reference exception here that should be handled
var filename = item.Attribute("filename").Value;
// Do something with your data
Console.WriteLine("action: {0}, filename {1}", action, filename);
}

There are a bunch of problems with the code in the question:
1. You are using an XPath in the GetElementsByTagName, just use the tag
2. You are only getting the first XmlNode in the XmlNodeCollection by using [0]
3. Since you only have one XmlNode, you are only getting a string result for getting the attribute, not a collection of strings, which you are then trying to enumerate through
4. Your foreach is broken, there is no type for the resulting object
Here is a snippet that would work:
var doc = new XmlDocument();
doc.Load("test.xml");
var items = doc.GetElementsByTagName("Item");
var xmlActions = new string[items.Count];
var xmlFileNames = new string[items.Count];
for (int i = 0; i < items.Count; i++) {
var xmlAttributeCollection = items[i].Attributes;
if (xmlAttributeCollection != null) {
var action = xmlAttributeCollection["action"];
xmlActions[i] = action.Value;
var fileName = xmlAttributeCollection["filename"];
xmlFileNames[i] = fileName.Value;
}
}
foreach (var action in xmlActions) {
//working
}
foreach (var file in xmlFileNames) {
//working
}
Or, if you don't need all of the actions and filenames in a collection before you act on them, you could just act on each action/filename in the for loop.

Related

XElement.Elements() returning empty collection

I'm trying to get the values from an XElement that I'm receiving from db notification.
The xml structure is like this:
<?xml version="1.0"?>
<root>
<inserted>
<row>
<CODI_AVERIA>22</CODI_AVERIA>
<NUMERO_LINIA>2</NUMERO_LINIA>
<DIA>2016-07-17T00:00:00</DIA>
<HORA>1899-12-30T10:26:15.790</HORA>
<CODI_USUARI>1</CODI_USUARI>
<ACCIO>0</ACCIO>
<CODI_PSEUDO>-1</CODI_PSEUDO>
</row>
</inserted>
</root>
And this is the method that I'm using to get the data, and it returns me a List that is empty.
static void getAccio(XElement xml)
{
try
{
xml.Descendants("deleted").Remove();
var items = xml.Elements("row").Select(n => new
{
Codi_averia = n.Element("CODI_AVERIA").Value,
Numero_linia = n.Element("NUMERO_LINIA)").Value,
Accio = n.Element("ACCIO").Value
}).ToList();
}
catch (Exception e)
{
Console.Write(e.Message);
}
}
I have tried to get the value of each field apart and it doesn't allow me to get them separetly as XElements.
Use .Descendants() instead of .Elements():
var items = xml.Descendants("row").Select(n => new
{
Codi_averia = n.Element("CODI_AVERIA").Value,
Numero_linia = n.Element("NUMERO_LINIA)").Value,
Accio = n.Element("ACCIO").Value
}).ToList();
.Elements finds only those elements that are direct descendents, i.e. immediate children. - And I assume that your XElement is the parent of the inserted section.
For difference between .Element and .Descendants see this question
If you don't want to find them in all inner elements too then do .Element("inserted").Elements("rows")
Your document doesn't have any root element with the name row. Instead, you need to select inserted first, and enumerate the row elements of inserted:
var xml = #"<inserted>
<row>
<CODI_AVERIA>21</CODI_AVERIA>
<NUMERO_LINIA>2</NUMERO_LINIA>
<DIA>2016-07-17T00:00:00</DIA>
<HORA>1899-12-30T10:26:15.790</HORA>
<CODI_USUARI>1</CODI_USUARI>
<ACCIO>0</ACCIO>
<CODI_PSEUDO>-1</CODI_PSEUDO>
</row>
</inserted>";
var xdoc = XDocument.Parse(xml);
var items = xdoc.Element("inserted").Elements("row").Select(n => new
{
Codi_averia = n.Element("CODI_AVERIA").Value,
Numero_linia = n.Element("NUMERO_LINIA").Value,
Accio = n.Element("ACCIO").Value
}).ToList();
In your sample code ("NUMERO_LINIA)") is wrong because of additionnal quote and parenthesis.
This works for me :
XDocument xd = XDocument.Load("XMLFile1.xml");
var lst = (from n in xd.Descendants("row")
select new
{
Codi_averia = n.Element("CODI_AVERIA").Value,
Numero_linia = n.Element("NUMERO_LINIA").Value,
Accio = n.Element("ACCIO").Value
}).ToList();
foreach (var item in lst)
Console.WriteLine(string.Format("{0}{1}{2}", item.Codi_averia, item.Numero_linia, item.Accio));

How to extract xml child element

I am trying to figure out the code to extract xml child (I think this is worded correctly) elements. I have searched and tried many samples but cannot find how to drill down to pick out the section I want and return the information I need. Maybe I all I need is someone to define the data I am trying to pull so I can read up on the issue, of course any code would be very helpful and I will figure it out from there. Thanks in advanced for any help!
Here is the xml file. I am trying to run an if statement to find the section named <STATISTICTYPE>PVCAP_CharactersSaved</STATISTICTYPE> and return the <JOBNAME>,<TIMEDELTA>,<VALUESUM>.
<?xml version="1.0" encoding="utf-8"?>
<PVCAPTURESTATISTICCONTAINTER xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PVCAPTUREJOBSTATISTICS>
<PVCAPTURESTATISTICSUMMARY>
<STATISTICTYPE>PVCAP_CharactersSaved</STATISTICTYPE>
<STATISTICNAME>Characters saved</STATISTICNAME>
<JOBID>24</JOBID>
<JOBNAME>HEAT FILES</JOBNAME>
<TIMEDELTA>422</TIMEDELTA>
<VALUESUM>25432</VALUESUM>
</PVCAPTURESTATISTICSUMMARY>
<PVCAPTURESTATISTICSUMMARY>
<STATISTICTYPE>PVCAP_CharactersSaved_NoMM</STATISTICTYPE>
<STATISTICNAME>Characters saved (no match and merge)</STATISTICNAME>
<JOBID>24</JOBID>
<JOBNAME>HEAT FILES</JOBNAME>
<TIMEDELTA>422</TIMEDELTA>
<VALUESUM>25432</VALUESUM>
</PVCAPTURESTATISTICSUMMARY>
</PVCAPTUREJOBSTATISTICS>
<DOCUMENTCOUNT>762</DOCUMENTCOUNT>
<PAGECOUNT>3194</PAGECOUNT>
<IMAGECOUNT>3194</IMAGECOUNT>
<VERSION>2.0</VERSION>
</PVCAPTURESTATISTICCONTAINTER>
You can use LINQ to XML, particularly the XElement class.
var element = XElement.Parse(xmlStr).Element("PVCAPTUREJOBSTATISTICS")
.Elements("PVCAPTURESTATISTICSUMMARY")
.First(c => c.Element("STATISTICTYPE").Value == "PVCAP_CharactersSaved")
var jobName = element.Element("JOBNAME").Value;
var timeDelta = element.Element("TIMEDELTA").Value;
var valueSum = element.Element("VALUESUM").Value;
You'll want to add in some error handling and whatnot here, but this should get you going in the right direction.
You can do something like this:
XElement res = XElement.Parse(xmlResult);
foreach(var elem in res.Element("PVCAPTUREJOBSTATISTICS").Elements("PVCAPTURESTATISTICSUMMARY"))
{
if (elem.Element("STATISTICTYPE").Value.Equals("PVCAP_CharactersSaved", StringComparison.Ordinal))
{
string jobName = elem.Element("JOBNAME").Value;
string timeDelta = elem.Element("TIMEDELTA").Value;
string valueSum = elem.Element("VALUESUM").Value;
}
}
You can use XDocument and LINQ-to-XML to do that quite easily, for example :
string xml = "your xml content here";
XDocument doc = XDocument.Parse(xml);
//or if you have the xml file instead :
//XDocument doc = XDocument.Load("path_to_xml_file.xml");
var result = doc.Descendants("PVCAPTURESTATISTICSUMMARY")
.Where(o => (string) o.Element("STATISTICTYPE") == "PVCAP_CharactersSaved")
.Select(o => new
{
jobname = (string) o.Element("JOBNAME"),
timedelta = (string) o.Element("TIMEDELTA"),
valuesum = (string) o.Element("VALUESUM")
});
foreach (var r in result)
{
Console.WriteLine(r);
}

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.

how get tags from a XElement

I've developed code like this:
String resultString = await response.Content.ReadAsStringAsync();
Stream resultStream = await response.Content.ReadAsStreamAsync();
XElement rootElement = XDocument.Load(resultStream).Elements().First();
XElement blobs = rootElement.Element("Blobs");
foreach (var blob in blobs.Elements("Blob"))
{
var t = blob;
}
Now the resultString and resultStream come from HttpClient reposnse. I get the response from an Azure blob REST service (listing), like this:
<EnumerationResults>
<Blobs>
<Blob>
<Name></Name>
<Url></Url>
<Properties></Properties>
</Blob>
<Blob>
<Name></Name>
<Url></Url>
<Properties></Properties>
</Blob>
</Blobs>
</EnumerationResults>
With my code, I've managed to get an IEnumerable<XNode> from <Blobs> BUT I can't get to Name and Url inside the <Blob> element. I get all of it as a string in one line. How do I need to change my code to get each <Blob> and get from it <Name> and <Url>?
Well to start with, once you've loaded the document, you can just use the Root property to get at the root element. You can then get each Blob element using Descendants or via multiple calls to Elements. You can do that outside the foreach loop though:
var doc = XDocument.Load(resultStream);
var allBlobs = doc.Root.Elements("Blobs").Elements("Blob");
or
var doc = XDocument.Load(resultStream);
var allBlobs = doc.Root.Descendants("Blob");
Now when you iterate over each blob, you can just get the name element using Element again, and get the text content of the element by using the explicit string conversion
foreach (var blob in allBlobs)
{
var nameElement = blob.Element("Name");
var nameText = (string) nameElement;
...
}
Obviously that can be done as a single statement - I just wanted to keep them separate for clarity. You can then do the same for the URL. Using the string conversion gives you a null reference if the element is missing, whereas using the Value property will give you a NullReferenceException unless you guard against it. Which is more appropriate depends on your use case.
An alternative approach is to do all the extraction outside your foreach loop, in a query:
var data = doc.Root.Elements("Blobs").Elements("Blob")
.Select(blob => new {
Name = (string) blob.Element("Name"),
Url = (string) blob.Element("Url");
});
Then:
foreach (var item in data)
{
// Use item.Name and item.Data in here
}
XElement blobs = rootElement.Element("Blobs");
var v = blobs.Select(blob => new { Name = blob.Descendents.FirstOrDefault(x => x.Name.LocalName == "Name"), Url = blob.Descendants.FirstOrDefault(x => x.Name.LocalName == "Url") });

How can I know the index of a XML Tag

How can I get the index of my current XML tag ?
Example:
<User>
<Contact>
<Name>Lucas</Name>
</Contact>
<Contact>
<Name>Andre</Name>
</Contact>
...
</User>
I'm trying the code below
foreach (var element2 in doc2.Root.Descendants())
{
String name = element.Name.LocalName;
String value = element.Value;
}
I want to know if I'm reading the first <Contact> tag, or the second, or the third...
Using the appropriate overload of Select will yield the index as you enumerate the collection.
var userContacts = doc2.Root
.Descendants()
.Where(element => element.Name == "Contact")
.Select((c, i) => new {Contact = c, Index = i});
foreach(var indexedContact in userContacts)
{
// indexedContact.Contact
// indexedContact.Index
}
Note: I added the .Where because .Descendants will recurse.
You can use a for statement, then you'll always know the index. I am making an assumption that Descendants() can be used in a for statement.
The other possibility it to create a count variable outside the foreach.
int count = 0
foreach (var element2 in doc2.Root.Descendants())
{
String name = element.Name.LocalName;
String value = element.Value;
count++;
}
Replace your foreach loop with a normal for loop:
for (int i = 0; i < doc2.Root.Descendants().Count(); i++)
{
String name = doc2.Root.Descendants()[i].Name.LocalName;
String value = doc2.Root.Descendants()[i].Value;
}
Then use i to see if you're reading the first, second, third, etc. tag.
There is no way to get the index of a foreach enumerator without using an external counter.. AFAIK.
This also presents an efficiency problem, as you have to process the Descendants method twice every loop iteration, so I recommend keeping a List representing the Descendants outside of the for loop, and then use it like this:
var desecendants = doc2.Root.Descendants().ToList();
for (int i = 0; i < descendants.Count; i++)
{
String name = descendants[i].Name.LocalName;
String value = descendants[i].Value;
}
Use a variable as counter and put the result into an array. The problem here is, that you need to know the size of the array in advance.
int i = 0;
foreach (var element in doc2.Root.Descendants()) {
name[i] = element.Name.LocalName;
value[i] = element.Value;
i++;
}
with the use of a List<T> you don't have this problem
var list = new List<KeyValuePair<string,string>>();
foreach (var element in doc2.Root.Descendants()) {
list.Append(new KeyValuePair(element.Name.LocalName, element.Value));
}
I don't think you can with foreach, try using a normal for loop instead.
To get the position of your current node without counters (as previous solutions pointed out) you'll need to write a function to build up a the XPath of your current XmlElement. The only way to do it is to traverse the document from your node using parent node and previous siblings. That way you'll be able to build up the exact XPath to access your node from the document. Here's a sample taken from here
public static string GetXPath_UsingPreviousSiblings(this XmlElement element)
{
string path = "/" + element.Name;
XmlElement parentElement = element.ParentNode as XmlElement;
if (parentElement != null)
{
// Gets the position within the parent element, based on previous siblings of the same name.
// However, this position is irrelevant if the element is unique under its parent:
XPathNavigator navigator = parentElement.CreateNavigator();
int count = Convert.ToInt32(navigator.Evaluate("count(" + element.Name + ")"));
if (count > 1) // There's more than 1 element with the same name
{
int position = 1;
XmlElement previousSibling = element.PreviousSibling as XmlElement;
while (previousSibling != null)
{
if (previousSibling.Name == element.Name)
position++;
previousSibling = previousSibling.PreviousSibling as XmlElement;
}
path = path + "[" + position + "]";
}
// Climbing up to the parent elements:
path = parentElement.GetXPath_UsingPreviousSiblings() + path;
}
return path;
}
Assuming that's really what you really need, depending on the document size it could be resource intensive. If you only require the index, I'd recommend using one of the other methods.

Categories