質問

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

E.g。 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.");
    }
}

このクラスの最新の変更を共有させてください。 基本的に、要素に兄弟がなく、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.");
    }
}

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

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

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

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

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

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

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

<!> quot; full xpath <!> quot;任意の要素に一致する可能性のあるxpathの数は非常に大きくなる可能性があるため、単純なタグのチェーンを意味すると仮定します。

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

If <!> quot; no <!> quot;次に、現在の要素parentNodeを参照して再帰的にループすることにより、クエリを作成できます。 <!> quot; yes <!> quot;の場合、兄弟セット内のインデックス位置を相互参照し、IDのような属性が存在する場合はそれを参照することにより、その拡張を検討することになります。一般的な解決策が可能な場合は、XSDに依存します。

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>
  • として返します
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top