XPath 选择具有命名空间的节点
Posted
技术标签:
【中文标题】XPath 选择具有命名空间的节点【英文标题】:XPath select node with namespace 【发布时间】:2010-10-06 21:24:17 【问题描述】:它是一个 .vbproj,看起来像这样
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>15a7ee82-9020-4fda-a7fb-85a61664692d</ProjectGuid>
我想要得到的只是 ProjectGuid,但是当有命名空间时它不起作用......
Dim xmlDoc As New XmlDocument()
Dim filePath As String = Path.Combine(mDirectory, name + "\" + name + ".vbproj")
xmlDoc.Load(filePath)
Dim value As Object = xmlDoc.SelectNodes("/Project/PropertyGroup/ProjectGuid")
我能做些什么来解决这个问题?
【问题讨论】:
annakata 的解决方案有两个问题:1. 丑陋,2. 在这种情况下,它可以使用,但如果 'ProjectGuid' 元素属于多个命名空间并且我们想要仅来自单个命名空间的元素。使用 NamespaceManager 的解决方案更好 必须为 XPath 引擎提供正确的静态上下文,其中包含前缀和 NS URI 之间的绑定,以便在评估表达式时使用,否则您将无法引用命名空间内的内容。这就是@Teun 所做的。 【参考方案1】:一种方法是使用扩展 + NameSpaceManager。 代码在 VB 中,但很容易转换为 C#。
Imports System.Xml
Imports System.Runtime.CompilerServices
Public Module Extensions_XmlHelper
'XmlDocument Extension for SelectSingleNode
<Extension()>
Public Function _SelectSingleNode(ByVal XmlDoc As XmlDocument, xpath As String) As XmlNode
If XmlDoc Is Nothing Then Return Nothing
Dim nsMgr As XmlNamespaceManager = GetDefaultXmlNamespaceManager(XmlDoc, "x")
Return XmlDoc.SelectSingleNode(GetNewXPath(xpath, "x"), nsMgr)
End Function
'XmlDocument Extension for SelectNodes
<Extension()>
Public Function _SelectNodes(ByVal XmlDoc As XmlDocument, xpath As String) As XmlNodeList
If XmlDoc Is Nothing Then Return Nothing
Dim nsMgr As XmlNamespaceManager = GetDefaultXmlNamespaceManager(XmlDoc, "x")
Return XmlDoc.SelectNodes(GetNewXPath(xpath, "x"), nsMgr)
End Function
Private Function GetDefaultXmlNamespaceManager(ByVal XmlDoc As XmlDocument, DefaultNamespacePrefix As String) As XmlNamespaceManager
Dim nsMgr As New XmlNamespaceManager(XmlDoc.NameTable)
nsMgr.AddNamespace(DefaultNamespacePrefix, XmlDoc.DocumentElement.NamespaceURI)
Return nsMgr
End Function
Private Function GetNewXPath(xpath As String, DefaultNamespacePrefix As String) As String
'Methode 1: The easy way
Return xpath.Replace("/", "/" + DefaultNamespacePrefix + ":")
''Methode 2: Does not change the nodes with existing namespace prefix
'Dim Nodes() As String = xpath.Split("/"c)
'For i As Integer = 0 To Nodes.Length - 1
' 'If xpath starts with "/", don't add DefaultNamespacePrefix to the first empty node (before "/")
' If String.IsNullOrEmpty(Nodes(i)) Then Continue For
' 'Ignore existing namespaces prefixes
' If Nodes(i).Contains(":"c) Then Continue For
' 'Add DefaultNamespacePrefix
' Nodes(i) = DefaultNamespacePrefix + ":" + Nodes(i)
'Next
''Create and return then new xpath
'Return String.Join("/", Nodes)
End Function
End Module
并使用它:
Imports Extensions_XmlHelper
......
Dim FileXMLTextReader As New XmlTextReader(".....")
FileXMLTextReader.WhitespaceHandling = WhitespaceHandling.None
Dim xmlDoc As XmlDocument = xmlDoc.Load(FileXMLTextReader)
FileXMLTextReader.Close()
......
Dim MyNode As XmlNode = xmlDoc._SelectSingleNode("/Document/FirstLevelNode/SecondLevelNode")
Dim MyNode As XmlNodeList = xmlDoc._SelectNodes("/Document/FirstLevelNode/SecondLevelNode")
......
【讨论】:
【参考方案2】:这个问题一直在这里severaltimesalready。
您可以使用与命名空间无关的 XPath 表达式(不推荐使用,因为它笨拙且可能出现误报匹配 - <msb:ProjectGuid>
和 <foo:ProjectGuid>
对于此表达式是相同的):
//*[local-name() = 'ProjectGuid']
或者您做正确的事并使用XmlNamespaceManager
注册命名空间URI,这样您就可以在您的XPath 中包含命名空间前缀:
Dim xmlDoc As New XmlDocument()
xmlDoc.Load(Path.Combine(mDirectory, name, name + ".vbproj"))
Dim nsmgr As New XmlNamespaceManager(xmlDoc.NameTable)
nsmgr.AddNamespace("msb", "http://schemas.microsoft.com/developer/msbuild/2003")
Dim xpath As String = "/msb:Project/msb:PropertyGroup/msb:ProjectGuid"
Dim value As Object = xmlDoc.SelectNodes(xpath, nsmgr)
【讨论】:
【参考方案3】:做这种事情的最好方法(恕我直言)是创建一个命名空间管理器。这可以用来调用 SelectNodes 来指示哪些命名空间 URL 连接到哪些前缀。我通常会设置一个静态属性,它会返回一个像这样的适当实例(它是 C#,你必须翻译):
private static XmlNamespaceManager _nsMgr;
public static XmlNamespaceManager NsMgr
get
if (_nsMgr == null)
_nsMgr = new XmlNamespaceManager(new NameTable());
_nsMgr.AddNamespace("msb", "http://schemas.microsoft.com/developer/msbuild/2003");
return _nsMgr;
我在这里只包含一个命名空间,但您可以有多个。然后你可以像这样从文档中选择:
Dim value As Object = xmlDoc.SelectNodes("/msb:Project/msb:PropertyGroup/msb:ProjectGuid", NsMgr)
请注意,所有元素都在指定的命名空间中。
【讨论】:
您不需要创建新的 XmlDocument 来获取 XmlNameTable。你可以使用 nsMgr = new XmlNamespaceManager(new NameTable()); 啊,谢谢。我从来没有发现如何做到这一点。在 .NET 1.0 中是否已经可以使用 new NameTable()? 从长远来看,从一开始就正确使用命名空间可以节省多少时间。【参考方案4】:您只需注册此 XML 命名空间并与前缀关联,即可使查询正常工作。 选择节点时创建并传递命名空间管理器作为第二个参数:
Dim ns As New XmlNamespaceManager ( xmlDoc.NameTable )
ns.AddNamespace ( "msbuild", "http://schemas.microsoft.com/developer/msbuild/2003" )
Dim value As Object = xmlDoc.SelectNodes("/msbuild:Project/msbuild:PropertyGroup/msbuild:ProjectGuid", ns)
【讨论】:
【参考方案5】:我可能倾向于使用 Bartek 的* 命名空间解决方案,但一般的 xpath 解决方案是:
//*[local-name()='ProjectGuid']
**由于Bartek的回答消失了,我推荐Teun的(其实更彻底)*
【讨论】:
同意,尽管当您必须深入几个级别时,这将成为真正的 PITA。不过,它确实有效。 :) 很好,这就是我选择 Barteks 的原因——唯一阻止我的是,如果我事先不知道命名空间或不能保证,在这种情况下我可能会首先清洗整个文档,但这样说只会让我跟踪者投反对票:) 这有两个问题:1. 丑陋,2. 在这种情况下,它可以使用,但如果 'ProjectGuid' 元素属于多个命名空间并且我们想要这些元素,则会提供错误的结果仅来自单个命名空间。使用 NamespaceManager 的解决方案更好。 我对完全错误的东西投了反对票,虽然不是最好的解决方案,但不是解决方案 不错,这是避免声明无聊且昂贵的命名空间的好方法【参考方案6】:为什么不使用 // 来忽略命名空间:
Dim value As Object = xmlDoc.SelectNodes("//ProjectGuid")
// 充当通配符,跟踪根和指定的下一个节点名称(即 ProjectGuid)之间的所有内容
【讨论】:
实际上不起作用 - 是的,这表示在任何地方寻找任何 ProjectGuid,但它仍然希望它们在默认命名空间中以上是关于XPath 选择具有命名空间的节点的主要内容,如果未能解决你的问题,请参考以下文章