XPath Query C# to pull out nodes dynamically - c#

I am having to write a XPath Query to pull out the answer for a question based on the question id. The question id is passed dynamically to the query. I cannot use LINQ as the solution is in NET 2.0. Please find the XML file below
<?xml version="1.0" encoding="utf-8" ?>
<Questionaire>
<question id="1">
<answer>1</answer>
<correctAnswer>Text</correctAnswer>
</question>
<question id="2">
<answer>2</answer>
<correctAnswer>Text</correctAnswer>
</question>
</Questionaire>
I'am a novice to XPath and find it hard to get my head around it.
Many thanks in advance.

You could use the XmlDocument class and the SelectSingleNode method to perform XPath queries. You may checkout the following article for examples. In your case the XPath query will be something along the lines of Questionaire/question[id='1'] where the id could be variable of course in order to fetch the corresponding node. Once you find the <question> node corresponding to your search criteria you could navigate to its child nodes.

Your XPath expression can be dynamically generated like this:
myExpression = string.Format("/*/*[id='{0}']/answer", theId);
then, depending on the object representing the XML document you need to call one of the following methods: Select(), SelectNodes(), SelectSingleNode(), Evaluate().
Read the MSDN documentation about the appropriate methods of XmlDocument, XPathDocument, XPathNavigator and XPathExpression.

Related

Select a XML node based on a sibling node

I'm trying to select an XML node, where another child of the parent node contains a specific value.
The XML looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<AuthorIT>
<Objects>
<Media>don't care</Media>
<Style>don't care</Style>
<Book>don't care</Book>
<Topic>don't care</Topic>
<Topic>
<Object>
<Description>Performance Evidence</Description>
</Object>
<Text>This is what I want to select</Text>
</Topic>
</Objects>
</AuthorIT>
I'm using XPath in C#. My query at the moment looks like this: (but doesn't work, obviously)
docNav = new XPathDocument(localFile);
nav = docNav.CreateNavigator();
xPath = "//Topic[Object/Description = 'Performance Evidence']/Text";
string value = nav.SelectSingleNode(xPath).Value;
How do I get the contents of the Text node, from the Topic that has an Object/Description value of "Performance Evidence"?
You should first select the Description node containing your needle text, then move back to the common parent and select the Text nodes that are its children.
//Topic/Object/Description[text()='Performance Evidence']/../../Text/text()
As Kirill Polishchuk said in a comment, my XPath was correct.
What I left out of the example XML was the key to the solution... Namespace!
I found my answer on this other question: Using Xpath With Default Namespace in C#

Query XDocument based on the depth of the node

I have the following XML and I want to be able to query the XML based on the depth of it. I am aware of the depth before hand.
UPDATED QUESTION:
I have the following XML and I want to be able to query the XML based on if the nodes are repetitive.
So, this is my XML
<Books>
<BookID>12345</BookID>
<BookName>BookName</BookName>
<Authors>
<Author>
<Name>AuthorNameOne</Name>
<City>New York</City>
</Author>
<Author>
<Name>AuthorNameTwo</Name>
<City>New York</City>
</Author>
</Authors>
</Books>
Via XDocument I want to be able to query this XML and get node names for the elements where there is repetitive data such as Authors. Or I want to be able to query it based on the Depth of the Node.
UPDATED QUESTION:
Via XDocument I want to be able to query this XML and get node names for the elements where there is repetitive data such as Authors.
Any help will be much appreciated.
Your XML still doesn't really make sense, but I'm putting together this answer hoping that it will at least point you in the right direction. I'm going to completely ignore the portion of your question that references node depth because I'm not really sure how it applies to the following question that you posted:
Via XDocument I want to be able to query this XML and get node names for the elements where there is repetitive data such as Authors.
Here's how to do just that simple type query assuming that your XDocument is named xml:
List<XElement> repeatedNodes = new List<XElement>();
for(XElement node in xml.Descendants())
{
if(node.Parent.Elements(node.Name).Count() > 1))
{
repeatedNodes.Add(node);
}
}
Here's the same code compressed into a lambda that will provide you with an IEnumerable<XElement> containing all of the elements that would go into the List in my first example:
var dupes = xml.Descandants().Where(n => n.Parent.Elements(n.Name).Count() > 1);
This algorithm will look at every node in the xml tree and then from the parent of the current node it will count how many nodes with that same name exist. If that number is greater than one it will add it to our list of repeated nodes. This does not care what depth the current node is and it will only count duplicate named nodes at the same depth. Additionally this algorithm will put dupes in the List structure, but you can add in your own logic to prevent it from doing that or use a different structure that doesn't allow duplicates.

How to get all xpaths from an xml document

How can you get a list of all xpaths from an xml document?
<Tee Surname="Ray" Age="24">
<Login id="51" mid="1" "/>
<Server id="1" mid="144"/>
</Tee>
for example i want to get a list of all xpaths from the above xml
XPath is a search expression. You cannot "get all XPaths" from an XML document. This is like asking for all possible SQL statements from a database.

C# Load Datatable from LinQ XDocument using Using XPath where Xpath not known at compile time

I have a LinQ XDocument (from a SOAP response) that I want to turn into a Datatable. Here is the XML I am dealig with. It uses namespaces to further complicate the matter.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getDetailsResponse xmlns:ns2="http://service.xyz.com/">
<return>
<meeting meetingCode="8">
<meetingDate>2013-07-08T00:00:00+10:00</meetingDate>
<actionList>
<actionCoOrdinator>The action man</actionCoOrdinator>
<action>
<actionType>Type1</actionType>
<actionBy>John</actionBy>
</action>
<action>
<actionType>Type2</actionType>
<actionBy>Mary</actionBy>
</action>
<action>
<actionType>Type3</actionType>
<actionBy>Phil</actionBy>
</action>
</actionList>
</meeting>
</return>
</ns2:getDetailsResponse>
</soap:Body>
</soap:Envelope>
And here is the output that want in a DataTable
MeetingCode MeetingDate ActionCoOrdinator ActionType ActionBy
----------- ------------------------- ----------------- ---------- --------
8 2013-07-08T00:00:00+10:00 The action man Type1 John
8 2013-07-08T00:00:00+10:00 The action man Type2 Mary
8 2013-07-08T00:00:00+10:00 The action man Type3 Phil
As you can see I want a row for each "action" element within "actionList" but for each row I also need some info from parent/grandparent elements. All I have at this this stage is an XDocument using the code below;
XDocument myXDoc = XDocument.Parse(soapStringResult);
I've seen various posts about extracting data from an XDocument on this site but my problem is that the data items i want are not know beforehand. My starting point is an xPath which represents the "rows". The Xpath is
ns2:getDetailsResponse/return/meeting/actionList/action
Then i have a DB table that contains all the data items i want relative to this Xpath. The table looks like this
XpathDataItem
---------------------
../../#meetingCode
../../meetingDate
../actionCoOrdinator
actionType
actionBy
I can load the data into a DataTable when I know the Xpaths of the data items I'm looking for but I just dont know how to do it with an unknown set of XPaths's.
Thanks in advance
John
Which part is unknown? The starting point or the individual elements to retrieve? That's not clear from your question. If you simply don't know the starting point, then Linq-to-xml will work great for this.
Linq-to-xml doesn't normally retrieve elements based on xpath. The Elements() and Descendants() methods allow you query the xml data without knowing the specific path.
To retrieve the meetingDate regardless of what its path is, you could use the Descendants() method like this:
var meetingDateElement = myXDoc.Descendants("meetingDate").SingleOrDefault;
if (meetingDateElement != null) {
var meetingDate = meetingDateElement.Value;
}
meetingCode and actionCoOrdinator could be found in a similar manner.
And to get all the action elements, you'd simple use myXDoc.Descendants("action"). Something like this would get you the values for each action:
var actions = from a in myXDoc.Descendants("action")
select new {
actionType = a.Element("actionType").Value,
actionBy = a.Element("actionBy").Value
};
If the xml you're parsing could contain more than one meeting element, then you'd use `myXDoc.Descendants("meeting") to get all the meetings and retrieve child elements within a query or foreach statement.

CreatePathNavigator with XDocument in C# (xml with linq)

I have a question. Is it possible to navigate to an specific field in an xml and add elements to it?
I tried this:
XDocument doc = XDocument.Load("myxmldoc.xml");
doc.Element("Table").Element(Product).CreateNavigator().Add(new XElement("Lamp", "Lamp"));
Needless to say it didn't work. The thing is that I have an xml that looks like this:
<Table>
<Product>
<Chair/>
<Table/>
<ChessBoard/>
</Product>
<Product>
<Chair/>
<Table/>
<ChessBoard/>
</Product>
<Product>
<Chair/>
<Table/>
<ChessBoard/>
</Product>
</Table>
As you can see I have many Product tags and I want to be able to add the item Lamp to an specific node with linq. Every time I add elements with:
doc.Element("Table").Element("Porduct").Add(new XElement("Lamp", "Lamp"));
The Lamp element goes to the first Product node. I want it to go to an specific node, let's say the second one.
Someone advised me to use:
doc.Element("Table").Element("Porduct").ElementAt(2).Add(new XElement("Lamp", "Lamp"));
But it doesn't work. Visual Studio 2010 says it doesn't exist.
You need Elements("Porduct") (note the s) to use ElementAt() :
doc.Element("Table").Elements("Porduct").ElementAt(2).Add(...);
It would be a little easier if the Products had something to identify them, like an ID attribute.
(And probably you should fix the typo Porduct / Product)

Categories

Resources