XML reader can't find elements when using xmlns attribute - c#

I'm using visual studio for windows phone and my code for the XML reader does not work when there is attributes in the parent of the XML data.
My C# code
namespace youtube_xml
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;
}
private void listBox1_Loaded(object sender, RoutedEventArgs e)
{
var element = XElement.Load("Authors.xml");
var authors =
from var in element.Descendants("feed")
select new Authors
{
AuthorName = var.Attribute("scheme").Value,
};
listBoxAuthors.DataContext = authors;
}
public ImageSource GetImage(string path)
{
return new BitmapImage(new Uri(path, UriKind.Relative));
}
}
}
The Working XML data
<?xml version='1.0' encoding='UTF-8'?>
<feed>
<category scheme='http://schemas.google.com/g/2005#kind'/>
</feed>
NOT working data (note: the attribute "xmlns" in the root element "feed")
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' >
<category scheme='http://schemas.google.com/g/2005#kind'/>
</feed>

Welcome to the world of XML namespaces! The problem isn't the fact that "there's an attribute" - it's the fact that it's causing everything below it to be in a namespace. You can no longer say .Attribute("scheme") because that only looks for things in the empty namespace. Namespaces are used via a contraption based on operator overloading:
XNamespace atom = "http://www.w3.org/2005/Atom'";
// And now you can say:
.Descendants(atom + "feed")
.Attribute(atom + "scheme")
Et cetera. The ability to assign a string into an XNamespace variable is thanks to an implicit conversion operator. The + here actually constructs an XName (which, by the way, also has an implicit conversion from string - that's why you plain .Elements("feed") works even though the parameter type is not string)
Handy tip: You can cast an attribute into certain types instead of using .Value, for instance (string)foo.Attribute(atom + "scheme"). It also works with a bunch of other types, for instance int.

Related

Parsing URL/web-service

I made a request to a third party API and it gives me the following response in XML.
<?xml version="1.0" ?>
<abc>
<xyz>
<code>-112</code>
<message>No such device</message>
</xyz>
</abc>
I read this using this code.
XmlDocument doc = new XmlDocument();
doc.Load("*** url ***");
XmlNode node = doc.SelectSingleNode("/abc/xyz");
string code = node.SelectSingleNode("code").InnerText;
string msg = node.SelectSingleNode("message").InnerText;
Response.Write("Code: " + code);
Response.Write("Message: "+ msg);
But I get an error on this line:
string code = node.SelectSingleNode("code").InnerText;
Error is:
Object reference not set to an instance of an object.
I changed the first line of your XML file into:
<?xml version="1.0"?>
to make it valid XML. With this change, your code works for me. Without the change, the parser throws an exception.
You can use LINQ to XML (if confortable):
XDocument doc = XDocument.Load(url);
var selectors = (from elements in doc.Elements("abc").Elements("xyz")
select elements).FirstOrDefault();
string code = selectors.Element("code").Value;
string msg = selectors.Element("message").Value;
As you've given it, there doesn't seem to be anything wrong with your code Edit : Your declaration is wrong, as svinja pointed out, and your xml won't even load into the XmlDocument.
However, I'm guessing that your xml is more complicated, and there is at least one namespace involved, which would cause the select to fail.
It isn't pretty, but what you can do is use namespace agnostic xpath to locate your nodes to avoid using a XmlNamespaceManager:
XmlDocument doc = new XmlDocument();
doc.Load("*** url ***");
XmlNode node = doc.SelectSingleNode("/*[local-name()='abc']/*[local-name()='xyz']");
string code = node.SelectSingleNode("*[local-name()='code']").InnerText;
string msg = node.SelectSingleNode("*[local-name()='message']").InnerText;
Response.Write("Code: " + code);
Response.Write("Message: "+ msg);
Edit - Elaboration
Your code works fine if you correct the declaration to <?xml version="1.0"?>
However, if you introduce namespaces into the mix, your code will fail unless you use namespace managers appropriately.
My agnostic xpath above will also parse an xml document like so:
<?xml version="1.0"?>
<abc xmlns="foo">
<xyz xmlns="bar">
<code xmlns="bas">-112</code>
<message xmlns="xyz">No such device</message>
</xyz>
</abc>
<?xml version="1.0">
<abc>
<xyz>
<code>-112</code>
<message> No such device </message>
</xyz>
</abc>
try to set a list:
XmlNodeList nodeList = root.SelectNodes("/abc/xyz");
then read all the nodes and get their text:
foreach(XmlNode node in nodeList)
{
if(node.Name == "code")
{
string code = node.InnerText;
}
else
if(node.Name == "message")
{
string msg = node.InnerText;
}
}
[XmlRoot("abc")]
public class Entity
{
[XmlElement("xyz")]
public SubEntity SubEntity { get; set; }
}
public class SubEntity
{
[XmlElement("code")]
public string Code { get; set; }
[XmlElement("message")]
public string Message { get; set; }
}
And use standart xmlserializer
var xmlSerializer = new XmlSerializer(typeof(Entity));
var result = xmlSerializer.Deserialize(new XmlTextReader("*** url ***"));
Response.Write("Code: " + result.SubEntity.Code);
Response.Write("Message: "+ result.SubEntity.Message);

Add Namespace to xml file

I have a xml file to which I want to add predefined namespeces.. Following is the code:
private const string uri = "http://www.w3.org/TR/html4/";
private static readonly List<string> namespaces = new List<string> { "lun" };
public static XElement AddNameSpaceAndLoadXml(string xmlFile) {
var nameSpaceManager = new XmlNamespaceManager(new NameTable());
// add custom namespace to the manager and take the prefix from the collection
namespaces.ToList().ForEach(name => {
nameSpaceManager.AddNamespace(name, string.Concat(uri, name));
});
XmlParserContext parserContext = new XmlParserContext(null, nameSpaceManager, null, XmlSpace.Default);
using (var reader = XmlReader.Create(#xmlFile, null, parserContext)) {
return XElement.Load(reader);
}
}
The problem is that the resulting xml in memory does not show the correct namespaces added. Also, they are not added at the root but are added next to the tag. Xml added below.
In the xml it is showing p3:read_data while should be lun:read_data.
How do i get to add the namespace on the root tag and not get the incorrect name.
Sample Input xml:
<config file-suffix="perf">
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters lun:read_data=""/>
</overview-graph>
</config>
Output xml expected:
<config file-suffix="perf" xmlns:lun="http://www.w3.org/TR/html4/lun">
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters lun:read_data="" />
</overview-graph>
</config>
Output that is coming using the above code:
<config file-suffix="perf" >
<overview-graph title="Top 5 LUN Reads" max-series="5" remove-series="1">
<counters p3:read_data="" xmlns:p3="http://www.w3.org/TR/html4/lun"/>
</overview-graph>
</config>
I am not sure if there is a better way, but adding the namespace manually seems to work.
using (var reader = XmlReader.Create(#xmlFile, null, parserContext)) {
var newElement = XElement.Load(reader);
newElement.Add(new XAttribute(XNamespace.Xmlns + "lun", string.Concat(uri, "lun")));
return newElement;
}
I don't know offhand a way to generalize this however (obviously you can add the whole set by enumerating it, but outputting only used namespaces might be interesting).

Extracting values from XML returned by azure service management API

i have tried several methods of trying to extract values from an XML file but none of them seem to work. I am using C#. The XML Is as follows
<?xml version="1.0" encoding="utf-8"?>
<HostedService xmlns="http://schemas.microsoft.com/windowsazure">
<Url>hosted-service-url</Url>
<ServiceName>hosted-service-name</ServiceName>
<HostedServiceProperties>
<Description>description</Description>
<Location>location</Location>
<AffinityGroup>affinity-group</AffinityGroup>
<Label>label</Label>
</HostedServiceProperties>
</HostedService>
I would like to retrieve
hosted-service-url,
hosted-service-name,
description,
location,
affinity-group and
label
What would be the best of way of retrieving these values?
Edit :
Thanks L.B that method works perfectly. However i have just been told i will have to use the larger XML that is below.
<?xml version="1.0" encoding="utf-8"?>
<HostedService xmlns="http://schemas.microsoft.com/windowsazure">
<Url>hosted-service-url</Url>
<ServiceName>hosted-service-name</ServiceName>
<HostedServiceProperties>
<Description>description</Description>
<Location>location</Location>
<AffinityGroup>affinity-group</AffinityGroup>
<Label>base-64-encoded-name-of-the-service</Label>
</HostedServiceProperties>
<Deployments>
<Deployment>
<Name>deployment-name</Name>
<DeploymentSlot>deployment-slot</DeploymentSlot>
<PrivateID>deployment-id</PrivateID>
<Status>deployment-status</Status>
<Label>base64-encoded-deployment-label</Label>
<Url>deployment-url</Url>
<Configuration>base-64-encoded-configuration-file</Configuration>
<RoleInstanceList>
<RoleInstance>
<RoleName>role-name</RoleName>
<InstanceName>role-instance-name</InstanceName>
<InstanceStatus>instance-status</InstanceStatus>
</RoleInstance>
</RoleInstanceList>
<UpgradeDomainCount>upgrade-domain-count</UpgradeDomainCount>
<RoleList>
<Role>
<RoleName>role-name</RoleName>
<OsVersion>operating-system-version</OsVersion>
</Role>
</RoleList>
<SdkVersion>sdk-version-used-to-create-package</SdkVersion>
<InputEndpointList>
<InputEndpoint>
<RoleName>role-name</RoleName>
<Vip>virtual-ip-address</Vip>
<Port>port-number</Port>
</InputEndpoint>
…
</InputEndpointList>
<Locked>deployment-write-allowed-status</Locked>
<RollbackAllowed>rollback-operation-allowed</RollbackAllowed>
</Deployment>
</Deployments>
</HostedService>
My final question is, there is several repeated tags, such as ,
how can i differentiate between them?
you can use Xml to Linq to parse your xml string. For ex,
var xElem = XElement.Load(new StringReader(xml));
var ns = XNamespace.Get("http://schemas.microsoft.com/windowsazure");
var obj = new
{
ServiceName = xElem.Descendants(ns + "ServiceName").First().Value,
Description = xElem.Descendants(ns + "Description").First().Value,
};
or you can use XmlSerializer
XmlSerializer xs = new XmlSerializer(typeof(HostedService), "http://schemas.microsoft.com/windowsazure");
var obj2 = (HostedService)xs.Deserialize(new StringReader(xml));
public class HostedService
{
public string Url;
public string ServiceName;
public HostedServiceProperties HostedServiceProperties;
}
public class HostedServiceProperties
{
public string Description;
public string Location;
public string AffinityGroup;
public string Label;
}
Maybe you can try samples from XmlDocument ( http://msdn.microsoft.com/en-us/library/d271ytdx.aspx) and and LINQ to XML -( http://msdn.microsoft.com/en-us/library/bb669152.aspx) first and than apply it to your case.

Reading XML File with multiple NS

I am trying to read an XML feed to get the last post date. My xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
<title>mysite</title>
<atom:link href="http://www.mysite.com/news/feed/" rel="self" type="application/rss+xml" />
<link>http://www.mysite.com/news</link>
<description>mysite</description>
<lastBuildDate>Tue, 22 Nov 2011 16:10:27 +0000</lastBuildDate>
<language>en</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>http://wordpress.org/?v=3.0.4</generator>
<item>
<title>My first post!</title>
<link>http://www.mysite.com/news/2011/11/22/docstore-v2-released/</link>
<comments>http://www.mysite.com/news/2011/11/22/docstore-v2-released/#comments</comments>
<pubDate>Tue, 22 Nov 2011 16:10:27 +0000</pubDate>
<dc:creator>mysite</dc:creator>
<category><![CDATA[News]]></category>
<category><![CDATA[Promotions]]></category>
<category><![CDATA[docstore]]></category>
I didn't show all of the xml since it is rather long.
My method, so far, looks like this:
private void button1_Click(object sender, EventArgs e)
{
var XmlDoc = new XmlDocument();
// setup the XML namespace manager
var mgr = new XmlNamespaceManager(XmlDoc.NameTable);
// add the relevant namespaces to the XML namespace manager
mgr.AddNamespace("ns", "http://purl.org/rss/1.0/modules/content/");
var webClient = new WebClient();
var stream = new MemoryStream(webClient.DownloadData("http://www.mysite.com/news/feed/"));
XmlDoc.Load(stream);
// **USE** the XML anemspace in your XPath !!
XmlElement NodePath = (XmlElement)XmlDoc.SelectSingleNode("/ns:Response");
while (NodePath != null)
{
foreach (XmlNode Xml_Node in NodePath)
{
Console.WriteLine(Xml_Node.Name + ": " + Xml_Node.InnerText);
}
}
}
I'm having a problem with it telling me:
Namespace Manager or XsltContext needed. This query has a prefix,
variable, or user-defined function.
All I want to pull out of this xml code is the 'lastBuildDate'. I'm going in circles trying to get this code right.
Can someone tell me what I am doing wrong here?
Thank you!
You're not using the namespace manager.
// **USE** the XML anemspace in your XPath !!
XmlElement NodePath = (XmlElement)XmlDoc.SelectSingleNode("/ns:Response", mgr);
There is only one of the element you are going after, you could go directly to it using the XPath. That element is also in the default namespace, so you do not need to do anything special to get to it. What about:
var XPATH_BUILD_DATE="/rss/channel/lastBuildDate";
private void button1_Click(object sender, EventArgs e){
var xmlDoc = new XmlDocument();
var webClient = new WebClient();
var stream = new MemoryStream(webClient.DownloadData("http://www.mysite.com/news/feed/"));
xmlDoc.Load(stream);
XmlElement xmlNode = (XmlElement)xmlDoc.SelectSingleNode(XPATH_BUILD_DATE);
Console.WriteLine(xmlNode.Name + ": " + xmlNode.InnerText);
}
If you did however need to dig into elements in a different namespace, you can do that also with the XPath (example, getting the dc:creator:
/rss/channel/item[1]/*[local-name() = 'creator']

How to control namespace in .NET SoapFormatter?

I am writing some code that needs to be backwards compatible with EXISTING remoting code that is using SOAP to serialize some objects.
My difficulty is that I have had to move some objects to new assemblies, so the remoting is broken.
For example, I serialize an object using the .NET SoapFormatter like this:
Person p=new Person();
string path=#"c:\myfile.soap";
using (System.IO.FileStream fs = new System.IO.FileStream(path, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
System.Runtime.Serialization.Formatters.Soap.SoapFormatter
f = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
f.Serialize(fs, p);
fs.Close();
}
The resulting xml looks like this:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Person id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/dotneat_net.Serialization%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<FirstName id="ref-3">Joe</FirstName>
<LastName id="ref-4">Doe</LastName>
<_Address id="ref-5">dotneat.net Street, Zaragoza, Spain</_Address>
<_ZIPCode id="ref-6">50007</_ZIPCode>
</a1:Person>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
In that XML, I'd like to have some control over the xmlns on the a1:Person object:
<a1:Person id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/nsassem/Serialization/dotneat_net.Serialization%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
The reason is that my new Person object is not in the same assembly as the original object. So, later when deserialization occurs (in the older projects), it fails because of wrong assembly.
How can I control the text in the xmlns? I have tried a few things such as using the [SoapType Namespace="xxx"] attribute on the class that is being serialized. No luck.
I'd prefer to avoid modifying the XML manually.
I was able to set the namespace using the SoapType attribute.
[Serializable]
[System.Runtime.Remoting.Metadata.SoapType(XmlNamespace = "MY_NAMESPACE")]
public class Person
{
public string FirstName;
public string LastName;
public string _Address;
public string _ZIPCode;
}
This generated the following serialized XML:
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<i2:Person id="ref-1" xmlns:i2="MY_NAMESPACE">
<FirstName id="ref-3">John</FirstName>
<LastName id="ref-4">Doe</LastName>
<_Address id="ref-5">otneat.net Street, Zaragoza, Spain</_Address>
<_ZIPCode id="ref-6">50007</_ZIPCode>
</i2:Person>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
However, I wasn't able to verify the deserialization on the other side of the remoting channel. Hopefully, that helps you.
Have you tried SoapAttribute? It has an XmlNamespace property. May be that will work. You can also use Reflector to see what SoapFormatter.Serialize is doing. You may get some ideas that you can try.
While the solution from Randy does work as expected. I did want to preserve the original a1 notation:
<a1:Person id="ref-1"
So I ended up implementing:
[Serializable]
public class Person : System.Runtime.Serialization.ISerializable
{
public string FirstName;
public string LastName;
public string _Address;
public string _ZIPCode;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AssemblyName = "ConsoleApp";
System.Reflection.MemberInfo[] members = this.GetType().GetMembers();
foreach (var member in members)
{
if (member.MemberType == System.Reflection.MemberTypes.Field)
{
System.Reflection.FieldInfo field = ((System.Reflection.FieldInfo)member);
string name = member.Name;
object value = field.GetValue(this);
info.AddValue(name, value);
}
}
}
}
Turns out there is an even easier solution:
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
soapFormatter.Serialize(fs, p);

Categories