C# 获取当前节点的完整路径
如果节点上有一个XPathNavigator,如何从根节点获取表示该节点路径的XPath表达式 例如,如果XML为:C# 获取当前节点的完整路径,c#,.net,xpath,C#,.net,Xpath,如果节点上有一个XPathNavigator,如何从根节点获取表示该节点路径的XPath表达式 例如,如果XML为: <data> <class name='dogs'> <item name='doberman /> <item name='husky' /> </class> <class name='cats'> <item name='pe
<data>
<class name='dogs'>
<item name='doberman />
<item name='husky' />
</class>
<class name='cats'>
<item name='persian' />
<item name='tabby' />
</class> </data>
</data>
…那么波斯猫的路径可以表示为/data/class[2]/item[1]
我可以使用SelectFounders枚举相关节点的祖先,也可以使用SelectParent迭代爬升父关系,但这无法获取位置信息
我是否必须使用每个祖先的位置来评估XPath,或者是否有更好的方法来执行此操作?未经测试;仅适用于从XmlDocument对象创建的XPathNavigator对象:
private static string GetPath(this XPathNavigator navigator)
{
StringBuilder path = new StringBuilder();
for (XmlNode node = navigator.UnderlyingObject as XmlNode; node != null; node = node.ParentNode)
{
string append = "/" + path;
if (node.ParentNode != null && node.ParentNode.ChildNodes.Count > 1)
{
append += "[";
int index = 1;
while (node.PreviousSibling != null)
{
index++;
}
append += "]";
}
path.Insert(0, append);
}
return path.ToString();
}
以下是您将如何使用它:
XPathNavigator navigator = /* ... */;
string path = navigator.GetPath();
然而
XPathNavigator对象通常位于根节点上。创建它们后,它们的位置将无法更改,但您可以使用它们来选择子体。也许有办法完全避免这个问题?例如,如果您只需要当前节点,可以使用XPathNavigator.UnderlineObject,如示例中所示。假设您只对xml元素的xpath感兴趣,我实现了一个蛮力算法,即在XmlElement上作为扩展方法遍历xml结构。这与@Zenexer的答案非常相似,尽管当他发布自己的答案时,我已经开始了自己的版本 另外,我对Alexei关于性能的提示很感兴趣,我使用一个稍微复杂的XML文件创建了一种测试用例。然后我实现了相同算法的两个版本;一个依赖于以前的同级,另一个按顺序迭代节点。第三个版本依赖于XPath的position函数,但它没有按预期工作,因此被丢弃 虽然您应该自己检查,但在我的机器中,结果显示迭代版本具有显著的性能优势—1.7秒,而兄弟版本的分数为21秒 ImportStart:这些扩展方法在静态类XmlElementExtension中声明 以前的兄弟版本 迭代版本 测试用例
ParentNode的简单解决方案。只需向上移动,直到到达根节点并记住传递的每个节点名称。测试
// Get the node full path
static string getPath(XmlNode node)
{
string path = node.Name;
XmlNode search = null;
// Get up until ROOT
while ((search = node.ParentNode).NodeType != XmlNodeType.Document)
{
path = search.Name + "/" + path; // Add to path
node = search;
}
return "//"+path;
}
这是我的XPathNavigator解决方案,它是从XPathDocument创建的。它迭代到根节点,同时检查其上面是否有同级的路径中的每个节点
//returns full xpath, can make it extension method
protected string GetFullXpath(XPathNavigator navigator)
{
var firstNodeEscape = navigator.NodeType == XPathNodeType.Attribute
? "/@"
: "/";
string path = string.Concat(firstNodeEscape, $"{navigator.Name}{GetXmlArrayIndex(navigator)}");
while (navigator.MoveToParent())
{
path = string.Concat($"/{navigator.Name}{GetXmlArrayIndex(navigator)}", path);
}
return path;
}
//returns index of element in array, used inside GetFullXpath method to add node position in array to path
private string GetXmlArrayIndex(XPathNavigator navigator)
{
var backwardsNavigator = navigator.Clone();
var elementsBeforeCount = 0;
while (backwardsNavigator.MoveToPrevious()) //true while has siblings
{
if (backwardsNavigator.NodeType != XPathNodeType.Element) continue;
if (backwardsNavigator.Name == navigator.Name) elementsBeforeCount++;
else break;
}
return elementsBeforeCount > 0
? $"[{++elementsBeforeCount}]" //++ because xpath array indexes are 1-based
: string.Empty;
}
你为什么要这么做?XPathNavigator对象通常位于根目录上。一旦它们被创建,它们的位置就无法改变。也许有办法解决这个问题?@Zenexer:如果我使用CreateNavigator.Selectexpr,那么它将返回一个XPathNodeIterator。。。但是XPathNodeIterator为每个选定的节点提供了一个XPathNavigator,位于所述节点上。+1。位置XPath是一种方法。请注意,根据节点的实现,如果需要将所有子节点带到当前子节点,则以前的同级节点可能会非常慢。考虑向前走到当前节点。注意2:小心使用属性,因为它们没有位置-0,只有名称。@Zenexer:谢谢,但我处理的是一个XPathDocument。如果我意识到这会有所不同,我会提到这一点。抱歉。@Zenexer:在什么情况下node.ParentNode.ChildNodes.Count会为零?这似乎是一个矛盾…@GaryMcGill不会的。但它可能是1。这就是测试内容。@MickyD这只适用于XmlDocuments,而不适用于XPathDocuments。你在用后者吗?谢谢。人们在这件事上花了这么多时间,我几乎很抱歉我问了这个问题但是请注意,您的位置和迭代版本中存在复制粘贴错误。在递归调用时,两者实际上都调用您的兄弟版本。这也许可以解释为什么你看不到性能上的差异:-天哪!我的错。将很快更新。嗯,实际上我花的时间比我允许的要多,但这很有趣,也很有教育意义!尽管如此,问题还是很好。已更正并编辑。我还删除了xpath position版本,因为它不正确。我认为您没有完全阅读这个问题。我需要知道每个步骤的位置,例如/foo[3]/bar[2]
private static void Measure(string functionName, int iterations, Action implementation)
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
implementation();
}
watch.Stop();
Console.WriteLine("{0}: {1}ms", functionName, watch.ElapsedMilliseconds);
}
private static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load(@"location of some large and complex XML file");
string referenceXPath = "/vps/vendorProductSets/vendorProductSet/product[100]/prodName/locName";
Measure("UsingPreviousSiblings", 10000,
() =>
{
XmlElement target = doc.SelectSingleNode(referenceXPath) as XmlElement;
Debug.Assert(referenceXPath == target.GetXPath_UsingPreviousSiblings());
});
Measure("SequentialIteration", 10000,
() =>
{
XmlElement target = doc.SelectSingleNode(referenceXPath) as XmlElement;
Debug.Assert(referenceXPath == target.GetXPath_SequentialIteration());
});
}
// Get the node full path
static string getPath(XmlNode node)
{
string path = node.Name;
XmlNode search = null;
// Get up until ROOT
while ((search = node.ParentNode).NodeType != XmlNodeType.Document)
{
path = search.Name + "/" + path; // Add to path
node = search;
}
return "//"+path;
}
//returns full xpath, can make it extension method
protected string GetFullXpath(XPathNavigator navigator)
{
var firstNodeEscape = navigator.NodeType == XPathNodeType.Attribute
? "/@"
: "/";
string path = string.Concat(firstNodeEscape, $"{navigator.Name}{GetXmlArrayIndex(navigator)}");
while (navigator.MoveToParent())
{
path = string.Concat($"/{navigator.Name}{GetXmlArrayIndex(navigator)}", path);
}
return path;
}
//returns index of element in array, used inside GetFullXpath method to add node position in array to path
private string GetXmlArrayIndex(XPathNavigator navigator)
{
var backwardsNavigator = navigator.Clone();
var elementsBeforeCount = 0;
while (backwardsNavigator.MoveToPrevious()) //true while has siblings
{
if (backwardsNavigator.NodeType != XPathNodeType.Element) continue;
if (backwardsNavigator.Name == navigator.Name) elementsBeforeCount++;
else break;
}
return elementsBeforeCount > 0
? $"[{++elementsBeforeCount}]" //++ because xpath array indexes are 1-based
: string.Empty;
}