Holen Sie sich das XPath zu einem XElement?
Frage
Ich habe eine XElement bekommt tief innerhalb eines Dokuments. In Anbetracht der XElement (und XDocument?), Gibt es eine Erweiterungsmethode seine volle zu erhalten (das heißt absolut, zum Beispiel /root/item/element/child
) XPath?
z. myXElement.GetXPath ()?
EDIT: Okay, sieht aus wie ich etwas sehr Wichtiges übersehen. Hoppla! Der Index des Elements muss berücksichtigt werden. Siehe meine letzte Antwort für die vorgeschlagene Lösung korrigiert.
Lösung
Die Erweiterungen Methoden:
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.");
}
}
Und der 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);
}
}
}
}
Und Beispielausgabe:
/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]
Das soll dies regeln. Nein?
Andere Tipps
ich den Code von Chris aktualisiert Rechnung Namespacepräfixe zu nehmen. Nur die GetAbsoluteXPath Methode modifiziert wird.
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.");
}
}
Lassen Sie mich zu dieser Klasse meine letzte Änderung teilen. Basicaly schließt sie Index, wenn Element keine Geschwister und enthält Namespaces mit local-name hat () Operator hat war ich Probleme mit dem Namespacepräfix haben.
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.");
}
}
Dies ist eigentlich ein Duplikat diese Frage. Während es als Antwort nicht markiert ist, href="https://stackoverflow.com/questions/241238/how-to-get-xpath-from-an-xmlnode-instance-c#241492"> das Verfahren in ist der einzige Weg, der eindeutig den XPath zu einem Knoten in einem XML-Dokument zu formulieren, die immer unter allen Umständen funktionieren. (Es funktioniert auch für alle Knotentypen, nicht nur Elemente.)
Wie Sie sehen können, produziert die XPath es ist hässlich und abstrakt. aber es geht auf die Bedenken, die viele Antworter hier angesprochen haben. Die meisten der hier gemachten Vorschläge erzeugen einen XPath, die, wenn das Originaldokument zu suchen, verwendet wird, wird eine Reihe von einem oder mehreren Knoten erzeugen, die den Zielknoten enthält. Es ist das „oder mehr“ das ist das Problem. Zum Beispiel, wenn ich eine XML-Darstellung eines DataSet haben, die naive XPath zu einem Element des spezifischen DataRow, /DataSet1/DataTable1
, kehrt auch die Elemente von allen anderen DataRows in der Datatable. Sie können nicht, dass Mehrdeutigkeiten ohne etwas zu wissen, wie die XML forumlated ist (wie, ist es ein Primärschlüsselelement?).
Aber /node()[1]/node()[4]/node()[11]
, gibt es nur einen Knoten, der es jemals zurückkehren wird, egal was passiert.
Im Rahmen eines ich eine Erweiterungsmethode entwickeln eine einfache XPath zu erzeugen ein Element. Es ist ähnlich wie die gewählte Antwort, aber unterstützt XAttribute, XText, XCData und XComment zusätzlich zu XElement. Es ist erhältlich als Code nuget , Projektseite hier: xmlspecificationcompare.codeplex.com
Wenn Sie nach etwas suchen nativ von .NET zur Verfügung gestellt ist die Antwort nein. Sie müßten Ihre eigene Erweiterungsmethode schreiben, dies zu tun.
Es kann mehrere XPaths sein, die auf das gleiche Element führen, so die einfachste XPath zu finden, die zu dem Knoten führt, ist nicht trivial.
Das heißt, es ist ganz einfach einen XPath an den Knoten zu finden. step up einfach den Knotenbaum, bis Sie den Wurzelknoten lesen und die Knotennamen kombinieren, und Sie haben einen gültigen XPath.
Mit dem „full xpath“ Ich nehme an, Sie da die Anzahl der XPaths eine einfache Kette von Tags bedeuten, die potentiell jedes Element sein könnte passen könnten sehr groß.
Das Problem hier ist, dass es sehr schwer ist, wenn nicht ausdrücklich unmöglich, einen bestimmten XPath zu bauen, die reversibel zurück auf das gleiche Element verfolgen werden - ist, dass eine Bedingung
Wenn „nein“, dann vielleicht Sie eine Abfrage durch rekursiv Looping mit Verweis auf die aktuelle Elemente parentNode bauen könnte. Wenn „ja“, dann wirst du zu betrachten erstreckt, dass durch Querverweise für Indexposition innerhalb Geschwister-Sets, referecing ID-ähnlicher Eigenschaften, wenn sie existieren, und dies wird sehr abhängig sein, auf dem XSD, wenn eine allgemeine Lösung möglich.
Microsoft hat eine Erweiterung Methode, dies zu tun, da .NET Framework bereitgestellt 3,5:
http://msdn.microsoft. com / en-us / library / bb156083 (v = VS.100) aspx
Fügen Sie einfach ein mit den folgenden Methoden System.Xml.XPath
und ruft:
-
XPathSelectElement
: wählen Sie ein einzelnes Element -
XPathSelectElements
: Wählen Sie Elemente und zurück alsIEnumerable<XElement>
-
XPathEvaluate
: select Knoten (nicht nur Elemente, sondern auch Text, Kommentare etc.) und zurück alsIEnumerable<object>