I need some help in returning an XmlDocument object from XPathNodeIterator.
Here is what I was trying to do:
public XmlDocument GetFilteredXML(XmlDocument baseXML, int codeID)
{
XPathNavigator nav = baseXML.CreateNavigator();
string xpath = /*some expression based on codeID*/;
XPathExpression exp = nav.Compile(xpath);
exp.AddSort(/*do some sorting*/);
XPathNodeIterator iter = exp.Select(exp);
// Here how do I return an XmlDocument object from
// the iterator which contains the selected nodes only?
}
The XPathNodeIterator does not contain the nodes, exactly. The name is a clue - it is an iterator, which means it only contains the logic for how to iterate over the nodes you want. The nodes themselves come from somewhere else - in this case, the original baseXML object you provided. They never leave that object, you just created a navigator that knows how to navigate the document, and an iterator, which knows how to iterate the navigator using some criteria.
To do what you're describing, you need to create a new XmlDocument, give it a new root element, and for each node from the iterator, call ImportNode and then Append. This will create a flat XmlDocument with all the selected nodes in the root element.
This is an option:
if (iter.MoveNext()) // Position on first node selected,
{ // assuming that it is the container for the desired result.
XmlDocument output = new XmlDocument();
output.LoadXml(iter.Current.OuterXml);
return output;
}
However, I don't think you need to make it into an XPathNavigator in the first place. What operation are you doing, that cannot be accomplished using the XmlDocument methods ?(SelectNodes and SelectSingleNode springs to mind for evaluating an XPath expression)
Related
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.
I am trying to write a class, that contains a private XPathExpression with functions where users can pass in a XPathNavigator or HtmlNodeNavigator, which returns also XPathNavigator or HtmlNodeNavigator.
class Demo
{
private XPathExpression expr;
public Demo(String xpath)
{
expr = XPathExpression.Compile(xpath);
}
XPathNavigator GetFirst(XPathNavigator current)
{
return current.SelectSingleNode(expr);
}
}
The goal is that these can be used in sequence, so for example
String content = "<x><x>abc</x></x>";
var doc = new HtmlDocument(content);
var demo = new Demo("//x");
XPathNavigator root = doc.DocumentNode.CreateNavigator();
XPathNavigator outer = demo.GetFirst(root);
XPathNavigator inner = demo.GetFirst(outer);
Here I want that inner.Value should equal "abc". However, this doesn't work. The expression //x will always select from the root. To make it work, I could change the expression to .//x, so that it actually uses the current node.
But is there a way to do this without modifying the expression (which is all similar questions use as their answer)? I'm looking for something like current.AsRoot.SelectSingleNode(expr). This would be the best solution for me. Although I would prefer to do this without parsing an entirely new HtmlDocument for efficiency reasons.
If that is not possible, as a backup plan, how could I validate that the expression is actually relative to give feedback to the user? Would simply checking that it starts with . cover all cases? I think there are some function calls that could complicate the validation.
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;
I try to remove some XmlElements from my Xml file in C#2.0.
I can remove it successfully with XmlNode.Remove() method. But there is no Remove method in XmlElement.
I googled and found this.
elements are a type of node. In fact, if you look at the members of XmlNode and XmlElement in the .NET Framework, you will see that they are very much alike, but XmlElement has more going on. It inherits XmlNode and then is further customized. This is because an element is more specialized. A node is more general in scope. The document is a node, a processing instruction is a node, and so forth. Elements are different. If you look at the XmlNodeType property of an element, you will see that it is Element, one of the many types of nodes you find.
If element are a type of node, then why I can't use remove command. Then how?
XmlDocument doc_AlarmSettingUp = new XmlDocument();
doc_AlarmSettingUp.Load(xmlFile_AlarmSettingUp);
XmlNode rootDest = doc_AlarmSettingUp.SelectSingleNode("/Equipment/AlarmSettingUp/EnabledALIDs");
foreach (XmlElement el_AlarmSettingUp in doc_AlarmSettingUp.SelectNodes("/Equipment/AlarmSettingUp/EnabledALIDs/ALID"))
{
XmlElement outEl;
if (lookup.TryGetValue(el_AlarmSettingUp.GetAttribute("alid"), out outEl))
{
// exists; element now in "other"
// Console.WriteLine("exists");
}
else
{
// doesn't exist
Console.WriteLine("doesn't exist");
// Then How can I remove element with an element method? Thanks.
}
}
The following code should work on any XmlElement:
if (outEl.ParentNode != null) outEl.ParentNode.RemoveChild(outEl);
What are you trying to do with the code above? It doesn't relate to the question as far as I can see.
What is the problem with calling Remove on an XmlElement (which is an XmlNode)?
As far as I can see, XmlNode doesn't have a Remove() method either. It does have a RemoveChild(XmlNode) method, and so does XmlElement. You should use RemoveChild of the parent element to remove the child element.
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.