Note that there are some explanatory texts on larger screens.

plurals
  1. POSelecting (siblings) between two tags using XPath (in .NET)
    primarykey
    data
    text
    <p>I'm using .NET 3.5 (C#) and the <a href="http://www.codeplex.com/htmlagilitypack" rel="nofollow noreferrer">HTML Agility Pack</a> to do some web scraping. Some fields that I need to extract are structured as paragraphs within which components are separated by line-break tags. I'd like to be able to select out the individual components between the line-breaks. Each component may be formed from multiple elements (i.e., it may not be just a single string). Example:</p> <pre><code>&lt;h3&gt;Section title&lt;/h3&gt; &lt;p&gt; &lt;b&gt;Component A&lt;/b&gt;&lt;br /&gt; Component B &lt;i&gt;includes&lt;/i&gt; &lt;strong&gt;multiple elements&lt;/strong&gt;&lt;br /&gt; Component C &lt;/p&gt; </code></pre> <p>I'd like to select out</p> <pre><code>&lt;b&gt;Component A&lt;/b&gt; </code></pre> <p>Then:</p> <pre><code>Component B &lt;i&gt;includes&lt;/i&gt; &lt;strong&gt;multiple elements&lt;/strong&gt; </code></pre> <p>And then:</p> <pre><code>Component C </code></pre> <p>There may be more (<code>&lt;br /&gt;</code> separated) components, too.</p> <p>I can easily get the first component with:</p> <pre><code>p/br[1]/preceding-sibling::node() </code></pre> <p>I can also easily get the last component with:</p> <pre><code>p/br[2]/following-sibling::node() </code></pre> <p>But I haven't been able to work out how to extract the set of nodes /between/ two other tags (that is, nodes which are siblings but which precede node X and follow node Y).</p> <p>The alternative is to scan through the elements manually &ndash; if that's the easiest way to do it then that's what I'll do, but XPath has so-far impressed me with its terseness, so I'm hoping there's a way of doing this, too.</p> <h3>Edit</h3> <p>Since I need to cope with the situation of having more than 3 components, it seems the answer will require multiple XPath calls at a minimum, so I shall proceed with a solution based on that (this is the answer I have 'accepted'). AakashM's answer has also helped me with my understanding of XPath, and so I have voted it up.</p> <p>Thank you all for your help! I hope I can return the favour one day.</p> <h3>Edit 2</h3> <p>The new answer provided by Dimitre Novatchev, with some tweaks, does indeed work correctly.</p> <p>Solution:</p> <pre><code>int i = 0; do { yield return para.SelectNodes(String.Format( "node()[not(self::br) and count(preceding-sibling::br) = {0}]", i)); ++i; } while (para.SelectSingleNode(String.Format("br[{0}]", i)) != null); </code></pre> <p>I should note that this loop is somewhat inefficient due to the repeated XPath queries to find out whether there are any more <code>br</code> tags. In my case that inefficiency is not a problem, but be aware if you want to use this answer in some other situation (then again, if you did want to do this in a performance sensitive situation you should probably be scanning through manually anyway rather than using XPath).</p> <p>And full test code (a modified version of the test code helpfully included by AakashM):</p> <pre><code>using System; using System.Collections.Generic; using System.Xml; namespace TestXPath { class Program { static void Main(string[] args) { XmlDocument doc = new XmlDocument(); doc.LoadXml(@" &lt;x&gt; &lt;h3&gt;Section title&lt;/h3&gt; &lt;p&gt; &lt;b&gt;Component A&lt;/b&gt;&lt;br /&gt; Component B &lt;i&gt;includes&lt;/i&gt; multiple &lt;strong&gt;elements&lt;/strong&gt;&lt;br /&gt; Component C &lt;/p&gt; &lt;/x&gt; "); foreach (var nodes in SplitOnLineBreak(doc.SelectSingleNode("x/p"))) { Dump(nodes); Console.WriteLine(); } Console.ReadLine(); } private static IEnumerable&lt;XmlNodeList&gt; SplitOnLineBreak(XmlNode para) { int i = 0; do { yield return para.SelectNodes(String.Format( "node()[not(self::br) and count(preceding-sibling::br) = {0}]", i)); ++i; } while (para.SelectSingleNode(String.Format("br[{0}]", i)) != null); } private static void Dump(XmlNodeList nodes) { foreach (XmlNode node in nodes) { Console.WriteLine(string.Format("--&gt;{0}&lt;---", node.OuterXml)); } } } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload