How to: Write a Query that Finds Elements Based on Context
Sometimes you might have to write a query that selects elements based on their context. You might want to filter based on preceding or following sibling elements. You might want to filter based on child or ancestor elements.
You can do this by writing a query and using the results of the query in the where clause. If you have to first test against null, and then test the value, it is more convenient to do the query in a let clause, and then use the results in the where clause.
Example
The following example selects all p elements that are immediately followed by a ul element.
XElement doc = XElement.Parse(@"<Root>
<p id=""1""/>
<ul>abc</ul>
<Child>
<p id=""2""/>
<notul/>
<p id=""3""/>
<ul>def</ul>
<p id=""4""/>
</Child>
<Child>
<p id=""5""/>
<notul/>
<p id=""6""/>
<ul>abc</ul>
<p id=""7""/>
</Child>
</Root>");
IEnumerable<XElement> items =
from e in doc.Descendants("p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name.LocalName == "ul"
select e;
foreach (XElement e in items)
Console.WriteLine("id = {0}", (string)e.Attribute("id"));
Dim doc As XElement = _
<Root>
<p id='1'/>
<ul>abc</ul>
<Child>
<p id='2'/>
<notul/>
<p id='3'/>
<ul>def</ul>
<p id='4'/>
</Child>
<Child>
<p id='5'/>
<notul/>
<p id='6'/>
<ul>abc</ul>
<p id='7'/>
</Child>
</Root>
Dim items As IEnumerable(Of XElement) = _
From e In doc...<p> _
Let z = e.ElementsAfterSelf().FirstOrDefault() _
Where z IsNot Nothing AndAlso z.Name.LocalName = "ul" _
Select e
For Each e As XElement In items
Console.WriteLine("id = {0}", e.@<id>)
Next
This code produces the following output:
id = 1
id = 3
id = 6
The following example shows the same query for XML that is in a namespace. For more information, see Working with XML Namespaces.
XElement doc = XElement.Parse(@"<Root xmlns='http://www.adatum.com'>
<p id=""1""/>
<ul>abc</ul>
<Child>
<p id=""2""/>
<notul/>
<p id=""3""/>
<ul>def</ul>
<p id=""4""/>
</Child>
<Child>
<p id=""5""/>
<notul/>
<p id=""6""/>
<ul>abc</ul>
<p id=""7""/>
</Child>
</Root>");
XNamespace ad = "http://www.adatum.com";
IEnumerable<XElement> items =
from e in doc.Descendants(ad + "p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name == ad.GetName("ul")
select e;
foreach (XElement e in items)
Console.WriteLine("id = {0}", (string)e.Attribute("id"));
Imports <xmlns='http://www.adatum.com'>
Module Module1
Sub Main()
Dim doc As XElement = _
<Root>
<p id='1'/>
<ul>abc</ul>
<Child>
<p id='2'/>
<notul/>
<p id='3'/>
<ul>def</ul>
<p id='4'/>
</Child>
<Child>
<p id='5'/>
<notul/>
<p id='6'/>
<ul>abc</ul>
<p id='7'/>
</Child>
</Root>
Dim items As IEnumerable(Of XElement) = _
From e In doc...<p> _
Let z = e.ElementsAfterSelf().FirstOrDefault() _
Where z IsNot Nothing AndAlso z.Name = GetXmlNamespace().GetName("ul") _
Select e
For Each e As XElement In items
Console.WriteLine("id = {0}", e.@<id>)
Next
End Sub
End Module
This code produces the following output:
id = 1
id = 3
id = 6