2010-03-26 79 views
30

什么是XPath(从C#API到XDocument.XPathSelectElements(xpath,nsman)如果很重要)查询此文档中的所有MyNode?如何在没有前缀的默认名称空间中使用XPath?

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <MyNode xmlns="lcmp" attr="true"> 
    <subnode /> 
    </MyNode> 
</configuration> 
  • 我试图/configuration/MyNode这是错误的,因为它忽略了命名空间。
  • 我试过/configuration/lcmp:MyNode这是错误的,因为lcmp是URI,而不是前缀。
  • 我试图/configuration/{lcmp}MyNode其失败的原因Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

编辑:我不能使用mgr.AddNamespace("df", "lcmp");因为一些回答者建议。这就要求XML解析程序知道我计划提前使用的所有命名空间。由于这是为了适用于任何源文件,我不知道哪些名称空间需要手动添加前缀。看起来好像{my uri}是XPath语法,但是微软并没有打算实现那个......真的?

+0

目前尚不清楚您想要达到的目标。确定您正在寻找哪个节点的标准是什么?你在寻找基于他们名字空间的元素吗?在这种情况下,你的代码会知道命名空间。 至于{my uri}是“XPath语法”,您认为XPath 1.0规范中的语法已被定义?无论您将名称空间URI放在大括号中还是将名称空间URI传递给AddNamespace方法都不会影响您的C#代码,在这两种情况下,名称空间URI都需要以字符串形式提供。 – 2010-03-27 11:36:38

+0

@Martin:我想在XPath中指定名称空间,但我只有名称空间URI和没有名称空间前缀。我仔细观察了我发明'{}'的位置,并且我可能错误地剔除了...我从这个参考文献中获得了它:http://www.jclark.com/xml/xmlns.htm。感谢您指出了这一点。当然,即使它不是有效的,它似乎是一个有用的事情,可以轻松完成......;) – 2010-03-27 17:23:37

+0

Scott,您需要选择任何允许的前缀,将其与您使用AddNamespace的名称空间URI相关联(前缀,namespaceURI),并在您的XPath表达式中使用选择的前缀。这就是XPath的工作方式,至少XPath 1.0。前缀不必在输入XML中存在,或者可以不同于输入XML中使用的前缀,元素选择将根据命名空间匹配发生,而不是前缀。 – 2010-03-27 17:45:59

回答

34

configuration元素位于未命名的名称空间中,而MyNode绑定到名称空间没有名称空间前缀的lcmp

XPATH声明将允许您解决MyNode元素,而不必宣布lcmp命名空间或在您的XPATH使用一个命名空间前缀:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode'] 

这即是configuration和一个孩子的任何元素相匹配然后使用具有namespace-uri()local-name()函数的谓词归档器将其限制为MyNode元素。

如果您不知道哪些命名空间的URI将被用于元件做,那么你可以让XPATH更通用的,只是比赛的local-name()

/configuration/*[local-name()='MyNode'] 

然而,在运行匹配使用相同名称的不同词汇表(绑定到不同名称空间的URI)中的不同元素的风险。

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

参见:

+0

@Mads:啊,有趣的是,我不知道“[namespace-uri()='lcmp'”的语法......应该可以工作,如果可以的话(我会在星期一尝试)作为答案。你知道“/ configuration/{lcmp} MyNode”实际上是否正确,并且不被C#支持? – 2010-03-27 17:16:21

+0

@Scott不,您尝试使用的语法不是有效的XPATH语句,并且在我意识到的任何实现中不受支持。虽然它可能会扩展到QName,但您无法在XPATH语句中以这种方式解决该问题。 – 2010-03-27 18:04:30

+0

像魅力一样工作,非常感谢。 – 2010-03-29 13:55:31

12

您需要使用的XmlNamespaceManager如下:

XDocument doc = XDocument.Load(@"..\..\XMLFile1.xml"); 
    XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable()); 
    mgr.AddNamespace("df", "lcmp"); 
    foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr)) 
    { 
     Console.WriteLine(myNode.Attribute("attr").Value); 
    } 
+2

是的,我认为这样会工作,但我做不到。由于XML解析代码与实际的XML文件及其使用的任何名称空间无关,因此mgr.AddNamespace(“df”,“lcmp”);是一个不可能的行... – 2010-03-26 18:11:07

+2

但是你解析代码不能不知道元素名称,对不对?命名空间被认为是名称的一部分,所以忽略它是有点不好的设计,但如果你确定不会有名称空间冲突,你可以做一些类似于“配置/ * [local-name()='MyNode']” – 2010-03-26 18:19:39

+0

斯科特,请解释你的代码应该如何识别元素,如果名称空间URI不知道?什么是你的代码,确切地说,在任何命名空间中使用本地名称“MyNode”的元素?然后使用奥列格的建议。否则更详细地解释你正在寻找什么元素。 – 2010-03-26 18:26:34

4

下面是如何使命名空间提供给XPath表达式在 XPathSelectElements扩展方法的例子:

using System; 
using System.Xml.Linq; 
using System.Xml.XPath; 
using System.Xml; 
namespace XPathExpt 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
    XElement cfg = XElement.Parse(
     @"<configuration> 
      <MyNode xmlns=""lcmp"" attr=""true""> 
      <subnode /> 
      </MyNode> 
     </configuration>"); 
    XmlNameTable nameTable = new NameTable(); 
    var nsMgr = new XmlNamespaceManager(nameTable); 
    // Tell the namespace manager about the namespace 
    // of interest (lcmp), and give it a prefix (pfx) that we'll 
    // use to refer to it in XPath expressions. 
    // Note that the prefix choice is pretty arbitrary at 
    // this point. 
    nsMgr.AddNamespace("pfx", "lcmp"); 
    foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr)) 
    { 
     Console.WriteLine("Found element named {0}", el.Name); 
    } 
    } 
} 
} 
+0

@Dan:是的,我认为这是可行的,但需要对所使用的命名空间进行硬编码。而我只能控制XPath,请参阅@Martin Honnen的回答。 – 2010-03-26 18:12:24

5

XPath是(故意)不适用于您希望对只存在于XML文档中的某些未知名称空间使用相同的XPath表达式的情况。您需要提前知道名称空间,将名称空间声明为XPath处理器,并在表达式中使用该名称。 Martin和Dan的答案显示了如何在C#中完成此操作。

的原因难度最好在XML namespaces规格表示:

我们设想可扩展标记语言(XML),其中一个XML文档可能包含的元素和属性(这里被称为应用“标记词汇表“),它们是为多个软件模块定义和使用的。其中一个动机就是模块化:如果存在这样一个已被很好理解并且有可用软件的标记词汇,最好重新使用这个标记而不是重新发明它。

这种包含多个标记词汇表的文档带来了识别和碰撞的问题。软件模块需要能够识别它们设计处理的元素和属性,即使在面向其他软件包的标记使用相同元素名称或属性名称时出现“冲突”时也是如此。

这些考虑因素要求文档结构应该有名称构造,以避免来自不同标记词汇表的名称之间的冲突。本规范描述了一种机制,即XML名称空间,它通过将扩展名称分配给元素和属性来完成此操作。

也就是说,命名空间都应该被用来确保你知道你的文件正在谈论:是<head>元素谈论序言XHTML文档或大人物领导一个AnatomyML文档中?你永远不会被认为对命名空间不可知,它几乎是你应该在任何XML词汇表中定义的第一件事情。

应该可以做你想做的事,但我不认为它可以在单个XPath表达式中完成。首先,您需要在文档中搜索并提取所有的namespaceURI,然后将它们添加到命名空间管理器中,然后运行您想要的实际XPath表达式(并且您需要知道有关文档中名称空间在此处的分布情况点,或者你有很多表达式运行)。我认为你可能最好使用XPath之外的东西(例如类似DOM或SAX的API)来查找名称空间URI,但是您也可以探索XPath名称空间轴(在XPath 1.0中),使用函数(在XPath 2.0 )或使用Oleg的"configuration/*[local-name() = 'MyNode']"等表达式。无论如何,我认为你最好的选择是尽量避免编写名称空间不可知的XPath!为什么你提前不知道你的名字空间?你如何避免匹配你不想匹配的东西?

编辑 - 你知道namespaceURI吗?

所以事实证明,你的问题困扰了我们所有人。显然你知道命名空间URI,但是你不知道在XML文档中使用的命名空间前缀。的确,在这种情况下,没有使用名称空间前缀,并且URI成为定义它的默认namspace。关键要知道的是,选择的前缀(或缺少前缀)与您的XPath表达式(以及一般的XML解析)无关。当文档被表示为文本时,前缀/ xmlns属性只是将节点与名称空间URI相关联的一种方式。你可能想看看this answer,我试着澄清命名空间前缀。

您应该尝试用解析器认为的方式来思考XML文档 - 每个节点都有一个名称空间URI和一个本地名称。命名空间前缀/继承规则只是保存了很多次输入的URI。写下这种情况的一种方法是使用Clark记法:也就是说,你写了LocalNodeName,但这种记法通常只是用于文档 - XPath对这种记法一无所知。

相反,XPath使用自己的名称空间前缀。例如/ns1:root/ns2:node。但是这些与原始XML文档中可能使用的任何前缀完全分离,并且与此无关。任何XPath实现都会有一种方法将它自己的前缀映射到名称空间URI。对于C#实现,您使用XmlNamespaceManager,在Perl中提供了一个散列,xmllint使用命令行参数......因此,您只需为知道的名称空间URI创建一些任意前缀,然后在XPath表达式中使用此前缀。使用什么前缀并不重要,在XML中您只关心URI和localName的组合。

要记住的另一件事(通常是一个惊喜)是XPath不会执行名称空间继承。无论名称空间来自继承,xmlns属性还是命名空间前缀,都需要为每个具有名称空间的前缀添加前缀。此外,尽管您应该始终以URI和localNames的方式来思考,但也有从XML文档访问前缀的方法。很少有必要使用这些。

+0

@Andrew:我确实知道命名空间,可以将它放入XPath中。我不知道的是命名空间前缀,当你说“/ configuration/lcmp:MyNode”时,这就是使用的名称空间前缀。 “/ configuration/{lcmp} MyNode”似乎是使用名称空间URI而不是前缀的恰当语法,但C#似乎不支持{}表示法。我没有前缀。 – 2010-03-27 17:14:00

+0

啊,我明白了。我将写一个新的答案 - 基本上你只需要知道XML文档中的命名空间前缀与XPath表达式中的命名空间前缀没有任何共同之处,除非它们都映射到相同的nsURI。 – 2010-03-27 19:18:29

+0

非常丰富和详细的编辑写作,但我不认为它实际上解决我的问题,即:XPath找到该节点?另外,你是否说如果XML DID指定了前缀(它没有),那么XPath查询找不到它? – 2010-03-29 13:51:12

0

我喜欢@ MADS-汉森,他的回答,这么好,我写了这些通用实用类成员:

/// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <returns></returns> 
    public static string GetLocalNameXPathQuery(string childElementName) 
    { 
     return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null); 
    } 

    /// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <returns></returns> 
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName) 
    { 
     return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null); 
    } 

    /// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <param name="childAttributeName">Name of the child attribute.</param> 
    /// <returns></returns> 
    /// <remarks> 
    /// This routine is useful when namespace-resolving is not desirable or available. 
    /// </remarks> 
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName) 
    { 
     if (string.IsNullOrEmpty(childElementName)) return null; 

     if (string.IsNullOrEmpty(childAttributeName)) 
     { 
      return string.IsNullOrEmpty(namespacePrefixOrUri) ? 
       string.Format("./*[local-name()='{0}']", childElementName) 
       : 
       string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']", namespacePrefixOrUri, childElementName); 
     } 
     else 
     { 
      return string.IsNullOrEmpty(namespacePrefixOrUri) ? 
       string.Format("./*[local-name()='{0}']/@{1}", childElementName, childAttributeName) 
       : 
       string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName); 
     } 
    }