Ottieni XPath su XElement?

Ho un XElement in profondità all’interno di un documento. Dato XElement (e XDocument?), Esiste un metodo di estensione per ottenere il suo completo (cioè assoluto, ad esempio /root/item/element/child ) XPath?

Ad esempio myXElement.GetXPath ()?

EDIT: Ok, sembra che ho 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.

I metodi di estensioni:

 public static class XExtensions { ///  /// Get the absolute XPath to a given XElement /// (eg "/people/person[6]/name[1]/last[1]"). ///  public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func 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); } ///  /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. ///  ///  /// The element to get the index of. ///  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 l’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?

Ho aggiornato il codice di Chris per prendere in considerazione i prefissi dei namespace. Viene modificato solo il metodo GetAbsoluteXPath.

 public static class XExtensions { ///  /// Get the absolute XPath to a given XElement, including the namespace. /// (eg "/a:people/b:person[6]/c:name[1]/d:last[1]"). ///  public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func 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); } ///  /// Get the index of the given XElement relative to its /// siblings with identical names. If the given element is /// the root, -1 is returned. ///  ///  /// The element to get the index of. ///  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."); } } 

Questo è in realtà un duplicato di questa domanda. Anche se non è contrassegnato come risposta, il metodo nella mia risposta a questa domanda è l’unico modo per formulare in modo univoco XPath su un nodo all’interno di un documento XML che funzionerà sempre in tutte le circostanze. (Funziona anche per tutti i tipi di nodes, non solo per gli elementi.)

Come puoi vedere, l’XPath che produce è brutto e astratto. ma affronta le preoccupazioni che molti rispondenti hanno sollevato qui. La maggior parte dei suggerimenti fatti qui produce un XPath che, se usato per cercare il documento originale, produrrà un insieme di uno o più nodes che include il nodo di destinazione. È che “o più” è questo il problema. Ad esempio, se ho una rappresentazione XML di un DataSet, l’ingenuo XPath su un elemento DataRow specifico, /DataSet1/DataTable1 , restituisce anche gli elementi di tutte le altre DataRows nel DataTable. Non si può disambiguarlo senza sapere qualcosa su come l’XML è forumlated (come, c’è un elemento chiave-chiave?).

Ma /node()[1]/node()[4]/node()[11] , c’è solo un nodo che potrà mai tornare, non importa quale.

Permettetemi di condividere la mia ultima modifica a questa class. In pratica esclude l’indice se l’elemento non ha fratelli e include gli spazi dei nomi con l’operatore nome-locale () ho avuto problemi con il prefisso namespace.

 public static class XExtensions { ///  /// Get the absolute XPath to a given XElement, including the namespace. /// (eg "/a:people/b:person[6]/c:name[1]/d:last[1]"). ///  public static string GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func 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); } ///  /// 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. ///  ///  /// The element to get the index of. ///  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."); } } 

Come parte di un progetto diverso, ho sviluppato un metodo di estensione per generare un XPath semplice per un elemento. È simile alla risposta selezionata, ma supporta XAttribute, XText, XCData e XComment oltre a XElement. È disponibile come codice 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.

Ci possono essere diversi xpath che portano allo stesso elemento, quindi trovare il più semplice xpath che conduce al nodo non è banale.

Detto questo, è abbastanza facile trovare un percorso x sul nodo. Basta aumentare la struttura del nodo fino a quando non si legge il nodo radice e si combinano i nomi dei nodes e si ha un percorso x valido.

Con “full xpath” presumo tu intenda una semplice catena di tag poiché il numero di xpath che potenzialmente potrebbero corrispondere a qualsiasi elemento potrebbe essere molto grande.

Il problema qui è che è molto difficile se non è specificamente imansible creare un determinato xpath che rinvierà in modo reversibile allo stesso elemento – è una condizione?

Se “no”, forse potresti creare una query ricorrendo in modo ricorsivo con riferimento agli attuali elementi parentNode. Se “sì”, allora stai andando a cercare di estenderlo tramite riferimenti incrociati per la posizione dell’indice all’interno di insiemi fratelli e sorelle, facendo riferimento ad attributi di tipo ID se esistono, e questo sarà molto dipendente dal tuo XSD se una soluzione generale è ansible.

Microsoft ha fornito un metodo di estensione per fare ciò dal .NET Framework 3.5:

http://msdn.microsoft.com/en-us/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 restituisce come IEnumerable
  • XPathEvaluate : seleziona i nodes (non solo gli elementi, ma anche il testo, i commenti, ecc.) E restituisci come IEnumerable