Quelqu'un pourrait-il fournir du code qui obtiendrait le xpath d'une instance System.Xml.XmlNode?
Merci!
Quelqu'un pourrait-il fournir du code qui obtiendrait le xpath d'une instance System.Xml.XmlNode?
Merci!
D'accord, je n'ai pas pu m'empêcher de tenter ma chance. Cela ne fonctionnera que pour les attributs et les éléments, mais bon… à quoi pouvez-vous vous attendre dans 15 minutes :) De même, il pourrait très bien y avoir une façon plus propre de le faire.
Il est superflu d'inclure l'index sur chaque élément (en particulier celui qui est à la racine!), Mais il est plus facile que d'essayer de déterminer s'il existe une ambiguïté.
using System;
using System.Text;
using System.Xml;
class Test
{
static void Main()
{
string xml = @"
<root>
<foo />
<foo>
<bar attr='value'/>
<bar other='va' />
</foo>
<foo><bar /></foo>
</root>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNode node = doc.SelectSingleNode("//@attr");
Console.WriteLine(FindXPath(node));
Console.WriteLine(doc.SelectSingleNode(FindXPath(node)) == node);
}
static string FindXPath(XmlNode node)
{
StringBuilder builder = new StringBuilder();
while (node != null)
{
switch (node.NodeType)
{
case XmlNodeType.Attribute:
builder.Insert(0, "/@" + node.Name);
node = ((XmlAttribute) node).OwnerElement;
break;
case XmlNodeType.Element:
int index = FindElementIndex((XmlElement) node);
builder.Insert(0, "/" + node.Name + "[" + index + "]");
node = node.ParentNode;
break;
case XmlNodeType.Document:
return builder.ToString();
default:
throw new ArgumentException("Only elements and attributes are supported");
}
}
throw new ArgumentException("Node was not in a document");
}
static int FindElementIndex(XmlElement element)
{
XmlNode parentNode = element.ParentNode;
if (parentNode is XmlDocument)
{
return 1;
}
XmlElement parent = (XmlElement) parentNode;
int index = 1;
foreach (XmlNode candidate in parent.ChildNodes)
{
if (candidate is XmlElement && candidate.Name == element.Name)
{
if (candidate == element)
{
return index;
}
index++;
}
}
throw new ArgumentException("Couldn't find element within parent");
}
}
Jon est exact que il ya un certain nombre d'expressions XPath qui donnera le même nœud dans une une instance de document. La façon la plus simple de construire une expression sans ambiguïté les rendements d'un nœud spécifique est une chaîne de tests de nœuds qui utilisent la position du noeud dans le prédicat, par exemple:
/node()[0]/node()[2]/node()[6]/node()[1]/node()[2]
Évidemment, cette expression n'est pas à l'aide de noms d'éléments, mais alors si tout ce que vous essayez de faire est de localiser un nœud au sein d'un document, vous n'avez pas besoin de son nom. Il a également ne peut pas être utilisé pour trouver des attributs (comme les attributs ne sont pas des nœuds et n'ont pas de position; vous ne pouvez les trouver par nom), mais il va rechercher tous les autres types de nœuds.
Pour construire cette expression, vous devez écrire une méthode qui retourne un nœud qui est de la position de son parent enfant nœuds, car XmlNode
ne pas exposer que comme une propriété:
static int GetNodePosition(XmlNode child)
{
for (int i=0; i<child.ParentNode.ChildNodes.Count; i++)
{
if (child.ParentNode.ChildNodes[i] == child)
{
// tricksy XPath, not starting its positions at 0 like a normal language
return i + 1;
}
}
throw new InvalidOperationException("Child node somehow not found in its parent's ChildNodes property.");
}
(Il y a probablement une façon plus élégante de le faire à l'aide de LINQ, depuis XmlNodeList
implémente IEnumerable
, mais je vais avec ce que je connais ici.)
Ensuite, vous pouvez écrire une méthode récursive comme ceci:
static string GetXPathToNode(XmlNode node)
{
if (node.NodeType == XmlNodeType.Attribute)
{
// attributes have an OwnerElement, not a ParentNode; also they have
// to be matched by name, not found by position
return String.Format(
"{0}/@{1}",
GetXPathToNode(((XmlAttribute)node).OwnerElement),
node.Name
);
}
if (node.ParentNode == null)
{
// the only node with no parent is the root node, which has no path
return "";
}
// the path to a node is the path to its parent, plus "/node()[n]", where
// n is its position among its siblings.
return String.Format(
"{0}/node()[{1}]",
GetXPathToNode(node.ParentNode),
GetNodePosition(node)
);
}
Comme vous pouvez le voir, j'ai piraté un moyen pour elle de trouver des attributs.
Jon a glissé dans lequel sa version alors que j'écrivais le mien. Il y a quelque chose au sujet de son code qui va me faire grogner un peu maintenant, et je m'en excuse à l'avance si on dirait que je suis bizutage sur Jon. (Je ne suis pas. Je suis assez sûr que la liste des choses qu'il a à apprendre de moi, c'est extrêmement court.) Mais je pense que le point que je vais faire est assez important pour toute personne qui travaille avec XML à penser.
Je soupçonne que Jon solution est le fruit de quelque chose que je vois beaucoup de développeurs: la pensée de documents XML, comme les arbres d'éléments et d'attributs. Je pense que cela provient en grande partie de développeurs dont la principale utilisation de XML est un format de sérialisation, parce que tous les XML ils sont utilisés à l'utilisation est structuré de cette manière. Vous pouvez repérer ces développeurs car ils utilisent les termes de "nœud" et "élément" de façon interchangeable. Ce qui les amène à trouver des solutions qui traitent tous les autres types de nœuds comme des cas particuliers. (J'ai été un de ces gars à moi-même pour un temps très long.)
Cela se sent comme c'est une hypothèse simplificatrice pendant que vous êtes à le faire. Mais il n'est pas. Il rend les problèmes plus difficiles et code plus complexe. Il vous conduit à contourner les morceaux de la technologie XML (comme l' node()
fonction XPath) qui sont spécifiquement conçus pour traiter tous les types de nœuds, de manière générique.
Il y a un drapeau rouge dans la Jon de code qui me ferait de la requête dans une revue de code, même si je ne sais pas quelles sont les conditions, et que l' GetElementsByTagName
. Chaque fois que je vois que la méthode à utiliser, la question qui saute à l'esprit est de toujours "pourquoi a-t-il d'être un élément?" Et la réponse est très souvent "oh, est-ce le code nécessaire pour gérer les nœuds de texte trop?"
Je connais, mon ancien billet, mais la version que j’aimais le plus (celle avec les noms) était défectueuse: quand un nœud parent a des nœuds avec des noms différents, il cesse de compter l’index après avoir trouvé le premier nom de nœud ne correspondant pas.
Voici ma version corrigée:
/// <summary>
/// Gets the X-Path to a given Node
/// </summary>
/// <param name="node">The Node to get the X-Path from</param>
/// <returns>The X-Path of the Node</returns>
public string GetXPathToNode(XmlNode node)
{
if (node.NodeType == XmlNodeType.Attribute)
{
// attributes have an OwnerElement, not a ParentNode; also they have
// to be matched by name, not found by position
return String.Format("{0}/@{1}", GetXPathToNode(((XmlAttribute)node).OwnerElement), node.Name);
}
if (node.ParentNode == null)
{
// the only node with no parent is the root node, which has no path
return "";
}
// Get the Index
int indexInParent = 1;
XmlNode siblingNode = node.PreviousSibling;
// Loop thru all Siblings
while (siblingNode != null)
{
// Increase the Index if the Sibling has the same Name
if (siblingNode.Name == node.Name)
{
indexInParent++;
}
siblingNode = siblingNode.PreviousSibling;
}
// the path to a node is the path to its parent, plus "/node()[n]", where n is its position among its siblings.
return String.Format("{0}/{1}[{2}]", GetXPathToNode(node.ParentNode), node.Name, indexInParent);
}
Mon 10p vaut un hybride de réponses de Robert et Corey. Je ne peux réclamer un crédit que pour la saisie des lignes de code supplémentaires.
private static string GetXPathToNode(XmlNode node)
{
if (node.NodeType == XmlNodeType.Attribute)
{
// attributes have an OwnerElement, not a ParentNode; also they have
// to be matched by name, not found by position
return String.Format(
"{0}/@{1}",
GetXPathToNode(((XmlAttribute)node).OwnerElement),
node.Name
);
}
if (node.ParentNode == null)
{
// the only node with no parent is the root node, which has no path
return "";
}
//get the index
int iIndex = 1;
XmlNode xnIndex = node;
while (xnIndex.PreviousSibling != null) { iIndex++; xnIndex = xnIndex.PreviousSibling; }
// the path to a node is the path to its parent, plus "/node()[n]", where
// n is its position among its siblings.
return String.Format(
"{0}/node()[{1}]",
GetXPathToNode(node.ParentNode),
iIndex
);
}
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.