Obtenir le XPath à un XElement?

J’ai un XElement profondément dans un document. Étant donné le XElement (et XDocument?), Existe-t-il une méthode d’extension pour obtenir son XPath complet (absolu, par exemple /root/item/element/child )?

Par exemple myXElement.GetXPath ()?

EDIT: OK, on ​​dirait que j’ai négligé 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.

Les méthodes d’extensions:

 public static class XExtensions { ///  /// Get the absolute XPath to a given XElement /// (eg "/people/person[6]/name[1]/last[1]"). ///  public static ssortingng GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func relativeXPath = e => { int index = e.IndexPosition(); ssortingng name = e.Name.LocalName; // If the element is the root, no index is required return (index == -1) ? "/" + name : ssortingng.Format ( "/{0}[{1}]", name, index.ToSsortingng() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return ssortingng.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."); } } 

Et le test:

 class Program { static void Main(ssortingng[] 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 sortie d’échantillon:

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

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 { ///  /// 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 ssortingng GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func relativeXPath = e => { int index = e.IndexPosition(); var currentNamespace = e.Name.Namespace; ssortingng name; if (currentNamespace == null) { name = e.Name.LocalName; } else { ssortingng namespacePrefix = e.GetPrefixOfNamespace(currentNamespace); name = namespacePrefix + ":" + e.Name.LocalName; } // If the element is the root, no index is required return (index == -1) ? "/" + name : ssortingng.Format ( "/{0}[{1}]", name, index.ToSsortingng() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return ssortingng.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."); } } 

C’est en fait un duplicata de cette question. Bien qu’elle ne soit pas marquée comme réponse, la méthode utilisée dans ma réponse à cette question est la seule façon de formuler sans ambiguïté le XPath sur un nœud dans un document XML qui fonctionnera toujours dans toutes les circonstances. (Cela fonctionne aussi pour tous les types de nœuds, pas seulement pour les éléments.)

Comme vous pouvez le voir, le XPath produit est laid et abstrait. mais cela répond aux préoccupations soulevées par de nombreux intervenants. La plupart des suggestions faites ici produisent un XPath qui, lorsqu’il est utilisé pour rechercher le document original, produira un ensemble d’un ou plusieurs nœuds incluant le nœud cible. C’est ça le problème ou plus. Par exemple, si j’ai une représentation XML d’un DataSet, le XPath naïf associé à un élément DataRow spécifique, /DataSet1/DataTable1 , renvoie également les éléments de tous les autres DataRows du DataTable. Vous ne pouvez pas désambiguïser cela sans savoir quelque chose sur la façon dont le XML est mis en forum (comme, y a-t-il un élément de clé primaire?).

Mais /node()[1]/node()[4]/node()[11] , il n’ya qu’un seul noeud qu’il renverra jamais, peu importe quoi.

Permettez-moi de partager ma dernière modification à cette classe. Fondamentalement, il exclut index si element n’a pas de frères et soeurs et inclut des espaces de noms avec l’opérateur local-name () J’ai eu des problèmes avec le préfixe d’espace de noms.

 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 ssortingng GetAbsoluteXPath(this XElement element) { if (element == null) { throw new ArgumentNullException("element"); } Func relativeXPath = e => { int index = e.IndexPosition(); var currentNamespace = e.Name.Namespace; ssortingng name; if (Ssortingng.IsNullOrEmpty(currentNamespace.ToSsortingng())) { name = e.Name.LocalName; } else { name = "*[local-name()='" + e.Name.LocalName + "']"; //ssortingng 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 : ssortingng.Format ( "/{0}[{1}]", name, index.ToSsortingng() ); }; var ancestors = from e in element.Ancestors() select relativeXPath(e); return ssortingng.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."); } } 

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 XAtsortingbute, XText, XCData et XComment en plus de XElement. Il est disponible sous forme de nuget de code , page de projet ici: xmlspecificationcompare.codeplex.com

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

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

Cela dit, il est assez facile de trouver un xpath au nœud. Augmentez simplement l’arborescence des nœuds jusqu’à ce que vous lisiez le nœud racine et que vous combiniez les noms des nœuds et que vous ayez un chemin xpath valide.

Par “xpath complet”, je suppose que vous voulez dire une simple chaîne de balises, car le nombre de xpath pouvant potentiellement correspondre à n’importe quel élément peut être très important.

Le problème ici est qu’il est très difficile, si ce n’est pas impossible, de construire un chemin xpath donné qui remontera de manière réversible vers le même élément – est-ce une condition?

Si “non” alors vous pourriez peut-être construire une requête en boucle de manière récursive en référence aux éléments courants parentNode. Si “oui”, alors vous allez chercher à étendre cela en effectuant des références croisées pour la position de l’index dans les ensembles frères, en référençant les atsortingbuts de type ID s’ils existent, et cela dépendra beaucoup de votre XSD si une solution générale est possible.

Microsoft a fourni une méthode d’extension pour ce faire depuis .NET Framework 3.5:

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

Ajoutez simplement une utilisation à System.Xml.XPath et appelez les méthodes suivantes:

  • XPathSelectElement : sélectionnez un seul élément
  • XPathSelectElements : sélectionne les éléments et renvoie en tant que IEnumerable
  • XPathEvaluate : sélectionne des noeuds (pas seulement des éléments, mais aussi du texte, des commentaires, etc.) et retourne sous la forme d’un IEnumerable