Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/279.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 获取当前节点的完整路径_C#_.net_Xpath - Fatal编程技术网

C# 获取当前节点的完整路径

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

如果节点上有一个XPathNavigator,如何从根节点获取表示该节点路径的XPath表达式

例如,如果XML为:

<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;
    }