[英]C#/XML: XPathNavigator.SelectSingleNode() always returns null
I'm trying to integrate a WebDAV client into some bigger tool suite to be able to create events/notifications from my software in the users existing calendar.我正在尝试将 WebDAV 客户端集成到一些更大的工具套件中,以便能够从我的软件在用户现有日历中创建事件/通知。 My project is a WPF application written in c#.我的项目是用 c# 编写的 WPF 应用程序。
I have set up a calendar with a WebDAV interface/api available and now I try to read the ctag
property of the calendar.我已经设置了一个带有可用 WebDAV 接口/api 的日历,现在我尝试读取日历的ctag
属性。 When sending the PROPFIND
http request发送PROPFIND
http请求时
<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\">
<d:prop>
<d:displayname/>
<cs:getctag/>
</d:prop>
</d:propfind>
I receive a http response with the following content:我收到 http响应,其中包含以下内容:
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:" xmlns:nmm="http://all-inkl.com/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
<d:response>
<d:href>/calendars/cal0015dc8/1/</d:href>
<d:propstat>
<d:prop>
<d:displayname>My Calendar Name</d:displayname>
<cs:getctag>0</cs:getctag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
I know that the namespaces might look a little suspicious, some with and some without a trailing slash /
, namespace d
even with a trailing colon :
, but this is exactly what I get from the server.我知道名称空间可能看起来有点可疑,有些带有斜杠 / ,有些没有斜杠/
,名称空间d
甚至带有尾随冒号:
,但这正是我从服务器得到的。 If I for example change the namespace xmlns:d="DAV:"
in my request to xmlns:d="DAV"
, I get a response status 500: InternalServerError
, so I took the namespace declarations exactly as they are in the response.例如,如果我将请求中的命名空间xmlns:d="DAV:"
更改为xmlns:d="DAV"
,我会得到响应状态500: InternalServerError
,因此我将命名空间声明与响应中的完全一致。
Now, I want to get the value from the cs:getctag
node.现在,我想从cs:getctag
节点获取值。 Problem is, everything I tried always returns null
when navigating through the xml structure.问题是,在浏览 xml 结构时,我尝试的所有操作总是返回null
。
For clarification: response.Content.ReadAsStringAsync().Result
returns the afore mentioned response xml string.澄清一下: response.Content.ReadAsStringAsync().Result
返回上述响应xml 字符串。
First try: Load response in a XmlDocument and access the subnodes by namespace/name combination:第一次尝试:在 XmlDocument 中加载响应并通过命名空间/名称组合访问子节点:
using System.Xml;
XmlDocument doc = new XmlDocument();
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable);
xmlNamespaceManager.AddNamespace("d", "DAV:");
xmlNamespaceManager.AddNamespace("nmm", "http://all-inkl.com/ns");
xmlNamespaceManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
xmlNamespaceManager.AddNamespace("cs", "http://calendarserver.org/ns/");
doc.LoadXml(response.Content.ReadAsStringAsync().Result);
XmlNode root = doc.DocumentElement;
XmlNode ctagNode = root["response", "d"]["propstat", "d"]["prop", "d"]["getctag", "cs"];
ctag = Convert.ToInt64(ctagNode.InnerText);
The node root
is correctly set to element <d:multistatus>
, but in the next line, where ctagNode
should get selected, the code throws an exception:节点root
被正确设置为元素<d:multistatus>
,但在下一行,应该选择ctagNode
的地方,代码抛出异常:
System.NullReferenceException: Object reference not set to an instance of an object.
Second Try: Get the node with a XPath selection第二次尝试:获取具有 XPath 选择的节点
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
XmlReader xmlReader = XmlReader.Create(new StringReader(response.Content.ReadAsStringAsync().Result));
XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlReader.NameTable);
nsManager.AddNamespace("d", "DAV:");
nsManager.AddNamespace("nmm", "http://all-inkl.com/ns");
nsManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
nsManager.AddNamespace("cs", "http://calendarserver.org/ns/");
XDocument myXDocument = XDocument.Load(xmlReader);
XPathNavigator myNavigator = myXDocument.CreateNavigator();
string query = "//d:multistatus/d:response/d:propstat/d:prop/cs:getctag";
XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);
ctag = ctagElement.ValueAsLong;
After the execution of XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);
XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);
, the object ctagElement
is still null. ,object ctagElement
仍然是 null。
Can someone point out what I'm doing wrong in either case (1-Bare xml, 2-XPath) and how to do it right?有人可以指出我在这两种情况下做错了什么(1-Bare xml,2-XPath)以及如何做对吗?
I would appreciate answers that help me solve this problem and that generally help me understand how to correctly navigate in xml data.我很感激能帮助我解决这个问题的答案,并且通常能帮助我理解如何在 xml 数据中正确导航。 You're welcome to also link to a comprehensive documentation or tutorial.欢迎您也链接到综合文档或教程。
As @GSerg pointed out in his comment to my question, I was indeed not using the XmlNamespaceManager
I have created in my First Try solution.正如@GSerg 在他对我的问题的评论中指出的那样,我确实没有使用我在First Try解决方案中创建的XmlNamespaceManager
。
As it turns out, in my code example was just one small mistake:事实证明,在我的代码示例中只是一个小错误:
using System.Xml;
XmlDocument doc = new XmlDocument();
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable);
xmlNamespaceManager.AddNamespace("d", "DAV:");
xmlNamespaceManager.AddNamespace("nmm", "http://all-inkl.com/ns");
xmlNamespaceManager.AddNamespace("cal", "urn:ietf:params:xml:ns:caldav");
xmlNamespaceManager.AddNamespace("cs", "http://calendarserver.org/ns/");
doc.LoadXml(response.Content.ReadAsStringAsync().Result);
XmlNode root = doc.DocumentElement;
// THIS LINE WAS WRONG
XmlNode ctagNode = root["response", "d"]
["propstat", "d"]
["prop", "d"]
["getctag", "cs"];
// IT SHOULD LOOK LIKE THIS:
XmlNode ctagNode = root["response", xmlNamespaceManager.LookupNamespace("d")]
["propstat", xmlNamespaceManager.LookupNamespace("d")]
["prop", xmlNamespaceManager.LookupNamespace("d")]
["getctag", xmlNamespaceManager.LookupNamespace("cs")];
ctag = Convert.ToInt64(ctagNode.InnerText);
Looks like the syntax看起来像语法
XmlNode childNode = parentNode["nameOfChildNode", "namespaceOfChildNode"]
requires the full namespace, not the namespace prefix.需要完整的命名空间,而不是命名空间前缀。
As for my Second Try , I already used the namespace manager and the code worked after a VisualStudio restart and solution rebuild.至于我的Second Try ,我已经使用了命名空间管理器,并且代码在 VisualStudio 重新启动和解决方案重建后工作。 No code change required.无需更改代码。
Thank you @GSerg:-)谢谢@GSerg :-)
Try following:尝试以下操作:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication186
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
StringReader sReader = new StringReader(xml);
XmlReader xReader = XmlReader.Create(sReader);
XmlSerializer serializaer = new XmlSerializer(typeof(MultiStatus));
MultiStatus multiStatus = (MultiStatus)serializaer.Deserialize(xReader);
}
}
[XmlRoot(ElementName = "multistatus", Namespace = "DAV:")]
public class MultiStatus
{
[XmlElement(Namespace = "DAV:")]
public Response response { get; set; }
}
public class Response
{
[XmlElement(Namespace = "DAV:")]
public string href { get; set; }
[XmlElement(ElementName = "propstat", Namespace = "DAV:")]
public Propstat propstat { get; set; }
}
public class Propstat
{
[XmlElement(ElementName = "prop", Namespace = "DAV:")]
public Prop prop { get; set; }
[XmlElement(ElementName = "status", Namespace = "DAV:")]
public string status { get; set; }
}
public class Prop
{
[XmlElement(Namespace = "DAV:")]
public string displayname { get; set; }
[XmlElement(Namespace = "http://calendarserver.org/ns/")]
public string getctag { get; set; }
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.