web-dev-qa-db-fra.com

Obtenez le XPath à un xelement?

J'ai un xélément au-delà d'un document. Compte tenu du xelement (et XDocument?), Existe-t-il une méthode d'extension pour intégrer son plein (c'est-à-dire absolu, par exemple /root/item/element/child) Xpath?

Par exemple. myxelement.getxpath ()?

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

42
core

Les extensions Méthodes:

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?

42
core

J'ai mis à jour le code par Chris pour prendre en compte les préfixes d'espace de noms. 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.");
    }
}
11

Permettez-moi de partager mes dernières modifications à cette classe. Basicaly, il exclut l'index si l'élément n'a pas de frère et comprend des espaces de noms avec le nom de nom local () que j'ai eu des problèmes avec le préfixe de l'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.");
    }
}
6
Chaveiro

C'est en fait un duplicata de this question. Bien que ce ne soit pas marqué comme la réponse, la méthode dans ma réponse à cette question est la seule façon de formuler sans ambiguïté le XPath à un noeud au sein d'un document XML qui fonctionnera toujours dans toutes les circonstances. (Cela fonctionne également pour tous les types de nœuds, pas seulement des éléments.)

Comme vous pouvez le constater, le XPATH qui produit est laid et abstrait. Mais il aborde les préoccupations que de nombreux intervenants ont soulevé ici. La plupart des suggestions fabriqué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 qui inclut le nœud cible. C'est ce "ou plus" c'est le problème. Par exemple, si j'ai une représentation XML d'un jeu de données, le NAIVE XPATH à un élément de datarow spécifique, /DataSet1/DataTable1, Renvoie également les éléments de toutes les autres datarses dans le type de données. Vous ne pouvez pas le désambigucher sans savoir quelque chose sur la manière dont la XML est forcée (comme, existe-t-il un élément clé principal?).

Mais /node()[1]/node()[4]/node()[11], il n'y a qu'un noeud que cela reviendra jamais, peu importe quoi.

4
Robert Rossney

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. Il est similaire à la réponse sélectionnée, mais prend en charge Xattribute, XText, XCDATA et XCOMMENT en plus du xelement. Il est disponible comme code nuget , page de projet ici: xmlspecificationcompare.codeplex.com

2
Eli Algranti

Si vous recherchez quelque chose de natalement fourni par .net, la réponse est non. Vous devriez écrire votre propre méthode d'extension pour le faire.

0
Scott Dorman

Il peut y avoir plusieurs XPaths qui mènent au même élément, alors trouver le plus simple xpath qui mène au nœud n'est pas trivial.

Cela dit, il est assez facile de trouver un xpath au nœud. Suscitez simplement l'arborescence des nœuds avant de lire le nœud racine et de combiner les noms de nœuds et vous avez un XPath valide.

0
Rune Grimstad

Par "Full XPath", je suppose que vous voulez dire une simple chaîne de balises depuis le nombre de xpaths pouvant éventuellement correspondre à tout élément pourraient être très = grand.

Le problème ici est que c'est très difficile si ce n'est pas spécifiquement impossible de construire un XPath donné qui reviendra de manière réversible sur le même élément - est-ce une condition?

Si "Non", vous pourriez peut-être construire une requête en boucle récursivement en référence aux éléments actuels parentnode. Si "oui", vous allez chercher à étendre celle-ci par référencement croisée pour la position de l'index dans les ensembles de frères de sœurs, des attributs de type ID de références s'ils existent, et cela va être très dépendant de votre XSD si une solution générale est possible.

0
annakata