Determine whether an element exists in XML to Linq - c#

So I'm using Amazon MWS and I was finally able to parse through the ListOrder response, but I have a problem. If there are over 100 orders, it will put a "NextToken" element in the 3rd level. I keep trying to find it, but whenever I run my code, it shows up null even though I know it's there (by looking at the actual XML generated in the response). To clear things up, here's an XML sample (irrelevant Elements redacted) and the code I'm using to read it.
<ListOrdersResponse xmlns="https://mws.amazonservices.com/Orders/2011-01-01">
<ListOrdersResult>
<NextToken>let's just pretend this is a nice token :)</NextToken>
</ListOrdersResult>
</ListOrdersResponse>
And the code:
XElement nextToken = null;
XDocument responseXMLDoc = XDocument.Parse(responseXml);
XNamespace ns = "http://mws.amazonservices.com/schema/Products/2011-10-01";
nextToken = responseXMLDoc.Root.Element(ns + "NextToken");
if (nextToken != null)
{
hasNext = true;
}
else
{
Console.WriteLine("No more pages!");
System.Threading.Thread.Sleep(20000);
}
Every time I run this, even though is always there, I receive a null. I actually have to define the XElement in a parent scope so I can use it later.
Some things I've tried:
Removed "root" from responseXMLDoc.Root.Element
Didn't use the namespace in (ns + "NextToken)
There will only ever be one NextToken element in a request, and I just need the token from it so I can call the request again with the token - and keep repeating until there is no "NextToken".
Update: I'm certain I am getting the syntax wrong, I just can't seem to put my finger on the problem. With the same sample, trying
XElement listOrdersResult = responseXMLDoc.Root.Element(ns + "ListOrdersResult");
will also return a null value! I've read a bunch of questions on the Linq/XML topics here, and that's where I learned most of the syntax. Still not getting any results.
Update 2: Thanks to Brad Cunningham for the answer!
To fix the namespace issue I changed the following [the root node always has the xmlns attribute (and only that attribute):
String docNameSpace = responseXMLDoc.Root.FirstAttribute.Value.ToString();
XNamespace ns = docNameSpace;
And changing the following gives me the element I'm looking for:
nextToken = responseXMLDoc.Root.Descendants(ns + "NextToken").FirstOrDefault();

Your namespace is incorrect in your example (maybe a copy and paste error just for the example)
NextToken has a namespace of
https://mws.amazonservices.com/Orders/2011-01-01
However you are looking for a namespace of
http://mws.amazonservices.com/schema/Products/2011-10-01
Also using Element will only return the element if it is a immediate child of the parent element.
You should use Descendants if you don't know what level the node will be at
This works for me
XDocument responseXMLDoc = XDocument.Parse(responseXml);
XNamespace ns = "https://mws.amazonservices.com/Orders/2011-01-01";
XElement nextToken = responseXMLDoc.Root.Descendants(ns + "NextToken").FirstOrDefault();

Related

Insert a node into a XML file

I am trying to add a single line/node (provided below) into an XML:
<Import Project=".www\temp.proj" Condition="Exists('.www\temp.proj')" />
The line could be under the main/root node of the XML:
<Project Sdk="Microsoft.NET.Sdk">
The approach I used:
XmlDocument Proj = new XmlDocument();
Proj.LoadXml(file);
XmlElement root = Proj.DocumentElement;
// Not sure about the next steps
root.SetAttribute("not sure", "not sure", "not sure");
Though I don't exactly know how to add that line in the XML, cause it was my first try on directly editing XML files, the error caused an extra problem over it.
I get this error on my first attempt:
C# "loadxml" 'Data at the root level is invalid. Line 1, position 1.'
Know this error was a famous one, which some provided a variety of approaches in this link:
xml.LoadData - Data at the root level is invalid. Line 1, position 1
Unfortunately, most of the solutions are outdated, the answer didn't work on this case, and I don't know how to apply others on this case.
Provided/accepted answer on the link for that issue:
string _byteOrderMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
if (xml.StartsWith(_byteOrderMarkUtf8))
{
xml = xml.Remove(0, _byteOrderMarkUtf8.Length);
}
Basically it didn't work, cause xml.StartsWith seems not existing anymore, at the same time xml.Remove also doesn't exist.
Can you please provide a piece of code that bypass the error and add the line to the XML?
Edit:
The sample XML file is provided in the comments section.
For the Xml posted in the comment, I have used two approachs :
1 - XmlDocument
XmlDocument Proj = new XmlDocument();
Proj.Load(file);
XmlElement root = Proj.DocumentElement;
//Create node
XmlNode node = Proj.CreateNode(XmlNodeType.Element, "Import", null);
//create attributes
XmlAttribute attrP = Proj.CreateAttribute("Project");
attrP.Value = ".www\\temp.proj";
XmlAttribute attrC = Proj.CreateAttribute("Condition");
attrC.Value = "Exists('.www\\temp.proj')";
node.Attributes.Append(attrP);
node.Attributes.Append(attrC);
//Get node PropertyGroup, the new node will be inserted before it
XmlNode pG = Proj.SelectSingleNode("/Project/PropertyGroup");
root.InsertBefore(node, pG);
Console.WriteLine(root.OuterXml);
2 - Linq To Xml, by using XDocument
XDocument xDocument = XDocument.Load(file);
xDocument.Root.AddFirst(new XElement("Import",
new XAttribute[]
{
new XAttribute("Project", ".www\\temp.proj"),
new XAttribute("Condition", "Exists('.www\\temp.proj')")
}));
Console.WriteLine(xDocument);
Namespace to add for XDocument:
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
Both solutions give the same result, but the last one is simple.
I hope you find this helpful.
Would it be possible for you to use the official MSBuild libraries?(https://www.nuget.org/packages/Microsoft.Build/)
I'm not sure which nuget package is actually required to read and edit project files only.
I've tried to programatically edit MSBuild project files directly and can not recommend it. It broke regulary due to unexpected changes...
The MSBuild library does a good job in editing project files and e.g. adding Properties, Items or Imports.

Selectnodes for XmlNode cannot find Relationship nodes

Hullo,
I am trying to use XmlNode's SelectNodes method to get the ID information below. I need the ID to tie it to an image reference from it. I've used selectnodes with namespaces before successfully, but I think it has something to do with the namespace for relationships specifically, since it's defining namespaces within this node.
<pkg:package xmlns:pkg="http:blah">
<pkg:part pkg:name="/_rels/.rels" pkg:contentType="blah" pkg:padding="512">...</pkg:part>
<pkg:part pkg:name="blah" pkg:contentType="blah" pkg:padding="256">
<pkg:xmlData>
<Relationships xmlns="http:blah">
<Relationship Id="rId8" Type="http:blah" Target="media/image2.png/>
<Relationship Id="rId3" Type="http:blah" Target="media/image3.png/>
Things I've tried that have returned no results:
...SelectNodes("//pkg:package/pkg:part/pkg:xmlData/Relationships/Relationship[#Type='blah'", nsm);
...SelectNodes("//Relationships", nsm);
...SelectNodes("//xmlns:Relationships", nsm);
...SelectNodes("//Relationship", nsm);
...SelectNodes("//Relationship[#Type='http:blah'], nsm);
and so on and so on. I hoped this would help but it didnt work.
here is my setup of nsm...
nsm = new XmlNamespaceManager(xmldocin.NameTable);
nsm.AddNamespace ("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
nsm.AddNamespace("pkg", "http://schemas.microsoft.com/office/2006/xmlPackage");
nsm.AddNamespace("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
nsm.AddNamespace("a", "http://schemas.openxmlformats.org/drawingml/2006/main");
nsm.AddNamespace("pic", "http://schemas.openxmlformats.org/drawingml/2006/picture");
nsm.AddNamespace("wp", "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing");
If I use pkg:xmlData in the select query works just fine, so I know I have the rest of my code working fine.The problem is specifically with the "Relationship" nodes.
The actual information I need is the ID so i can correlate it to the target so i know which image to use when i hit that ID going through the document.
Any ideas?
Add the default namespace to your namespace manager, using some key (for instance ns):
nsm.AddNamespace("ns", ...)
then you can target the nodes of this default namespace with that key:
.. SelectNodes("//ns:Relationship", nsm);

Retrieving XmlNode SelectSingleNode Parents Node

stack overflow has helped me a ton and decided to join and ask a question myself.
My process that I am trying to do is basically select a node out of an XML document and delete the entire node that the user had selected.
Now for some code!
int index = index = list_serverlist.SelectedIndex;
string selectedItem = list_serverlist.Items[index].ToString();
XmlNode selectedNode = doc.SelectSingleNode("/ServerList/Server/ServerName[text()='" + selectedItem + "']");
selectedNode.ParentNode.RemoveAll();
doc.Save(filePath);
Also the XML file that I am using
<?xml version="1.0"?>
<ServerList>
<Server>
<ServerName>FAB13-HST01</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
<Server>
<ServerName>FAB13-HST02</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
<Server>
<ServerName>FAB13-HST03</ServerName>
<ServerIP>wasd</ServerIP>
<ServerUsername>..\Administrator</ServerUsername>
<ServerPassword>wasd</ServerPassword>
</Server>
</ServerList>
Now how I see that code happening is...
basically I get what the user selected out of the ListBox make it a string and than select the single node that has that in the ServerName field. Which when debugging seems to work fine.
However when I use the command
selectedNode.ParentNode.RemoveAll();
It deletes all childs of the node, and not including the parent null. When I debug it and try to get the Parent it seems to be returning null for some odd reason and can't figure out why.
New to XML so not sure what I am doing wrong...
If you try to get the parent after calling RemoveAll(), the selected node no longer exists.
To remove the whole server element, you could use something like.
XmlNode nodeParent = selectedNode.ParentNode;
nodeParent.ParentNode.RemoveChild(nodeParent);

Unable to Return XML Request

I'm trying to create an XML document to return through a web service, and am quite stumped. I'm still pretty new at XML itself, so I'm trying to learn as I go. The error I'm getting is
object reference not set to an
instance of an object.
The code works by calling a constructor, taking in the request document and parsing it to the response. I have the format of both the request and the response, and just need to be able to send it back.
Code:
XmlTextReader xml_read = new XmlTextReader(HttpContext.Current.Request.MapPath("/ap/aitcXMLConfirmationRequest.xml"));
XmlDocument xml_doc = new XmlDocument();
xml_doc.Load(xml_read);
xml_read.Close();
//Do some stuff.
int int_dtl = 1;
//Builds the list of Confirmation items.
XmlNodeList nodelst_cnfrm = p_transdoc.SelectNodes("//Request/OrderRequest/ItemOut");
foreach (XmlNode node in nodelst_cnfrm)
{
XmlNode node_cnfrm_itm = this.CreateElement("ConfirmationItem");
//Do some other stuff here
}
xml_doc.ImportNode(node_cnfrm_itm,true);
root.AppendChild(xml_doc); //Error generated here.
this.AppendChild(root);
But it's giving me the aforementioned error. Can anyone help out? I'm not understanding how there is no instance of an object, if I have been manipulating it before the AppendChild request.
Any ideas?
With respect to NullReferenceExceptions in general, you should just put a break point (typically F9) on that line and start the Debugger. Once that line is hit, inspect the variables and confirm that one is in fact null.
In your case, it should be pretty obvious that root is null (given the code successfully uses xml_doc). At that point, find the places where root is supposed to be set and investigate why that isn't happening.

Is there a bug in my XML code or in .NET?

I just ran into an issue where my code was parsing xml fine but once I added in a second node it started to load incorrect data. The real code spans a number of classes and projects but for the sample I've put together the basics of what's causing the issue
When the code runs I'd expect the output to be the contents of the second Task node, but instead the contents of the first node is output. It keeps pulling from the first occurrence of the EmailAddresses node despite how when you check the settings object its inner xml is that of the second Task node. The call to SelectSingleNode("//EmailAddresses") is where the issue happens.
I have two ways around this issue
Remove the leading slashes from the EmailAddresses XPath expression
Call Clone() after getting the Task or Settings node
Solution 1 works in this case but I believe this will cause other code in my project to stop working.
Solution 2 looks more like a hack to me than a real solution.
MY question is am I in fact doing this correctly and there's a bug in .NET (all versions) or am I just pulling the XML wrong?
The c# code
var doc = new XmlDocument();
doc.Load(#"D:\temp\Sample.xml");
var tasks = doc.SelectSingleNode("Server/Tasks");
foreach (XmlNode threadNode in tasks.ChildNodes)
{
if (threadNode.Name.ToLower() != "thread")
{
continue;
}
foreach (XmlNode taskNode in threadNode.ChildNodes)
{
if (taskNode.Name.ToLower() != "task" || taskNode.Attributes["name"].Value != "task 1")
{
continue;
}
var settings = taskNode.SelectSingleNode("Settings");
var emails = settings.SelectSingleNode("//EmailAddresses");
Console.WriteLine(emails.InnerText);
}
}
The XML
<?xml version="1.0"?>
<Server>
<Tasks>
<Thread>
<Task name="task 2">
<Settings>
<EmailAddresses>task 2 data</EmailAddresses>
</Settings>
</Task>
</Thread>
<Thread>
<Task name="task 1">
<Settings>
<EmailAddresses>task 1 data</EmailAddresses>
</Settings>
</Task>
</Thread>
</Tasks>
</Server>
From http://www.w3.org/TR/xpath/#path-abbrev
// is short for
/descendant-or-self::node()/. For
example, //para is short for
/descendant-or-self::node()/child::para
and so will select any para element in
the document (even a para element that
is a document element will be selected
by //para since the document element
node is a child of the root node);
And also:
A location step of . is short for
self::node(). This is particularly
useful in conjunction with //. For
example, the location path .//para
is short for
self::node()/descendant-or-self::node()/child::para
and so will select all para descendant
elements of the context node.
Instead of:
var settings = taskNode.SelectSingleNode("Settings");
var emails = settings.SelectSingleNode("//EmailAddresses");
Use:
var emails = taskNode.SelectSingleNode("Settings/EmailAddresses");
The // XPath expression does not do what you think it does. It selects nodes in the document from the current node that match the selection no matter where they are.
In other words, it's not limited by the current scope, it actually crawls back up the document tree and starts matching from the root element.
To select the first <EmailAddresses> element in your current scope, you only need:
var emails = settings.SelectSingleNode("EmailAddresses");

Categories

Resources