45

ドキュメントの奥深くに XElement があります。XElement (および XDocument?) が与えられた場合、完全な (つまり、絶対的な/root/item/element/child) XPath を取得するための拡張メソッドはありますか?

例 myXElement.GetXPath()?

編集:わかりました、私は何か非常に重要なことを見落としているようです. おっと!要素のインデックスを考慮する必要があります。提案された修正されたソリューションについては、私の最後の回答を参照してください。

4

10 に答える 10

46

拡張メソッド:

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]

これで解決するはずです。いいえ?

于 2009-01-18T03:34:17.010 に答える
11

名前空間の接頭辞を考慮して、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.");
    }
}
于 2010-03-22T14:40:42.743 に答える
7

このクラスへの最新の変更を共有させてください。基本的に、要素に兄弟がなく、local-name() 演算子を含む名前空間が含まれている場合はインデックスを除外し、名前空間プレフィックスに問題があった場合。

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.");
    }
}
于 2014-05-08T12:02:53.637 に答える
4

これは実際にはこの質問の複製です。回答としてマークされていませんが、その質問に対する私の回答の方法は、すべての状況で常に機能する XML ドキュメント内のノードへの XPath を明確に定式化する唯一の方法です。(要素だけでなく、すべてのノード タイプに対しても機能します。)

ご覧のとおり、それが生成する XPath は見苦しく抽象的なものです。しかし、多くの回答者がここで提起した懸念に対処しています。ここで行った提案のほとんどは、元のドキュメントを検索するために使用すると、ターゲット ノードを含む 1 つ以上のノードのセットを生成する XPath を生成します。その「以上」が問題なのです。たとえば、DataSet の XML 表現がある場合、特定の DataRow の要素への単純な XPath/DataSet1/DataTable1は、DataTable 内の他のすべての DataRow の要素も返します。XML がどのようにフォーラム化されているか (たとえば、主キー要素があるかどうかなど) を知らなければ、それを明確にすることはできません。

しかし/node()[1]/node()[4]/node()[11]、何があっても返されるノードは 1 つだけです。

于 2009-01-17T01:54:20.627 に答える
3

別のプロジェクトの一環として、要素への単純な XPath を生成する拡張メソッドを開発しました。選択した回答に似ていますが、XElement に加えて XAttribute、XText、XCData、および XComment をサポートしています。これは、コード ナゲット、プロジェクト ページとして入手できます: xmlspecificationcompare.codeplex.com

于 2013-09-01T12:51:15.420 に答える
0

同じ要素につながる複数の xpath が存在する可能性があるため、ノードにつながる最も単純な xpath を見つけることは簡単ではありません。

つまり、ノードへの xpath を見つけるのは非常に簡単です。ルート ノードを読み取ってノード名を結合し、有効な xpath が得られるまで、ノード ツリーをステップ アップするだけです。

于 2009-01-16T21:12:50.510 に答える
0

「完全な xpath」とは、任意の要素に一致する可能性のある xpath の数が非常に多くなる可能性があるため、タグの単純なチェーンを意味すると思います。

ここでの問題は、同じ要素に可逆的にトレースバックする特定の xpath を構築することが特に不可能ではないにしても非常に難しいことです-それは条件ですか?

「いいえ」の場合、現在の要素のparentNodeを参照して再帰的にループすることにより、おそらくクエリを構築できます。「はい」の場合、兄弟セット内のインデックス位置を相互参照し、ID のような属性が存在する場合は参照することでそれを拡張することを検討します。これは、一般的な解決策である場合、XSD に大きく依存します。可能です。

于 2009-01-16T21:14:18.323 に答える
0

.NET によってネイティブに提供されるものを探している場合、答えはノーです。これを行うには、独自の拡張メソッドを作成する必要があります。

于 2009-01-16T21:07:45.823 に答える
-1

Microsoft は、.NET Framework 3.5 以降、これを行うための拡張メソッドを提供しています。

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

using を追加してSystem.Xml.XPath、次のメソッドを呼び出すだけです。

  • XPathSelectElement: 単一の要素を選択します
  • XPathSelectElements: 要素を選択し、IEnumerable<XElement>
  • XPathEvaluate: ノード (要素だけでなく、テキスト、コメントなど) を選択し、IEnumerable<object>
于 2014-06-07T18:27:35.673 に答える