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