Selecting Value from Last Child of an XML file - c#

I am parsing an URI for query_id. I want to get the last query's id. That is actually the last added node in the URI.
I am using the following code but it is failing to return the last node but is returning info from the first node. HELP !!!
XmlDocument doc = new XmlDocument();
doc.Load("helpdesk.hujelabs.com/user.php/1/query");
XmlNode node = doc.DocumentElement;
XmlNode id = node.LastChild.SelectSingleNode("//queries/query/description/text()");
string TID = id.InnerText;

Any answer of the form:
//queries/query[position() = last()]/query_id/text()
or
//queries/query/description[last()]/text()
is wrong.
It is a FAQ: The XPath // pseudo-operator has lower precedence then the [] operator -- this is why the above expressions select any query (or respectively description) element that is the last child of its parent -- these can be all query or description elements.
Solution:
Use:
(//queries/query)[last()]/query_id/text()
Also note: The use of the // pseudo-operator usually results in signifficant loss of efficiency, because this causes the whole (sub) tree rooted at the current node to be completely traversed (O(N^2) operation).
A golden rule: Whenever the structure of the XML document is statically (in advance) known and stable, never use //. Instead use an XPath expression that has a series of specific location steps.
For example, if all the elements you want to select can be selected using:
/x/y/queries/query
then use the above XPath expression -- not //queries/query

use this XPath
//queries/query/description[last()]/text()

To retrieve last query's query_id, change your XPath to
/queries/query[position() = last()]/query_id/text()
Or alternatively, use LINQ to XML:
var doc = XDocument.Load("http://helpdesk.hujelabs.com/user.php/1/query");
var elem = doc.Root.Elements("query").Last().Element("query_id");
var TID = (int)elem;

Related

nodes are return null when trying to read XML document

i've programmically written an xml document to store some data and when i try to load it back into my application in a different area all of my Xmlnodes are returning null even though the node name i've given it is identical. This is preventing me from extracting the innertext of each node.
Question:
What am i missing that is preventing me from reading this xml document
Code:
var xmlDocument = new XmlDocument();
xmlDocument.Load(#"\\mi\dfs\shared\Everyone\The Guy Technology\cavanaugh\OutageInformationDocument.xml");
XmlNode title = xmlDocument.SelectSingleNode("TitleTextvariable");
XmlNode type = xmlDocument.SelectSingleNode("TypeTextvaraible");
XmlNode information = xmlDocument.SelectSingleNode("InformationText");
XmlNode conference = xmlDocument.SelectSingleNode("ConferenceText");
XmlNode steps = xmlDocument.SelectSingleNode("StepsText");
XmlNode eta = xmlDocument.SelectSingleNode("EtaText");
XmlNode phone = xmlDocument.SelectSingleNode("PhoneMessageText");
XmlNode banner = xmlDocument.SelectSingleNode("BannerText");
XML Example:
<OutageInfo>
<OutageInformation>
<OutageInfoitems>
<TitleTextvariable>title text</TitleTextvariable>
<TypeTextvaraible>info</TypeTextvaraible>
<InformationText>this is a test of the outage information</InformationText>
<ConferenceText>information</ConferenceText>
<StepsText>resolve it in this way</StepsText>
<EtaText>30 minutes</EtaText>
<PhoneMessageText>There is currently a phone message up</PhoneMessageText>
<BannerText>There is not currently a banner posted</BannerText>
</OutageInfoitems>
</OutageInformation>
</OutageInfo>
You could use XElement:
var xml = XElement.Load(pathToFile);
var infoItem = xml.Descendants("OutageInfoitems").First();
var title = (string)infoItem.Element("TitleTextvariable");
The casting element to string (in the last line) is preferable way, because if there's no such element, then title will be null rather throwing exception.
The method you are using, XmlNode.SelectSingleNode(string xpath), selects the first XmlNode that matches the XPath expression passed in as the argument value.
Thus you need to use the XPath recursive descent operator // to descend your XML node hierarchy to pick out deeply nested nodes:
XmlNode title = xmlDocument.SelectSingleNode("//TitleTextvariable");
XmlNode type = xmlDocument.SelectSingleNode("//TypeTextvaraible");
XmlNode information = xmlDocument.SelectSingleNode("//InformationText");
XmlNode conference = xmlDocument.SelectSingleNode("//ConferenceText");
XmlNode steps = xmlDocument.SelectSingleNode("//StepsText");
XmlNode eta = xmlDocument.SelectSingleNode("//EtaText");
XmlNode phone = xmlDocument.SelectSingleNode("//PhoneMessageText");
XmlNode banner = xmlDocument.SelectSingleNode("//BannerText");
For more see Context for XPath Expressions:
The evaluation of an XPath expression depends on the context against which the expression operates. The context consists of the node against which the expression is evaluated and its associated environment, which includes the following...
Recursive descent
An expression that uses the double forward slash (//) indicates a search that can include zero or more levels of hierarchy. When this operator appears at the beginning of the pattern, the context is relative to the root of the document.
Working .Net fiddle.

Read XML Attribute using C#

<Block ID="Ar0010100" BOX="185 211 825 278" ELEMENT_TYPE="h1" SEQ_NO="0" />
This is an example from my XML code. In C# I need to store ONLY ID'S inside of a block element in one variable, and ONLY Box's inside of a block element. I have been trying to do this for two days, and I don't know how to narrow down my question.
XmlNodeList idList = doc.SelectNodes("/Block/ID");
doesn't work... Any version of doc.selectnode, doc.GetElementBy... doesn't return the right element/children/whatever you call it. I'm not able to find documentation that tells me what I'm trying to reference. i don't know if ID or BOX are children, if they're attributes or what. This is my first time using XML, and I can't seem to narrow down my problem.
You can simply use following code
XmlNodeList elemList = doc.GetElementsByTagName("Your Element");
for (int i = 0; i < elemList.Count; i++)
{
string attrVal = elemList[i].Attributes["ID"].Value;
}
Demo: https://dotnetfiddle.net/5PpNPk
the above code is taken from here Read XML Attribute using XmlDocument
The problem is that ID is actually neither child nor part.
It's a node's attribute. You can access it this way:
doc.SelectSingleNode("/Block").GetAttribute("ID")
// or
doc.SelectSingleNode("/Block").Attributes["ID"].Value
Of course, you can iterate through them:
foreach (XmlElement element in doc.SelectNodes("/Block"))
{
Console.WriteLine(element.GetAttribute("ID"));
}
You also can ensure that it contains ID attribute, so, you won't get NullReferenceException or other exception. Use the following XPath:
foreach (XmlElement element in doc.SelectNodes("/Block[#ID]"))
{
Console.WriteLine(element.GetAttribute("ID"));
}
Your attempted xpath tried to find <Block> element having child element <ID>. In xpath, you use # at the beginning of attribute name to reference an attribute, for example /Block/#ID.
Given a correct xpath expression as parameter, SelectNodes() and SelectSingleNode() are capable of returning attributes. Here is an example :
var xml = #"<Block ID=""Ar0010100"" BOX=""185 211 825 278"" ELEMENT_TYPE=""h1"" SEQ_NO=""0"" />";
var doc = new XmlDocument();
doc.LoadXml(xml);
XmlNodeList idList = doc.SelectNodes("/Block/#ID");
foreach(XmlNode id in idList)
{
Console.WriteLine(id.Value);
}
Demo

XPathSelectElements returns null

Load function is already defined in xmlData class
public class XmlData
{
public void Load(XElement xDoc)
{
var id = xDoc.XPathSelectElements("//ID");
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
}
}
I'm just calling the Load function from my end.
XmlData aXmlData = new XmlData();
string input, stringXML = "";
TextReader aTextReader = new StreamReader("D:\\test.xml");
while ((input = aTextReader.ReadLine()) != null)
{
stringXML += input;
}
XElement Content = XElement.Parse(stringXML);
aXmlData.Load(Content);
in load function,im getting both id and and listIds as null.
My test.xml contains
<SEARCH>
<ID>11242</ID>
<Lists>
<List CURRENT="true" AGGREGATEDCHANGED="false">
<ListIDS>
<ListID>100567</ListID>
<ListID>100564</ListID>
<ListID>100025</ListID>
<ListID>2</ListID>
<ListID>1</ListID>
</ListIDS>
</List>
</Lists>
</SEARCH>
EDIT: Your sample XML doesn't have an id element in the namespace with the nss alias. It would be <nss:id> in that case, or there'd be a default namespace set up. I've assumed for this answer that in reality the element you're looking for is in the namespace.
Your query is trying to find an element called id at the root level. To find all id elements, you need:
var tempId = xDoc.XPathSelectElements("//nss:id", ns);
... although personally I'd use:
XDocument doc = XDocument.Parse(...);
XNamespace nss = "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner";
// Or use FirstOrDefault(), or whatever...
XElement idElement = doc.Descendants(nss + "id").Single();
(I prefer using the query methods on LINQ to XML types instead of XPath... I find it easier to avoid silly syntax errors etc.)
Your sample code is also unclear as you're using xDoc which hasn't been declared... it helps to write complete examples, ideally including everything required to compile and run as a console app.
I am looking at the question 3 hours after it was submitted and 41 minutes after it was (last) edited.
There are no namespaces defined in the provided XML document.
var listIds = xDoc.XPathSelectElements("/Lists//List/ListIDS/ListIDS");
This XPath expression obviously doesn't select any node from the provided XML document, because the XML document doesn't have a top element named Lists (the name of the actual top element is SEARCH)
var id = xDoc.XPathSelectElements("//ID");
in load function,im getting both id and and listIds as null.
This statement is false, because //ID selects the only element named ID in the provided XML document, thus the value of the C# variable id is non-null. Probably you didn't test thoroughly after editing the XML document.
Most probably the original ID element belonged to some namespace. But now it is in "no namespace" and the XPath expression above does select it.
string xmldocument = "<response xmlns:nss=\"http://schemas.microsoft.com/SQLServer/reporting/reportdesigner\"><action>test</action><id>1</id></response>";
XElement Content = XElement.Parse(xmldocument);
XPathNavigator navigator = Content.CreateNavigator();
XmlNamespaceManager ns = new XmlNamespaceManager(navigator.NameTable);
ns.AddNamespace("nss", "http://schemas.microsoft.com/SQLServer/reporting/reportdesigner");
var tempId = navigator.SelectSingleNode("/id");
The reason for the null value or system returned value is due to the following
var id = xDoc.XPathSelectElements("//ID");
XpathSElectElements is System.xml.linq.XElment which is linq queried date. It cannot be directly outputed as such.
To Get individual first match element
use XPathSelectElement("//ID");
You can check the number of occurrences using XPathSelectElements as
var count=xDoc.XPathSelectElements("//ID").count();
you can also query the linq statement as order by using specific conditions
Inorder to get node value from a list u can use this
foreach (XmlNode xNode in xDoc.SelectNodes("//ListIDS/ListID"))
{
Console.WriteLine(xNode.InnerText);
}
For Second list you havnt got the value since, the XPath for list items is not correct

LINQ to XML equivalent of XPath

I have code which parses XML that looks like this:
<custom_fields>
<custom_field>
<column_name>foo</column_name>
<column_value>0</column_value>
<description>Submitted</description>
<data_type>BOOLEAN</data_type>
<length>0</length>
<decimal>0</decimal>
</custom_field>
<custom_field>
<column_name>bar</column_name>
<column_value>0</column_value>
<description>Validated</description>
<data_type>BOOLEAN</data_type>
<length>0</length>
<decimal>0</decimal>
</custom_field>
</custom_fields>
... more <custom_field> elements...
I want to find the element called custom_field which has a child element called column_name with a certain value (for example bar), and then find that child's sibling called column_value and get its value. Right now I use XPath on an XMlDocument to do this:
string path = "//custom_fields/custom_field[column_name='" + key + "']";
XmlNode xNode = doc.SelectSingleNode(path);
if (xNode != null)
{
XmlNode v = xNode.SelectSingleNode("column_value");
val.SetValue(v.InnerText);
}
Where key is the name of the field I am looking for.
But I want to do this using the new LINQ to XML syntax on an XDocument. My thinking is that I will move much of my old-style XPath parsing to the LINQ methods. Maybe it's not a good idea, but this is a case where if I can get it to work, then I believe I will have a much better understanding of LINQ in general, and will be able to clean up a lot of complex code.
You can always use XPath within LINQ to XML. Just include the System.Xml.XPath namespace.
var xpath = $"//custom_fields/custom_field[column_name='{key}']/column_value";
var columnValue = doc.XPathSelectElement(xpath);
if (columnValue != null)
{
val.SetValue((int)columnValue);
}
Otherwise for the equivalent LINQ to XML query:
var columnValue = doc.Descendants("custom_fields")
.Elements("custom_field")
.Where(cf => (string)cf.Element("column_name") == key) // assuming `key` is a string
.Elements("column_value")
.SingleOrDefault();
Your XQuery expression
//custom_fields/custom_field[column_name='key']
selects all custom_field elements in custom_fields elements where the value of the column_key child element equals "key". You expect a single element to be returned and select the value of the column_value child element.
You can express this using LINQ to XML as follows:
var doc = XDocument.Load(...);
var query = from fields in doc.Descendants("custom_fields")
from field in fields.Elements("custom_field")
where (string)field.Element("column_name") == "key"
select (int)field.Element("column_value");
int result = query.Single();
I want to find the element called
custom_field which has a child element
called column_name with a certain
value (for example "bar", and then
find that child's sibling called
column_value and get its value.
Use:
/custom_fields/custom_field[column_name = 'bar']/column_value

xmldoc.Childnodes.item() question

I decided to try out the tutorial on this website
http://www.csharphelp.com/2006/05/creating-a-xml-document-with-c/
Here's my code, which is more or less the same but a bit easier to read
using System;
using System.Xml;
public class Mainclass
{
public static void Main()
{
XmlDocument XmlDoc = new XmlDocument();
XmlDocument xmldoc;
XmlNode node1;
node1 = XmlDoc.CreateNode(XmlNodeType.XmlDeclaration, "", "");
XmlDoc.AppendChild(node1);
XmlElement element1;
element1 = XmlDoc.CreateElement("", "ROOT", "");
XmlText text1;
text1 = XmlDoc.CreateTextNode("this is the text of the root element");
element1.AppendChild(text1);
// appends the text specified above to the element1
XmlDoc.AppendChild(element1);
// another element
XmlElement element2;
element2 = XmlDoc.CreateElement("", "AnotherElement", "");
XmlText text2;
text2 = XmlDoc.CreateTextNode("This is the text of this element");
element2.AppendChild(text2);
XmlDoc.ChildNodes.Item(1).AppendChild(element2);
}
}
So far, I'm liking XmlDocument, but I can't figure out how this line works
XmlDoc.ChildNodes.Item(1).AppendChild(element2);
Specifically, the Item() part of it
according to MSDN...
//
// Summary:
// Retrieves a node at the given index.
//
// Parameters:
// index:
// Zero-based index into the list of nodes.
//
// Returns:
// The System.Xml.XmlNode in the collection. If index is greater than or equal
// to the number of nodes in the list, this returns null.
However, I'm still not really sure what "index" refers to, or what Item() does. Does it move down the tree or down a branch?
Also, when I was looking at it, I thought it would end up like this
what I thought would happen:
<?xml version="1.0"?>
<ROOT>this is the text of the root element</ROOT>
<AnotherElement>This is the text of this element</AnotherElement>
but it ended up like this
Actual output
<?xml version="1.0"?>
<ROOT>this is the text of the root element
<AnotherElement>This is the text of this element</AnotherElement>
</ROOT>
(formatting added)
The ChildNodes property returns the XmlNodeList of immediate children of what you call it on. Item then finds the nth member of that list. It won't recurse into grand-children etc. In particular, I believe in this case Item(0) would return the XML declaration, and Item(1) returns the root element. A nicer way of expressing "get to the root element" would be to use XmlDocument.DocumentElement.
Note that your "expected" output wouldn't even be valid XML - an XML document can only have one root element.
To be honest, this isn't a terribly nice use of it - and in particular I would recommend using LINQ to XML rather than XmlDocument if you possibly can. It's not particularly clear what you're trying to achieve with the code you've given, but it would almost certainly be much simpler in LINQ to XML.

Categories

Resources