Get the Xpath to an Xelement

Get the XPath to an XElement?

The extensions methods:

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

And the 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);
}
}
}
}

And sample output:

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

That should settle this. No?

How to use XPath with XElement or LINQ?

To use XPath with LINQ to XML add a using declaration for System.Xml.XPath, this will bring the extension methods of System.Xml.XPath.Extensions into scope.

In your example:

var value = (string)xml.XPathEvaluate("/response/data/hash");

How can I perform an XPath selection on an XElement that was extracted from a larger XDocument?

Unless you make a copy or clone of the XML structure, you can simply use a path relative to the context node like

b.XPathSelectSelectElement("C")

This should return the <C /> element.

The / axis always selects the document node of the XML.

Get the XPath to an XElement?

The extensions methods:

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

And the 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);
}
}
}
}

And sample output:

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

That should settle this. No?

List of XPaths on XElement

The first thing, we use XDocument, you can use XElementbut you need to change the XPath expression for xpathList,
more details:

1 - You have an error in all xml test case, Error tag not closed, then correct them:

<Request>
<Header>
<Error>
<Details>
<StackTrace>trace</StackTrace>
</Details>
</Error>
</Header>
</Request>

2 - you have an error in this expression //Request/Header/Error[0]/Details/ErrorMesage/text(),
to get the first Error use 1 not 0, you miss alsos in ErrorMesage:

List<string> xPathList = new List<string>
{
"//Request/Header/Error/text()",
"//Request/Header/Error[1]/Details/ErrorMessage/text()",
"//Request/Header/Error/Details/@code",
"//Request/Header/Error/Details/StackTrace/text()"
};

3 - Change GetValue, like the following code:

private string GetValue(List<string> xPaths, XDocument xml)
{
string stringValue = string.Empty;
foreach (string xpath in xPaths)
{
var value = xml.XPathEvaluate(xpath);

foreach (XObject xObject in (IEnumerable)value)
{
if (xObject is XElement)
{
return ((XElement)xObject).Value;
}
else if (xObject is XAttribute)
{
return ((XAttribute)xObject).Value;
}
else
{
return ((XText)xObject).Value;
}
}
}

return stringValue;
}

4 - Test

string result = GetValue(xPathList, XDocument.Parse(xmlString));

5 - Demo:

 string xml1 = @"<Request>
<Header>
<Error>
<Details>
<StackTrace>trace</StackTrace>
</Details>
</Error>
</Header>
</Request>";

string xml2 = @"<Request>
<Header>
<Error>
<Details>
<ErrorMessage>This is the Error Message1.</ErrorMessage>
</Details>
</Error>
<Error>
<Details>
<ErrorMessage>This is the Error Message2.</ErrorMessage>
</Details>
</Error>
</Header>
</Request>";

string xml3 = @"<Request>
<Header>
<Error>This is the error message.</Error>
</Header>
</Request>";

string xml4 = @"<Request>
<Header>
<Error>
<Details code=""123"">
<StackTrace>trace</StackTrace>
</Details>
</Error>
</Header>
</Request>";

string xml5 = @"<Request>
<Header>
<Error>
<Details>
<ErrorMessage>This is the Error Message.</ErrorMessage>
</Details>
</Error>
</Header>
</Request>";

List<string> xPathList = new List<string>
{
"//Request/Header/Error/text()",
"//Request/Header/Error[1]/Details/ErrorMessage/text()",
"//Request/Header/Error/Details/@code",
"//Request/Header/Error/Details/StackTrace/text()"
};

string result1 = GetValue(xPathList, XDocument.Parse(xml1));
Console.WriteLine($"xml1 : {result1}");

string result2 = GetValue(xPathList, XDocument.Parse(xml2));
Console.WriteLine($"xml2 : {result2}");

string result3 = GetValue(xPathList, XDocument.Parse(xml3));
Console.WriteLine($"xml3 : {result3}");

string result4 = GetValue(xPathList, XDocument.Parse(xml4));
Console.WriteLine($"xml4 : {result4}");

string result5 = GetValue(xPathList, XDocument.Parse(xml5));
Console.WriteLine($"xml5 : {result5}");

6 - Result

xml1 : trace
xml2 : This is the Error Message1.
xml3 : This is the error message.
xml4 : 123
xml5 : This is the Error Message.

I hope this help

How to get absolute xpath by passing the xElement name in XmlDocument or XDocument?

For simple XML without namespaces try this:

public static string GetPath(XElement element)
{
return string.Join("/", element.AncestorsAndSelf().Reverse()
.Select(e =>
{
var index = GetIndex(e);

if (index == 1)
{
return e.Name.LocalName;
}

return string.Format("{0}[{1}]", e.Name.LocalName, GetIndex(e));
}));

}

private static int GetIndex(XElement element)
{
var i = 1;

if (element.Parent == null)
{
return 1;
}

foreach (var e in element.Parent.Elements(element.Name.LocalName))
{
if (e == element)
{
break;
}

i++;
}

return i;
}

Get XElement with XPathSelectElement

Code works fine

var xmlString = @"<root>
<Acct>
<Id>
<IBAN>TestIban</IBAN>
</Id>
<Ccy>EUR</Ccy>
<Svcr>
<FinInstnId>
<BIC>TestBic</BIC>
<ClrSysMmbId>
<ClrSysId>
<Cd>TestSysId</Cd>
</ClrSysId>
<MmbId>TestMemberId</MmbId>
</ClrSysMmbId>
<Nm>TestName</Nm>
<PstlAdr>
<AdrLine>TestAdrLine</AdrLine>
</PstlAdr>
<Othr>
<Id>OtherId</Id>
<Issr>Issr</Issr>
</Othr>
</FinInstnId>
</Svcr>
</Acct>
</root>";

XDocument doc = XDocument.Parse(xmlString);
XElement acct = doc.XPathSelectElement("root/Acct");
XElement element = acct.XPathSelectElement("Svcr/FinInstnId/BIC");
Console.WriteLine(element.Value);
//print "TestBic"

are you sure acct contain right XElement?

update with namespace

XDocument doc = XDocument.Parse(xmlString);
var reader = doc.CreateReader();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02");
XElement acct = doc.XPathSelectElement("//ns:Acct", nsmgr);
XElement element = acct.XPathSelectElement("*[local-name() = 'Svcr']/*[local-name() = 'FinInstnId']/*[local-name() = 'BIC']");
var anotherWay = acct.XPathSelectElement("ns:Svcr/ns:FinInstnId/ns:BIC", nsmgr);

with local-name() you can ignore namespace

XPath: Selecting a child from an XElement in C#

Whenever namespaces are used (in your case in the <html>) you need to define the namespace when searching for nodes:

Using LINQ to XML
(In my opinion the easier and cleaner solution)

// Create a XNamespace instance for the default namespace
XNamespace xhtml = "http://www.w3.org/1999/xhtml";

// Select the node using LINQ to XML
var bodyByLinq = doc.Element(xhtml + "html").Element(xhtml + "body");

Using XPath

// Create a namespace manager
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(new NameTable());

// Define your default namespace including a prefix to be used later in the XPath expression
namespaceManager.AddNamespace("xhtml", "http://www.w3.org/1999/xhtml");

// Select the node using XPath
var bodyByXPath = doc.XPathSelectElement("/xhtml:html/xhtml:body", namespaceManager);

Update: Improved my answer as the example provided was not correct

Getting Xelement.elements() using a path

You need to use XElement.XPathSelectElement method if you wanna select an element using XPath, Elements method takes an element name not a Path.

XElement element = parentElement.XPathSelectElement("Map/Pr");

Or use parentElement.Descendants("Pr") if you want to get all Pr elements.

iterating XElements and then using XPath to extract child element values gets wrong value

Use a relative path return element.XPathSelectElement("id").Value;, with //id you search down from / which is the document node.



Related Topics



Leave a reply



Submit