在C#中使用带默认命名空间的Xpath

我有一个默认名称空间的XML文档。 我正在使用XPathNavigator来使用Xpathselect一组节点,如下所示:

XmlElement myXML = ...; XPathNavigator navigator = myXML.CreateNavigator(); XPathNodeIterator result = navigator.Select("/outerelement/innerelement"); 

我没有得到任何结果:我假设这是因为我没有指定命名空间。 我如何在我的select中包含命名空间?

首先 – 你不需要导航仪; SelectNodes / SelectSingleNode就足够了。

但是,您可能需要一个名称空间pipe理器,例如:

 XmlElement el = ...; //TODO XmlNamespaceManager nsmgr = new XmlNamespaceManager( el.OwnerDocument.NameTable); nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI); var nodes = el.SelectNodes(@"/x:outerelement/x:innerelement", nsmgr); 

您可能想要尝试使用XPath Visualizer工具来帮助您完成任务。

XPathVisualizer是免费的,易于使用。

替代文字

重要提示:如果您使用的是Windows 7/8并没有看到文件,编辑和帮助菜单项目,请按ALT键。

对于任何寻找快速入侵解决scheme的人来说,特别是在那些你知道 XML并且不需要担心命名空间的情况下,你可以通过简单地读取文件到一个string来解决这个烦人的小“function”replace冒犯性的属性:

 XmlDocument doc = new XmlDocument(); string fileData = File.ReadAllText(fileName); fileData = fileData.Replace(" xmlns=\"", " whocares=\""); using (StringReader sr = new StringReader(fileData)) { doc.Load(sr); } XmlNodeList nodeList = doc.SelectNodes("project/property"); 

当我处理单个文件时,我发现这比其他所有需要前缀默认名称空间的非义务都容易。 希望这可以帮助。

在XML中使用带有命名空间的.NET(通过导航器或SelectNodes / SelectSingleNode)使用XPath时,需要:

  • 提供你自己的XmlNamespaceManager

  • 明确地在名称空间中的XPathexpression式中的所有元素前缀。

后者是(从MS源代码链接下面):因为XPath 1.0忽略默认命名空间规范(xmlns =“some_namespace”)。 所以当你使用没有前缀的元素名称时,它假定为空名称空间。

这就是为什么.NET的XPath实现会忽略XmlNamespaceManager中前缀为String.Empty的名称空间,而allways使用空名称空间。

有关更多信息,请参阅XmlNamespaceManager和UndefinedXsltContext不处理默认名称空间 。

我发现这个“特性”非常不方便,因为你不能通过简单地添加默认名称空间声明来使旧的XPath名称空间感知,但这就是它的工作原理。

您可以使用XPath语句而不使用XmlNamespaceManager,如下所示:

 ... navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]") ... 

这是使用默认名称空间定义的XML内select元素的简单方法。

重点是使用:

 namespace-uri() = '' 

它将使用默认名称空间而不使用前缀来创build元素。

如果命名空间不同于独立单元和不独立单元

 XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable); manager.AddNamespace("o", "namespaceforOuterElement"); manager.AddNamespace("i", "namespaceforInnerElement"); string xpath = @"/o:outerelement/i:innerelement" // For single node value selection XPathExpression xPathExpression = navigator.Compile(xpath ); string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText; // For multiple node selection XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager); 

我遇到了一个空白默认命名空间类似的问题。 在这个例子中,我有一个名称空间前缀元素和一个元素(DataBlock)的混合,没有:

 <src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b"> <DataBlock> <a:DocID> <a:IdID>7</a:IdID> </a:DocID> <b:Supplimental> <b:Data1>Value</b:Data1> <b:Data2/> <b:Extra1> <b:More1>Value</b:More1> </b:Extra1> </b:Supplimental> </DataBlock> </src:SRCExample> 

我试图使用在XPath Visualizer中工作的XPath,但没有在我的代码中工作:

  XmlDocument doc = new XmlDocument(); doc.Load( textBox1.Text ); XPathNavigator nav = doc.DocumentElement.CreateNavigator(); XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable ); foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) { nsman.AddNamespace( nskvp.Key, nskvp.Value ); } XPathNodeIterator nodes; XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" ); failingexpr.SetContext( nsman ); nodes = nav.Select( failingexpr ); while ( nodes.MoveNext() ) { string testvalue = nodes.Current.Value; } 

我将其缩小到XPath的“DataBlock”元素,但不能使其工作,除非通过简单地通配DataBlock元素:

  XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" ); failingexpr.SetContext( nsman ); nodes = nav.Select( failingexpr ); while ( nodes.MoveNext() ) { string testvalue = nodes.Current.Value; } 

在经历了很多头痛的事情之后,我决定直接在我的XmlNamespaceManager加载器中处理默认的命名空间,方法是将其更改为:

  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) { nsman.AddNamespace( nskvp.Key, nskvp.Value ); if ( nskvp.Key == "" ) { nsman.AddNamespace( "default", nskvp.Value ); } } 

所以现在“默认”和“”指向相同的命名空间。 一旦我这样做了,XPath“/ src:SRCExample / default:DataBlock / a:DocID / a:IdID”就像我想要的那样返回了我的结果。 希望这有助于为他人澄清问题。

在我的情况下,添加一个前缀是不实际的。 太多的xml或xpath是在运行时确定的。 最终我扩展了XmlNode的方法。 这还没有被优化的性能,它可能不处理每一个案件,但目前为止我的工作。

  public static class XmlExtenders { public static XmlNode SelectFirstNode(this XmlNode node, string xPath) { const string prefix = "pfx"; XmlNamespaceManager nsmgr = GetNsmgr(node, prefix); string prefixedPath = GetPrefixedPath(xPath, prefix); return node.SelectSingleNode(prefixedPath, nsmgr); } public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath) { const string prefix = "pfx"; XmlNamespaceManager nsmgr = GetNsmgr(node, prefix); string prefixedPath = GetPrefixedPath(xPath, prefix); return node.SelectNodes(prefixedPath, nsmgr); } public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix) { string namespaceUri; XmlNameTable nameTable; if (node is XmlDocument) { nameTable = ((XmlDocument) node).NameTable; namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI; } else { nameTable = node.OwnerDocument.NameTable; namespaceUri = node.NamespaceURI; } XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable); nsmgr.AddNamespace(prefix, namespaceUri); return nsmgr; } public static string GetPrefixedPath(string xPath, string prefix) { char[] validLeadCharacters = "@/".ToCharArray(); char[] quoteChars = "\'\"".ToCharArray(); List<string> pathParts = xPath.Split("/".ToCharArray()).ToList(); string result = string.Join("/", pathParts.Select( x => (string.IsNullOrEmpty(x) || x.IndexOfAny(validLeadCharacters) == 0 || (x.IndexOf(':') > 0 && (x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':')))) ? x : prefix + ":" + x).ToArray()); return result; } } 

然后在你的代码中使用类似的东西

  XmlDocument document = new XmlDocument(); document.Load(pathToFile); XmlNode node = document.SelectFirstNode("/rootTag/subTag"); 

希望这可以帮助

我的回答扩展了布兰登以前的答案。 我用他的例子来创build一个扩展方法如下:

 static public class XmlDocumentExt { static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd) { XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable); XPathNavigator nav = xd.DocumentElement.CreateNavigator(); foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All)) { string sKey = kvp.Key; if (sKey == "") { sKey = "default"; } nmsp.AddNamespace(sKey, kvp.Value); } return nmsp; } } 

然后在我的XMLparsing代码中,我只添加一行:

 XmlDocument xdCandidate = new XmlDocument(); xdCandidate.Load(sCandidateFile); XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr(); // 1-line addition XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp); 

我真的很喜欢这种方法,因为从源XML文件加载名称空间方面它是完全dynamic的,并且不完全忽略XML名称空间的概念,所以这可以与需要多个名称空间以解除冲突的XML一起使用。

我使用了上面描述的SpikeDog所描述的有用的方法。 它工作得很好,直到我扔了一个XPathexpression式,它使用pipe道来组合多个path。

所以我用正则expression式重写了它,并认为我会分享:

 public string HackXPath(string xpath_, string prefix_) { return System.Text.RegularExpressions.Regex.Replace(xpath_, @"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[@|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x => { int expressionIndex = x.Groups["Expression"].Index - x.Index; string before = x.Value.Substring(0, expressionIndex); string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex); return String.Format("{0}{1}:{2}", before, prefix_, after); }); } 

或者,如果有人应该像我一样使用XPathDocument:

 XPathDocument xdoc = new XPathDocument(file); XPathNavigator nav = xdoc.CreateNavigator(); XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable); nsmgr.AddNamespace("y", "http://schemas.microsoft.com/developer/msbuild/2003"); XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr); 

在这种情况下,这可能是导致问题的命名空间parsing,但也可能是您的XPathexpression式本身不正确。 你可能想先评估一下。

这是使用XPathNavigator的代码。

 //xNav is the created XPathNavigator. XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable); mgr.AddNamespace("prefix", "http://tempuri.org/"); XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);