我在文档深处有一个 XElement。给定 XElement (和 XDocument?),是否有扩展方法来获取其完整内容(即绝对的,例如 /root/item/element/child) X 路径?

例如。myXElement.GetXPath()?

编辑:好吧,看来我忽略了一些非常重要的事情。哎呀!需要考虑元素的索引。有关建议的更正解决方案,请参阅我的最后一个答案。

有帮助吗?

解决方案

在扩展方法:

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement
    /// (e.g. "/people/person[6]/name[1]/last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();
            string name = e.Name.LocalName;

            // If the element is the root, no index is required

            return (index == -1) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name, 
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) + 
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

和测试:

class Program
{
    static void Main(string[] args)
    {
        Program.Process(XDocument.Load(@"C:\test.xml").Root);
        Console.Read();
    }

    static void Process(XElement element)
    {
        if (!element.HasElements)
        {
            Console.WriteLine(element.GetAbsoluteXPath());
        }
        else
        {
            foreach (XElement child in element.Elements())
            {
                Process(child);
            }
        }
    }
}

和输出样本:

/tests/test[1]/date[1]
/tests/test[1]/time[1]/start[1]
/tests/test[1]/time[1]/end[1]
/tests/test[1]/facility[1]/name[1]
/tests/test[1]/facility[1]/website[1]
/tests/test[1]/facility[1]/street[1]
/tests/test[1]/facility[1]/state[1]
/tests/test[1]/facility[1]/city[1]
/tests/test[1]/facility[1]/zip[1]
/tests/test[1]/facility[1]/phone[1]
/tests/test[1]/info[1]
/tests/test[2]/date[1]
/tests/test[2]/time[1]/start[1]
/tests/test[2]/time[1]/end[1]
/tests/test[2]/facility[1]/name[1]
/tests/test[2]/facility[1]/website[1]
/tests/test[2]/facility[1]/street[1]
/tests/test[2]/facility[1]/state[1]
/tests/test[2]/facility[1]/city[1]
/tests/test[2]/facility[1]/zip[1]
/tests/test[2]/facility[1]/phone[1]
/tests/test[2]/info[1]

这应该解决这个。没有?

其他提示

我更新由Chris代码考虑到命名空间前缀。只有GetAbsoluteXPath方法被修改。

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement, including the namespace.
    /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;
            if (currentNamespace == null)
            {
                name = e.Name.LocalName;
            }
            else
            {
                string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                name = namespacePrefix + ":" + e.Name.LocalName;
            }

            // If the element is the root, no index is required
            return (index == -1) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name,
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) +
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

让我分享我的最新修改此类。 Basicaly它排除索引如果元素没有兄弟,并且包括与本地名称命名空间()操作者必须我是具有与所述命名空间前缀的问题。

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement, including the namespace.
    /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }


        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;
            if (String.IsNullOrEmpty(currentNamespace.ToString()))
            {
                name = e.Name.LocalName;
            }
            else
            {
                name = "*[local-name()='" + e.Name.LocalName + "']";
                //string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                //name = namespacePrefix + ":" + e.Name.LocalName;
            }

            // If the element is the root or has no sibling elements, no index is required
            return ((index == -1) || (index == -2)) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name,
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) +
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned or -2 if element has no sibling elements.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            // Element is root
            return -1;
        }

        if (element.Parent.Elements(element.Name).Count() == 1)
        {
            // Element has no sibling elements
            return -2;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

这实际上是的副本题。虽然它没有标记为答案,在我回答这个问题是明确制定的XPath的节点的XML文档,将永远在所有情况下的工作范围内的唯一途径。 (它也适用于所有的节点类型,而不仅仅是元素。)

可以看到,它产生的XPath是难看和抽象。但它解决了许多应答者在这里提出的问题。这里的大多数提出的建议产生,用于搜索原始文档时,会产生一组包括目标节点的一个或多个节点的XPath。这是说,“以上”这就是问题所在。举例来说,如果我有一个数据集的XML表示,天真的XPath特定的DataRow的元素,/DataSet1/DataTable1,也将返回在DataTable中的其他数据行的元素。你不能说歧义不知道一些关于XML是如何forumlated(比如,有一个主键元素?)。

/node()[1]/node()[4]/node()[11],还有就是它会不会回来,无论是只有一个节点是什么。

作为href="http://formaldev.blogspot.com.au/2013_08_01_archive.html" rel="nofollow">不同项目我开发了一个扩展方法来生成一个简单的XPath一个代码的NuGet ,项目页面在这里:的 xmlspecificationcompare.codeplex.com

如果你正在寻找由.NET本身提供的东西的答案是否定的。你将不得不编写自己的扩展方法来做到这一点。

可以有多个的XPath,导致相同的元件,因此能够发现通向节点的最简单的xpath并不是微不足道的。

这就是说,它是很容易找到的XPath到该节点。只要加强节点树,直到你读根节点,结合节点名称和你有一个有效的XPath。

这是“完全的XPath”我想你指的标签一个简单链,因为这有可能匹配任何元素可能的非常的大的XPath的数量。

这里的问题是,它很难,如果没有专门不可能建立任何给定的XPath将可逆追溯相同的元素 - ?是条件

如果“否”,则也许你可以通过参考当前元素parentNode递归循环构建查询。如果“是”,那么你将要寻找的延伸,通过交叉引用的兄弟姐妹集内的索引位置,如果存在的话referecing ID样的属性,这将是非常依赖于你的XSD如果一个通用的解决方案是可能的。

从 .NET Framework 3.5 开始,Microsoft 提供了一个扩展方法来执行此操作:

http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx

只需添加一个 using 即可 System.Xml.XPath 并调用以下方法:

  • XPathSelectElement: :选择单个元素
  • XPathSelectElements: :选择元素并返回 IEnumerable<XElement>
  • XPathEvaluate: :选择节点(不仅是元素,还包括文本、注释等)并作为 IEnumerable<object>
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top