Question

J'ai un XElement au fond d'un document. Étant donné le XElement (et le XDocument?), Existe-t-il une méthode d'extension pour obtenir sa totalité (c'est-à-dire absolue, par exemple / racine / item / élément / élément ) XPath?

E.g. myXElement.GetXPath ()?

EDIT: Ok, on dirait que j'ai oublié quelque chose de très important. Oups! L'index de l'élément doit être pris en compte. Voir ma dernière réponse pour la solution corrigée proposée.

Était-ce utile?

La solution

Les méthodes d'extensions:

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

Et le test:

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

Et exemple de sortie:

/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]

Cela devrait régler cela. Non?

Autres conseils

J'ai mis à jour le code de Chris afin de prendre en compte les préfixes d'espace de nom. Seule la méthode GetAbsoluteXPath est modifiée.

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

Permettez-moi de partager ma dernière modification apportée à cette classe. En gros, il exclut index si l'élément n'a pas de frère et inclut les espaces de noms avec l'opérateur local-name () a eu des problèmes avec le préfixe d'espace de noms.

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

Il s'agit en réalité d'une copie de this question. Bien que ce ne soit pas indiqué comme étant la réponse, la méthode décrite dans mon La réponse à cette question est le seul moyen de formuler sans ambiguïté le XPath en nœud dans un document XML qui fonctionnera toujours dans toutes les circonstances. (Cela fonctionne également pour tous les types de nœuds, pas seulement les éléments.)

Comme vous pouvez le constater, le XPath qu’il produit est laid et abstrait. mais cela répond aux préoccupations soulevées par de nombreux intervenants. La plupart des suggestions présentées ici produisent un XPath qui, lorsqu'il est utilisé pour rechercher le document d'origine, produira un ensemble d'un ou plusieurs nœuds incluant le nœud cible. C'est ça " ou plus " c'est le problème. Par exemple, si j'ai la représentation XML d'un DataSet, le XPath naïf d'un élément spécifique de DataRow, / DataSet1 / DataTable1 , renvoie également les éléments de tous les autres DataRows du DataTable. Vous ne pouvez pas comprendre cette ambiguïté sans savoir comment le XML est traité dans les forums (par exemple, y a-t-il un élément de clé primaire?).

Mais / node () [1] / node () [4] / node () [11] , il n’ya qu’un seul nœud à renvoyer, quoi qu’il en soit.

Dans le cadre d'un projet différent , j'ai développé une méthode d'extension pour générer un XPath simple. à un élément. Elle est similaire à la réponse sélectionnée, mais prend en charge XAttribute, XText, XCData et XComment en plus de XElement. Il est disponible en tant que nuget de code , page de projet ici: xmlspecificationcompare.codeplex.com

Si vous cherchez quelque chose de natif dans .NET, la réponse est non. Pour ce faire, vous devrez écrire votre propre méthode d’extension.

Plusieurs chemins xpath pouvant mener au même élément, la recherche du xpath le plus simple menant au nœud n’est donc pas triviale.

Cela dit, il est assez facile de trouver un xpath vers le nœud. Intensifiez simplement l’arborescence jusqu’à ce que vous lisiez le nœud racine, associez les noms de nœuds et obtenez un xpath valide.

Par "full xpath" Je suppose que vous entendez une simple chaîne de balises, car le nombre de xpath pouvant potentiellement correspondre à n’importe quel élément pourrait être très .

Le problème ici est qu’il est très difficile, voire impossible, de construire un xpath donné qui remontera de manière réversible au même élément - est-ce une condition?

Si " no " alors peut-être pourriez-vous construire une requête en boucle récursive en référence aux éléments actuels parentNode. Si "oui", vous envisagez d'étendre cela en faisant des références croisées pour la position d'index dans les ensembles frères, en faisant référence aux attributs de type ID s'ils existent, et cela dépendra beaucoup de votre XSD si un la solution est possible.

Microsoft a fourni une méthode d’extension à cette fin depuis .NET Framework 3.5:

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

Ajoutez simplement using avec System.Xml.XPath et appelez les méthodes suivantes:

  • XPathSelectElement : sélectionnez un seul élément
  • XPathSelectElements : sélectionne des éléments et les renvoie sous forme de IEnumerable < XElement >
  • XPathEvaluate : sélectionne des nœuds (non seulement des éléments, mais également du texte, des commentaires, etc.) et retourne sous forme de IEnumerable < objet >
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top