C#/XML:XPathNavigator.SelectSingleNode() 始终返回 null

Posted

技术标签:

【中文标题】C#/XML:XPathNavigator.SelectSingleNode() 始终返回 null【英文标题】:C#/XML: XPathNavigator.SelectSingleNode() always returns null 【发布时间】:2021-06-16 15:52:35 【问题描述】:

我正在尝试将 WebDAV 客户端集成到一些更大的工具套件中,以便能够从我的软件在用户现有日历中创建事件/通知。我的项目是一个用 c# 编写的 WPF 应用程序。

我已经设置了一个带有可用 WebDAV 接口/api 的日历,现在我尝试读取日历的 ctag 属性。当发送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>

我收到包含以下内容的 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>

我知道命名空间可能看起来有点可疑,有些带有斜杠 /,有些没有斜杠 d,即使带有尾随冒号 :,但这正是我从服务器得到的。例如,如果我将请求中的命名空间 xmlns:d="DAV:" 更改为 xmlns:d="DAV",我会得到响应状态 500: InternalServerError,因此我将命名空间声明与响应中的完全相同。

现在,我想从cs:getctag 节点获取值。问题是,在浏览 xml 结构时,我尝试的所有内容总是返回 null

为了澄清:response.Content.ReadAsStringAsync().Result 返回前面提到的 response xml 字符串。

第一次尝试:在 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);

节点root 正确设置为元素&lt;d:multistatus&gt;,但在下一行应该选择ctagNode 时,代码会引发异常:

System.NullReferenceException: Object reference not set to an instance of an object.

第二次尝试:获取带有 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;

XPathNavigator ctagElement = myNavigator.SelectSingleNode(query, nsManager);执行后,对象ctagElement仍为null。

谁能指出我在这两种情况下做错了什么(1-Bare xml,2-XPath)以及如何正确地做?

我将不胜感激能帮助我解决这个问题的答案,并且通常能帮助我理解如何正确地在 xml 数据中导航。欢迎您也链接到综合文档或教程。

【问题讨论】:

这能回答你的问题吗? XmlDocument - SelectSingleNode with namespace 您创建了一个XmlNamespaceManager,但您没有使用。也请get rid.Result @GSerg 重新启动 Visual Studio 后,第二个版本 (XPath) 工作正常。可能是一些编译打嗝。 --- 如果我将 root["response", "d"] 更改为 root["response", xmlNamespaceManager.LookupNamespace("d")],裸 xml 解决方案将起作用 感谢您指出,命名空间不会自动查找,我必须明确使用 ns 管理器 【参考方案1】:

正如@GSerg 在他对我的问题的评论中指出的那样,我确实没有使用我在 First Try 解决方案中创建的XmlNamespaceManager

事实证明,在我的代码示例中只是一个小错误:

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);

看起来像语法

XmlNode childNode = parentNode["nameOfChildNode", "namespaceOfChildNode"]

需要完整的命名空间,而不是命名空间前缀。

至于我的第二次尝试,我已经使用了命名空间管理器,并且代码在 VisualStudio 重新启动和解决方案重建后工作。无需更改代码。

谢谢@GSerg :-)

【讨论】:

【参考方案2】:

尝试以下:

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; 
    

【讨论】:

以上是关于C#/XML:XPathNavigator.SelectSingleNode() 始终返回 null的主要内容,如果未能解决你的问题,请参考以下文章

C语言xml解析

如何使用目标 c 从 xml 信封中提取 xml 肥皂体?

c/c++转化为XML存储????

用C语言读取xml文件,怎么实现?

复习一下xml(c)

xml 使用XML和C#的WPF按钮