Получить полный путь к текущему узлу - PullRequest
5 голосов
/ 10 февраля 2012

Если у меня есть 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]

Я могу перечислить предков рассматриваемого узла с помощью SelectAncestors() (или я мог бы итеративно взбираться по родительским отношениям с помощью SelectParent()), но это не дает мне информацию о положении.

Должен ли я оценивать XPath, используя position() для каждого предка, или есть лучший способ сделать это?

Ответы [ 3 ]

6 голосов
/ 10 февраля 2012

Предполагая, что вы интересуетесь только xpath элементов xml, я реализовал алгоритм грубой силы (т.е. обход структуры XML) в качестве методов расширения на XmlElement.Это очень похоже на ответ @ Zenexer, хотя я уже начал свою собственную версию, когда он опубликовал его.

Кроме того, заинтригованный советами Алексея о производительности, я создал своего рода тестовый пример, используя несколько сложный XMLфайл лиринг здесь.Затем я реализовал две версии одного и того же алгоритма;один, который зависит от PreviousSibling, и другой, который последовательно выполняет итерации узлов.Третья версия опиралась на position() функцию XPath, но она не работала должным образом и была отброшена.

Хотя вы должны проверить сами, на моей машине результаты показали значительное преимущество в производительности дляитеративная версия - 1,7 с против 21 с, набранных по версии братьев и сестер.

Importart: эти методы расширения объявлены внутри static class XmlElementExtension.

PreviousSibling version

    public static string GetXPath_UsingPreviousSiblings(this XmlElement element)
    {
        string path = "/" + element.Name;

        XmlElement parentElement = element.ParentNode as XmlElement;
        if (parentElement != null)
        {
            // Gets the position within the parent element, based on previous siblings of the same name.
            // However, this position is irrelevant if the element is unique under its parent:
            XPathNavigator navigator = parentElement.CreateNavigator();
            int count = Convert.ToInt32(navigator.Evaluate("count(" + element.Name + ")"));
            if (count > 1) // There's more than 1 element with the same name
            {
                int position = 1;
                XmlElement previousSibling = element.PreviousSibling as XmlElement;
                while (previousSibling != null)
                {
                    if (previousSibling.Name == element.Name)
                        position++;

                    previousSibling = previousSibling.PreviousSibling as XmlElement;
                }

                path = path + "[" + position + "]";
            }

            // Climbing up to the parent elements:
            path = parentElement.GetXPath_UsingPreviousSiblings() + path;
        }

        return path;
    }

Итерационная версия

    public static string GetXPath_SequentialIteration(this XmlElement element)
    {
        string path = "/" + element.Name;

        XmlElement parentElement = element.ParentNode as XmlElement;
        if (parentElement != null)
        {
            // Gets the position within the parent element.
            // However, this position is irrelevant if the element is unique under its parent:
            XmlNodeList siblings = parentElement.SelectNodes(element.Name);
            if (siblings != null && siblings.Count > 1) // There's more than 1 element with the same name
            {
                int position = 1;
                foreach (XmlElement sibling in siblings)
                {
                    if (sibling == element)
                        break;

                    position++;
                }

                path = path + "[" + position + "]";
            }

            // Climbing up to the parent elements:
            path = parentElement.GetXPath_SequentialIteration() + path;
        }

        return path;
    }

Контрольный пример

    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());
                });
    }
3 голосов
/ 10 февраля 2012

Непроверено;работает только с объектами XPathNavigator, созданными из объектов XmlDocument:

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.UnderlyingObject, как в примере.

2 голосов
/ 22 декабря 2013

Простое решение с 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;
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...