Domanda

Ho un XElement nel profondo di un documento. Dato XElement (e XDocument?), Esiste un metodo di estensione per ottenere il suo pieno (cioè assoluto, ad esempio / root / item / element / child ) XPath?

es. myXElement.GetXPath ()?

EDIT: Ok, sembra che abbia trascurato qualcosa di molto importante. Ops! L'indice dell'elemento deve essere preso in considerazione. Vedi la mia ultima risposta per la soluzione corretta proposta.

È stato utile?

Soluzione

I metodi di estensione:

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

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

E output di esempio:

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

Questo dovrebbe risolvere questo. No?

Altri suggerimenti

Ho aggiornato il codice di Chris per tenere conto dei prefissi dello spazio dei nomi. Viene modificato solo il metodo 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.");
    }
}

Fammi condividere la mia ultima modifica a questa classe. Fondamentalmente esclude l'indice se element non ha fratelli e include spazi dei nomi con l'operatore local-name () ho avuto problemi con il prefisso dello spazio dei nomi.

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

Questo è in realtà un duplicato di this domanda. Sebbene non sia contrassegnato come risposta, il metodo in my la risposta a questa domanda è l'unico modo per formulare in modo inequivocabile XPath su un nodo all'interno di un documento XML che funzionerà sempre in tutte le circostanze. (Funziona anche per tutti i tipi di nodo, non solo per gli elementi.)

Come puoi vedere, l'XPath che produce è brutto e astratto. ma risolve le preoccupazioni che molti rispondenti hanno sollevato qui. La maggior parte dei suggerimenti qui forniti produce un XPath che, se utilizzato per cercare il documento originale, produrrà un insieme di uno o più nodi che include il nodo di destinazione. È che " o più " questo è il problema. Ad esempio, se ho una rappresentazione XML di un DataSet, l'ingenua XPath a un elemento DataRow specifico, / DataSet1 / DataTable1 , restituisce anche gli elementi di tutti gli altri DataRows nella DataTable. Non si può chiarire questo senza sapere qualcosa su come l'XML è associato al forum (come, c'è un elemento chiave primaria?).

Ma / node () [1] / node () [4] / node () [11] , c'è solo un nodo che potrà mai restituire, qualunque cosa accada.

Nell'ambito di un progetto diverso ho sviluppato un metodo di estensione per generare un semplice XPath ad un elemento. È simile alla risposta selezionata, ma supporta XAttribute, XText, XCData e XComment oltre a XElement. È disponibile come code nuget , pagina del progetto qui: xmlspecificationcompare.codeplex.com

Se stai cercando qualcosa fornito nativamente da .NET la risposta è no. Dovresti scrivere il tuo metodo di estensione per farlo.

Possono esserci diversi xpath che portano allo stesso elemento, quindi trovare l'xpath più semplice che porta al nodo non è banale.

Detto questo, è abbastanza facile trovare un xpath al nodo. È sufficiente aumentare la struttura dei nodi fino a quando non si legge il nodo principale e si combinano i nomi dei nodi e si dispone di un xpath valido.

Per " percorso x completo " Suppongo che intendi una semplice catena di tag poiché il numero di xpath che potrebbero corrispondere a qualsiasi elemento potrebbe essere molto grande.

Il problema qui è che è molto difficile, se non specificamente impossibile, costruire un dato xpath che possa risalire in modo reversibile allo stesso elemento - è una condizione?

Se " no " quindi forse potresti creare una query eseguendo un ciclo ricorsivo con riferimento agli elementi parentNode correnti. Se "sì", allora stai cercando di estenderlo facendo riferimento incrociato per la posizione dell'indice all'interno di insiemi di fratelli, facendo riferimento ad attributi simili a ID se esistono, e questo dipenderà molto dal tuo XSD se un generale la soluzione è possibile.

Microsoft ha fornito un metodo di estensione per farlo da .NET Framework 3.5:

http://msdn.microsoft. com / it-it / library / bb156083 (v = VS.100) aspx

Basta aggiungere un utilizzo a System.Xml.XPath e invocare i seguenti metodi:

  • XPathSelectElement : seleziona un singolo elemento
  • XPathSelectElements : seleziona gli elementi e ritorna come IEnumerable<XElement>
  • XPathEvaluate : seleziona i nodi (non solo elementi, ma anche testo, commenti ecc.) e ritorna come IEnumerable<object>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top