001package org.jsoup.select; 002 003import org.jsoup.helper.Validate; 004import org.jsoup.internal.QueryMode; 005import org.jsoup.nodes.Element; 006import org.jsoup.nodes.Node; 007import org.jsoup.parser.TokenQueue; 008import org.jspecify.annotations.Nullable; 009 010import java.util.Collection; 011import java.util.HashSet; 012import java.util.stream.Stream; 013 014/** 015 Find elements and nodes using CSS selectors. 016 017 <h2 id="using-selectors">Using selectors</h2> 018 <p>Use selectors to find elements by tag, class, attribute, text, or their position in the document. Pass a query to 019 {@link Element#select(String)} to get matching elements, or {@link Element#selectFirst(String)} to get the first match.</p> 020 <pre class="selector-example">{@code Elements links = doc.select("a[href]"); 021Elements images = doc.select("article img"); 022Element heading = doc.selectFirst("article > h1");}</pre> 023 <p>Combine selectors to narrow your search: {@code a.download[href$=.zip]} finds links with the {@code download} class 024 whose URLs end in {@code .zip}. Try queries in the 025 <a href="https://try.jsoup.org/?utm_source=jsoup&utm_medium=javadoc">Try jsoup online playground</a>. 026 To retrieve nodes such as text and comments, use {@link Element#selectNodes(String)}.</p> 027 <p>jsoup 1.24.1 introduces an opt-in Web query mode with changes to matching behavior; see 028 <a href="#query-compatibility">Query compatibility changes</a> and the 029 <a href="https://jsoup.org/news/selector-migration?utm_source=jsoup&utm_medium=javadoc">selector migration guide</a>.</p> 030 031 <h2 id="selector-syntax">Selector syntax</h2> 032 <p>A selector combines simple selectors, such as {@code .header[title]}, with combinators, such as {@code >}, to 033 describe relationships between elements. The universal selector {@code *} is implicit: 034 {@code .header} and {@code *.header} are equivalent.</p> 035 <p>See <a href="#element-attribute-selectors">element and attribute selectors</a>, 036 <a href="#combinators">combinators</a>, <a href="#logical-selectors">logical selectors</a>, 037 <a href="#structural-selectors">structural selectors</a>, <a href="#text-regex-selectors">text, regex, and index selectors</a>, 038 and <a href="#node-selectors">node selectors</a>.</p> 039 040 <h3 id="element-attribute-selectors">Element and attribute selectors</h3> 041 <div class="selector-table"> 042 <table class="syntax"> 043 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 044 <tr><td><code>*</code></td><td>any element</td><td><code>*</code></td></tr> 045 <tr><td><code>tag</code></td><td>elements with the given tag name</td><td><code>div</code></td></tr> 046 <tr><td><code>*|E</code></td><td>elements of type E in any namespace (including non-namespaced)</td><td><code>*|name</code> finds <code><dc:name></code> and <code><name></code> elements</td></tr> 047 <tr><td><code>ns|E</code></td><td>elements of type E in the namespace <i>ns</i></td><td><code>dc|name</code> finds <code><dc:name></code> elements</td></tr> 048 <tr><td><code>ns|*</code></td><td>all elements in the namespace <i>ns</i></td><td><code>dc|*</code> finds <code><dc:p></code> and <code><dc:img></code> elements</td></tr> 049 <tr><td><code>#id</code></td><td>elements with attribute ID of "id"</td><td><code>div#wrap</code>, <code>#logo</code></td></tr> 050 <tr><td><code>.class</code></td><td>elements with a class name of "class"</td><td><code>div.left</code>, <code>.result</code></td></tr> 051 <tr><td><code>[attr]</code></td><td>elements with an attribute named "attr" (with any value)</td><td><code>a[href]</code>, <code>[title]</code></td></tr> 052 <tr><td><code>[^attrPrefix]</code></td><td>elements with an attribute name starting with "attrPrefix". Use to find elements with HTML5 datasets</td><td><code>[^data-]</code>, <code>div[^data-]</code></td></tr> 053 <tr><td><code>[attr=val]</code></td><td>elements with an attribute named "attr", and value equal to "val"</td><td><code>img[width="500"]</code>, <code>a[rel=nofollow]</code></td></tr> 054 <tr><td><code>[attr!=val]</code></td><td>elements whose attribute value differs from "val", including elements without the attribute</td><td><code>a[rel!=nofollow]</code></td></tr> 055 <tr><td><code>[attr^=valPrefix]</code></td><td>elements with an attribute named "attr", and value starting with "valPrefix"</td><td><code>a[href^="http:"]</code></td></tr> 056 <tr><td><code>[attr$=valSuffix]</code></td><td>elements with an attribute named "attr", and value ending with "valSuffix"</td><td><code>img[src$=".png"]</code></td></tr> 057 <tr><td><code>[attr*=valContaining]</code></td><td>elements with an attribute named "attr", and value containing "valContaining"</td><td><code>a[href*="/search/"]</code></td></tr> 058 <tr><td><code>[attr~=<em>regex</em>]</code></td><td>elements with an attribute value matching the regular expression; see <a href="#query-compatibility">the upcoming change to token matching</a></td><td><code>img[src~=(?i)\.(png|jpe?g)]</code></td></tr> 059 <tr><td><code>[attr|=val]</code></td><td>matches an exact value or that value followed by a hyphen</td><td><code>[lang|=en]</code> matches "en" and "en-US"</td></tr> 060 <tr><td><code>[attr=val i]</code>, <code>[attr=val s]</code></td><td><code>i</code> ignores ASCII case in the attribute value; <code>s</code> requires exact case. Neither changes attribute-name matching. Also applies to substring and hyphen operators; see <a href="#preview-case">case matching</a></td><td><code>[data-sku="AbC123" i]</code>, <code>[type="TEXT" s]</code></td></tr> 061 <tr><td><code>:matchesAttr(attr, regex)</code></td><td>elements with an attribute value containing a regex match</td><td><code>img:matchesAttr(src, (?i)\.(png|jpe?g))</code></td></tr> 062 <tr><td><code>[*]</code></td><td>elements with any attribute</td><td><code>p[*]</code> finds <code>p</code> elements that have at least one attribute; <code>p:not([*])</code> finds those with no attributes</td></tr> 063 </table> 064 </div> 065 066 <h3 id="combinators">Combinators</h3> 067 <div class="selector-table"> 068 <table class="syntax"> 069 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 070 <tr><td><code>E F</code></td><td>an F element descended from an E element</td><td><code>div a</code>, <code>.logo h1</code></td></tr> 071 <tr><td><code>E {@literal >} F</code></td><td>an F direct child of E</td><td><code>ol {@literal >} li</code></td></tr> 072 <tr><td><code>E + F</code></td><td>an F element immediately preceded by sibling E</td><td><code>li + li</code>, <code>div.head + div</code></td></tr> 073 <tr><td><code>E ~ F</code></td><td>an F element preceded by sibling E</td><td><code>h1 ~ p</code></td></tr> 074 <tr><td><code>E, F, G</code></td><td>all matching elements E, F, or G</td><td><code>a[href], div, h3</code></td></tr> 075 </table> 076 </div> 077 078 <h3 id="logical-selectors">Logical selectors</h3> 079 <div class="selector-table"> 080 <table class="syntax"> 081 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 082 <tr><td><code>:has(<em>selector</em>)</code></td><td>elements that contain at least one element matching the <em>selector</em></td><td><code>div:has(p)</code> finds <code>div</code>s that contain <code>p</code> elements.<br><code>div:has(> a)</code> selects <code>div</code> elements that have at least one direct child <code>a</code> element.<br><code>section:has(h1, h2)</code> finds <code>section</code> elements that contain a <code>h1</code> or a <code>h2</code> element</td></tr> 083 <tr><td><code>:is(<em>selector list</em>)</code></td><td>elements that match any of the selectors in the selector list</td><td><code>:is(h1, h2, h3, h4, h5, h6)</code> finds any heading element.<br><code>:is(section, article) > :is(h1, h2)</code> finds a <code>h1</code> or <code>h2</code> that is a direct child of a <code>section</code> or an <code>article</code></td></tr> 084 <tr><td><code>:not(<em>selector</em>)</code></td><td>elements that do not match the <em>selector</em>. See also {@link Elements#not(String)}</td><td><code>div:not(.logo)</code> finds all divs that do not have the "logo" class.<p><code>div:not(:has(div))</code> finds divs that do not contain divs.</p></td></tr> 085 </table> 086 </div> 087 088 <h3 id="structural-selectors">Structural selectors</h3> 089 <div class="selector-table"> 090 <table class="syntax"> 091 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 092 <tr><td><code>:scope</code></td><td>The element you query from, or the document element (usually <code>html</code>) when querying a Document</td><td><code>:scope > p</code> finds direct child paragraphs</td></tr> 093 <tr><td><code>:root</code></td><td>The tree's root element: the document element, or the topmost element of a detached tree</td><td><code>:root</code></td></tr> 094 <tr><td><code>:nth-child(<em>a</em>n+<em>b</em>)</code></td><td>elements at child positions <em>a</em>n+<em>b</em>, for any integer n starting at zero. Positions start at 1 and count element siblings. Use <code>odd</code> for <code>2n+1</code>, or <code>even</code> for <code>2n</code></td><td><code>tr:nth-child(2n+1)</code> finds odd rows; <code>li:nth-child(5)</code> finds the fifth list item; <code>:nth-child(10n-1)</code> finds positions 9, 19, 29, etc.</td></tr> 095 <tr><td><code>:nth-last-child(<em>a</em>n+<em>b</em>)</code></td><td>like <code>:nth-child()</code>, counting from the last sibling</td><td><code>tr:nth-last-child(-n+2)</code> finds the last two rows</td></tr> 096 <tr><td><code>:nth-of-type(<em>a</em>n+<em>b</em>)</code></td><td>like <code>:nth-child()</code>, counting only siblings of the same type</td><td><code>img:nth-of-type(2n+1)</code></td></tr> 097 <tr><td><code>:nth-last-of-type(<em>a</em>n+<em>b</em>)</code></td><td>like <code>:nth-of-type()</code>, counting from the last sibling</td><td><code>img:nth-last-of-type(2n+1)</code></td></tr> 098 <tr><td><code>:first-child</code></td><td>elements that are the first child of some other element.</td><td><code>div {@literal >} p:first-child</code></td></tr> 099 <tr><td><code>:last-child</code></td><td>elements that are the last child of some other element.</td><td><code>ol {@literal >} li:last-child</code></td></tr> 100 <tr><td><code>:first-of-type</code></td><td>elements that are the first sibling of its type in the list of children of its parent element</td><td><code>dl dt:first-of-type</code></td></tr> 101 <tr><td><code>:last-of-type</code></td><td>elements that are the last sibling of its type in the list of children of its parent element</td><td><code>tr {@literal >} td:last-of-type</code></td></tr> 102 <tr><td><code>:only-child</code></td><td>elements with a parent and no element siblings</td><td><code>p:only-child</code></td></tr> 103 <tr><td><code>:only-of-type</code></td><td>elements with a parent and no siblings of the same type</td><td><code>p:only-of-type</code></td></tr> 104 <tr><td><code>:empty</code></td><td>elements that contain no child elements or nodes, with the exception of blank text nodes, comments, XML declarations, and doctype declarations. In other words, it matches elements that are effectively empty of meaningful content.</td><td><code>li:not(:empty)</code></td></tr> 105 </table> 106 </div> 107 108 <h3 id="text-regex-selectors">Text, regex, and index selectors</h3> 109 <p>These jsoup extensions match text or zero-based sibling indexes.</p> 110 <div class="selector-table"> 111 <table class="syntax"> 112 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 113 <tr><td><code>:lt(<em>n</em>)</code></td><td>elements whose sibling index is less than <em>n</em></td><td><code>td:lt(3)</code> finds the first 3 cells of each row</td></tr> 114 <tr><td><code>:gt(<em>n</em>)</code></td><td>elements whose sibling index is greater than <em>n</em></td><td><code>td:gt(1)</code> finds cells after skipping the first two</td></tr> 115 <tr><td><code>:eq(<em>n</em>)</code></td><td>elements whose sibling index is equal to <em>n</em></td><td><code>td:eq(0)</code> finds the first cell of each row</td></tr> 116 <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contain the specified text. The search is case insensitive. The text may appear in the found element, or any of its descendants. The text is whitespace normalized. <p>To find content that includes parentheses, escape those with a {@code \}.</p></td><td><code>p:contains(jsoup)</code> finds p elements containing the text "jsoup".<p>{@code p:contains(hello \(there\))} finds p elements containing the text "Hello (There)"</p></td></tr> 117 <tr><td><code>:containsOwn(<em>text</em>)</code></td><td>elements that directly contain the specified text. The search is case insensitive. The text must appear in the found element, not any of its descendants.</td><td><code>p:containsOwn(jsoup)</code> finds p elements with own text "jsoup".</td></tr> 118 <tr><td><code>:containsData(<em>data</em>)</code></td><td>elements that contain the specified <em>data</em>. The contents of {@code script} and {@code style} elements, and {@code comment} nodes (etc) are considered data nodes, not text nodes. The search is case insensitive. The data may appear in the found element, or any of its descendants.</td><td><code>script:containsData(jsoup)</code> finds script elements containing the data "jsoup".</td></tr> 119 <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements containing the exact text, including case, spaces, and newlines, in themselves or their descendants. A <code>br</code> contributes a newline. Escape parentheses with a backslash</td><td><code>p:containsWholeText(Hello)</code> finds paragraphs containing "Hello", but not "hello"</td></tr> 120 <tr><td><code>:containsWholeOwnText(<em>text</em>)</code></td><td>like <code>:containsWholeText()</code>, but matches only the element's own text</td><td><code>p:containsWholeOwnText(Hello)</code> finds paragraphs containing "Hello" in their own text, excluding child elements' text</td></tr> 121 <tr><td><code>:matches(<em>regex</em>)</code></td><td>elements containing <b>whitespace normalized</b> text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matches(\d+)</code> finds table cells containing digits. <code>div:matches((?i)login)</code> finds divs containing the text, case insensitively.</td></tr> 122 <tr><td><code>:matchesOwn(<em>regex</em>)</code></td><td>like <code>:matches()</code>, but matches only the element's own whitespace-normalized text</td><td><code>td:matchesOwn(\d+)</code></td></tr> 123 <tr><td><code>:matchesWholeText(<em>regex</em>)</code></td><td>elements containing <b>non-normalized</b> whole text that matches the specified regular expression. The text may appear in the found element, or any of its descendants.</td><td><code>td:matchesWholeText(\s{2,})</code> finds table cells containing a run of at least two whitespace characters.</td></tr> 124 <tr><td><code>:matchesWholeOwnText(<em>regex</em>)</code></td><td>elements whose own <b>non-normalized</b> whole text matches the specified regular expression. The text must appear in the found element, not any of its descendants.</td><td><code>td:matchesWholeOwnText(\n\d+)</code> finds table cells directly containing digits following a newline.</td></tr> 125 </table> 126 </div> 127 128 <h3 id="node-selectors">Node selectors</h3> 129 <p>These jsoup extensions select text, comments, and other nodes directly. Combine a node type with a content filter, 130 such as {@code ::text:contains(foo)}, and use combinators to restrict where it appears:</p> 131 <pre class="selector-example">{@code Nodes<TextNode> text = doc.selectNodes("p ::text:contains(foo)", TextNode.class);}</pre> 132 <p>This returns individual text nodes containing "foo" inside paragraphs. To find paragraphs containing that text, 133 use the regular element selector {@code p:contains(foo)}.</p> 134 <p>Use {@link Element#selectNodes(String)} to retrieve nodes, or the 135 {@link Element#selectNodes(String, Class) typed overload} to collect a specific node type. 136 With {@link Element#select(String)}, use node selectors inside {@code :has()} when their presence or content determines 137 which elements you want. For example, this finds divs with a direct child comment containing "TODO":</p> 138 <pre class="selector-example">{@code Elements annotated = doc.select("div:has(> ::comment:contains(TODO))");}</pre> 139 <div class="selector-table"> 140 <table class="syntax"> 141 <tr><th>Pattern</th><th>Matches</th><th>Example</th></tr> 142 <tr><td><code>::node</code></td><td>Any {@link org.jsoup.nodes.Node}, including elements and leaf nodes</td><td><code>section > ::node</code> finds all direct child nodes of sections, including elements, text, and comments</td></tr> 143 <tr><td><code>::leafnode</code></td><td>Non-element nodes, such as text, comments, data, and processing instructions. An empty element is still an element, not a leaf node</td><td><code>article ::leafnode:not(:blank)</code> finds non-element nodes with nonblank values inside articles</td></tr> 144 <tr><td><code>::text</code></td><td>{@link org.jsoup.nodes.TextNode Text nodes}, including CDATA sections</td><td><code>p ::text:contains(foo)</code> finds individual text nodes containing "foo" inside paragraphs</td></tr> 145 <tr><td><code>::comment</code></td><td>{@link org.jsoup.nodes.Comment Comments}; content filters search the comment data, without the enclosing markup</td><td><code>::comment:contains(TODO)</code> finds comments containing "TODO"</td></tr> 146 <tr><td><code>::data</code></td><td>{@link org.jsoup.nodes.DataNode Data nodes}, such as the contents of HTML <code>script</code> and <code>style</code> elements, which are stored as data rather than text nodes</td><td><code>script > ::data:contains(analytics)</code> finds script data containing "analytics"</td></tr> 147 <tr><td><code>::cdata</code></td><td>{@link org.jsoup.nodes.CDataNode CDATA sections}; content filters search the section's text, without the CDATA delimiters</td><td><code>::cdata:contains(config)</code> finds CDATA sections containing "config"</td></tr> 148 <tr><td><code>::pi</code></td><td>{@link org.jsoup.nodes.ProcessingInstruction Processing instructions}; content filters search the instruction's data</td><td><code>::pi:contains(country-options)</code> finds instructions with "country-options" in their data</td></tr> 149 <tr><td><code>::pi(<em>target</em>)</code></td><td>Processing instructions with the specified case-sensitive target</td><td><code>::pi(xml-stylesheet)</code> finds instructions such as <code><?xml-stylesheet href="style.xsl"?></code></td></tr> 150 <tr><td><code>::pi[...]</code></td><td>Processing instructions whose data matches an attribute selector. The instruction data is parsed into attributes for comparison</td><td><code>::pi(marker)[name=country-options]</code> finds instructions such as <code><?marker name="country-options"?></code></td></tr> 151 </table> 152 </div> 153 <p>Append {@code :contains(text)}, {@code :matches(regex)}, or {@code :blank} to any of these node selectors to test its 154 {@link org.jsoup.nodes.Node#nodeValue() value}. For text, comments, data, and CDATA, this is the node's content; for 155 processing instructions, its data; for elements, their own text, excluding child elements' text. 156 Node values retain whitespace; {@code :contains()} normalizes whitespace in the search text and ignores case. 157 {@code :matches()} searches the value using a regular expression, and {@code :blank} tests for an empty or whitespace-only value.</p> 158 <p>For example, {@code ::comment:matches(\d+)} finds comments containing digits, and {@code ::text:not(:blank)} finds 159 text nodes with nonblank content. These filters can be combined with {@code :not()} and with each other.</p> 160 161 <h2 id="matching-rules">Matching rules</h2> 162 <p>In the default legacy mode, {@link Element#select(String)} searches the element it is called on and its descendants. 163 For example, given {@code <div id="outer"><div id="inner"></div></div>}, {@code outer.select("div")} returns both divs, 164 while {@code outer.select(":scope > div")} returns only the inner div. 165 See <a href="#query-compatibility">Query compatibility changes</a> for Web mode's matching rules.</p> 166 <p>{@link Element#is(String)} checks whether the element itself matches the query. 167 {@link Element#closest(String)} finds the nearest matching element: itself or an ancestor.</p> 168 <p>Ancestor matching stops at the element you query from. For example, {@code panel.select("body p")} does not match 169 paragraphs when the body is outside the panel.</p> 170 <p>Tag and attribute <b>names</b> ignore ASCII case. Class names and literal attribute <b>values</b> ignore Unicode case; IDs match 171 exactly. Structural selectors such as {@code :nth-of-type()} group siblings by their normalized tag names.</p> 172 <p>Use {@code i} on an attribute selector to ignore ASCII case in its value, or {@code s} for exact case; see 173 <a href="#preview-case">case matching</a> for examples. These flags do not affect attribute names or the default regex 174 interpretation of {@code ~=}; use regex inline flags such as {@code (?i)} there.</p> 175 <p>Text selectors keep the matching rules described in their entries above. Regex selectors use Java's 176 {@link java.util.regex.Pattern} syntax and flags.</p> 177 178 <h3 id="escaping-selectors">Escaping and regular expressions</h3> 179 <p>The examples in the tables are CSS queries. When writing a query in a Java string literal, double each backslash.</p> 180 <p>Escape CSS special characters with a backslash. To match {@code <p id="i.d">}, the CSS query is {@code #i\.d}; 181 in Java, write {@code document.select("#i\\.d")}. You can also use {@link Selector#escapeCssIdentifier(String)} to 182 escape a tag, ID, or class name.</p> 183 <p>Regex arguments are passed to the regex parser without CSS unescaping. For example, the CSS query 184 {@code img:matchesAttr(src, (?i)\.(png|jpe?g))} is written in Java as 185 {@code document.select("img:matchesAttr(src, (?i)\\.(png|jpe?g))")}. 186 Whitespace immediately after the argument comma is skipped; use {@code \x20} for a leading literal space.</p> 187 <p>Regex arguments use balanced parentheses. Escape literal parentheses that would unbalance the argument, including 188 inside character classes: {@code :matchesAttr(x, ^[^\)]+$)} is written in Java as 189 {@code document.select(":matchesAttr(x, ^[^\\)]+$)")}. Surrounding quotes are part of the regex, not an argument wrapper.</p> 190 <p>When searching for literal text through a regex selector, use {@link java.util.regex.Pattern#quote(String)} to quote it: 191 {@code String query = "div:matches(" + Pattern.quote(text) + ")";}.</p> 192 193 <h2 id="query-compatibility">Query compatibility changes</h2> 194 <p>jsoup 1.24.1 introduces Web query behavior that follows CSS and Web DOM standards more closely. 195 Set the {@code jsoup.queryMode} system property before running queries to choose:</p> 196 <ul> 197 <li>{@code legacy}: use the query behavior of earlier jsoup releases.</li> 198 <li>{@code web}: use the updated CSS and Web DOM matching rules described below.</li> 199 <li>{@code check}: return legacy results while comparing supported queries against Web mode and reporting differences. 200 See <a href="#query-check">Checking query compatibility</a>.</li> 201 </ul> 202 <p>The default is {@code legacy} in 1.24.1 and changes to {@code web} in 1.25.1, when {@code legacy} and {@code check} 203 remain available. In 1.26.1, Web behavior is the only option and the property is removed. See the 204 <a href="https://jsoup.org/news/selector-migration">selector migration guide</a> for compatibility testing and query updates.</p> 205 206 <h3 id="preview-matching">What changes in Web mode</h3> 207 <div class="selector-table"> 208 <table class="syntax query-modes"> 209 <tr><th>Query behavior</th><th>Legacy mode</th><th>Web mode</th></tr> 210 <tr><td>Query results</td><td>Include the starting element if it matches.</td><td>Include only its descendants.</td></tr> 211 <tr><td>Selector relationships</td><td>Ancestor matching stops at the element you query from.</td><td>Can match ancestors and siblings outside its subtree; results stay inside the element you query from.</td></tr> 212 <tr><td>Tag and attribute names</td><td>Ignore ASCII case.</td><td>HTML names ignore ASCII case. Names in XML and foreign content (such as SVG and MathML) match exactly, as do attribute names on processing instructions.</td></tr> 213 <tr><td>Classes and IDs</td><td>Classes ignore Unicode case; IDs match exactly.</td><td>Match exactly, except class and ID selectors in quirks documents ignore ASCII case.</td></tr> 214 <tr><td>Literal attribute values without a flag</td><td>Ignore Unicode case.</td><td>Match exactly, except HTML attributes with case-insensitive values, such as {@code type}, {@code rel}, and {@code lang}, ignore ASCII case.</td></tr> 215 <tr><td>{@code ~=}</td><td>Matches an attribute value using a regex.</td><td>Matches a complete whitespace-separated token. Supports {@code i} and {@code s} flags.</td></tr> 216 <tr><td>{@code :nth-of-type()} and related selectors</td><td>Compares normalized tag names.</td><td>Compares namespace and exact local name.</td></tr> 217 </table> 218 </div> 219 <p>These rules apply to {@code select}, {@code selectFirst}, their stream and node variants, and the convenience finders on 220 {@link Element}: {@code getElementsBy*}, text finders, {@code getElementById}, and {@code getAllElements}. 221 {@link Element#hasClass(String)} uses the selected mode's class case rules. {@code getElementById} continues to match IDs exactly, 222 including in quirks documents. Text and regex selectors retain their value-matching rules.</p> 223 224 <h3 id="query-check">Checking query compatibility</h3> 225 <p>Check mode helps you find queries that may behave differently when switching to Web mode. 226 Enable it before running your queries:</p> 227 <pre class="selector-example">{@code System.setProperty("jsoup.queryMode", "check");}</pre> 228 <p>Each supported query runs in both modes and returns the legacy result. If the results differ, or the query fails in 229 Web mode, jsoup writes a diagnostic to {@code stderr} identifying the query and up to two caller locations. 230 Comparisons check which elements are returned and their order, not just the number of matches.</p> 231 <pre class="log-output">{@code jsoup query compat: select("div"): legacy=2, web=1; results differ; at example.ProductParser.items(ProductParser.java:42)}</pre> 232 <p>(Checks cover common string-query methods such as {@code select()} and {@code selectFirst()}; streams, node queries, 233 {@linkplain Evaluator supplied evaluators}, and convenience finders run without checks.)</p> 234 <p>Use check mode when running tests or assessing compatibility; running queries twice adds overhead. 235 After reviewing and updating affected queries, switch to {@code web}.</p> 236 237 <h3 id="preview-searches">Query results and scope</h3> 238 <p>{@code :scope} refers to the element the query is called on, or the document element when querying a Document. 239 In Web mode, adding {@code :scope} does not include that element in query results. {@link Element#is(String)} and 240 {@link Element#closest(String)} still test the supplied element; their {@code :scope} refers to that element. 241 {@link Elements#not(String)} tests each supplied element directly.</p> 242 <p>To include the starting element when it matches, filter {@link Element#stream()} with 243 {@link Evaluator#asPredicate(Element)}.</p> 244 <p>In Web mode, inside {@code :has()}, a leading combinator starts from each element being tested, but {@code :scope} still 245 refers to the element the query is called on. For example, {@code outer.select("div:has(> p)")} finds divs with a direct child 246 paragraph; {@code outer.select("div:has(:scope > p)")} finds none, because a paragraph inside a descendant div cannot also 247 be a direct child of outer.</p> 248 249 <h3 id="preview-regex">Attribute regular expressions</h3> 250 <p>Use {@code :matchesAttr(attr, regex)} for attribute regexes in either mode, as shown in the 251 <a href="#element-attribute-selectors">syntax table</a>. See <a href="#escaping-selectors">Escaping and regular expressions</a> 252 for Java string examples and escaping rules.</p> 253 254 <h3 id="preview-case">Case matching and attribute values</h3> 255 <p>In Web mode, the document's parser selects HTML rules (the default) or XML rules with 256 {@link org.jsoup.parser.Parser#xmlParser()}. The <a href="#preview-matching">comparison table</a> distinguishes attribute 257 <b>names</b> from <b>values</b>. The {@code i} and {@code s} flags affect values only: {@code [data-sku="AbC123" i]} ignores ASCII 258 case, while {@code [type="TEXT" s]} requires exact case. Both flags work in every query mode from 1.24.1.</p> 259 <p>For case-insensitive class matching in Web mode, use {@code [class~="card" i]} instead of {@code .card}. 260 See the <a href="#preview-matching">comparison table</a> for default class and ID case rules.</p> 261 <p>Unquoted attribute values are supported in both modes, including numbers, URLs, and text with spaces. In Web mode, 262 a final separate {@code i} or {@code s} is a case flag. Quote literal values ending in either token, 263 such as {@code [data-x="two words i"]}.</p> 264 265 @see Element#select(String css) 266 @see Element#selectFirst(String css) 267 @see Element#select(Evaluator eval) 268 @see Element#selectNodes(String css) 269 @see Element#selectNodes(String css, Class nodeType) 270 @see Elements#select(String css) 271 @see Element#selectXpath(String xpath) */ 272public class Selector { 273 // not instantiable 274 private Selector() {} 275 276 /** 277 Find Elements matching the CSS query. 278 279 @param query CSS selector 280 @param root root element to descend into 281 @return matching elements, empty if none 282 @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. 283 */ 284 public static Elements select(String query, Element root) { 285 Validate.notEmpty(query); 286 QueryMode mode = QueryMode.current(); 287 Elements result = select(QueryParser.parse(query, mode == QueryMode.Web), root); 288 if (mode == QueryMode.Check) 289 QueryCompat.check(query, result, evaluator -> select(evaluator, root)); 290 return result; 291 } 292 293 /** 294 Find Elements matching the Evaluator. 295 296 @param evaluator CSS Evaluator 297 @param root root (context) element to start from 298 @return matching elements, empty if none 299 */ 300 public static Elements select(Evaluator evaluator, Element root) { 301 Validate.notNull(evaluator); 302 Validate.notNull(root); 303 return Collector.collect(evaluator, root); 304 } 305 306 /** 307 Finds a Stream of elements matching the CSS query. 308 309 @param query CSS selector 310 @param root root element to descend into 311 @return a Stream of matching elements, empty if none 312 @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. 313 @since 1.19.1 314 */ 315 public static Stream<Element> selectStream(String query, Element root) { 316 Validate.notEmpty(query); 317 return selectStream(evaluatorOf(query), root); 318 } 319 320 /** 321 Finds a Stream of elements matching the evaluator. 322 323 @param evaluator CSS selector 324 @param root root element to descend into 325 @return matching elements, empty if none 326 @since 1.19.1 327 */ 328 public static Stream<Element> selectStream(Evaluator evaluator, Element root) { 329 Validate.notNull(evaluator); 330 Validate.notNull(root); 331 return Collector.stream(evaluator, root); 332 } 333 334 /** 335 Find elements matching the query, across multiple roots. Elements will be deduplicated (in the case of 336 overlapping hierarchies). 337 338 @param query CSS selector 339 @param roots root elements to descend into 340 @return matching elements, empty if none 341 */ 342 public static Elements select(String query, Iterable<Element> roots) { 343 Validate.notEmpty(query); 344 Validate.notNull(roots); 345 QueryMode mode = QueryMode.current(); 346 Elements result = selectAcrossRoots(QueryParser.parse(query, mode == QueryMode.Web), roots); 347 if (mode == QueryMode.Check && roots instanceof Collection) 348 QueryCompat.check(query, result, evaluator -> selectAcrossRoots(evaluator, roots)); 349 return result; 350 } 351 352 /** Reuses a compiled query across roots without changing its captured mode or result order. */ 353 static Elements selectAcrossRoots(Evaluator evaluator, Iterable<Element> roots) { 354 Elements elements = new Elements(); 355 HashSet<Element> seenElements = new HashSet<>(); // dedupe elements by identity, as .equals is == 356 357 for (Element root : roots) { 358 selectStream(evaluator, root) 359 .filter(seenElements::add) 360 .forEach(elements::add); 361 } 362 363 return elements; 364 } 365 366 // exclude set. package open so that Elements can implement .not() selector. 367 static Elements filterOut(Collection<Element> elements, Collection<Element> outs) { 368 Elements output = new Elements(); 369 for (Element el : elements) { 370 boolean found = false; 371 for (Element out : outs) { 372 if (el.equals(out)) { 373 found = true; 374 break; 375 } 376 } 377 if (!found) 378 output.add(el); 379 } 380 return output; 381 } 382 383 /** 384 Find the first Element that matches the query. 385 386 @param cssQuery CSS selector 387 @param root root element to descend into 388 @return the matching element, or <b>null</b> if none. 389 */ 390 public static @Nullable Element selectFirst(String cssQuery, Element root) { 391 Validate.notEmpty(cssQuery); 392 QueryMode mode = QueryMode.current(); 393 Element result = Collector.findFirst(QueryParser.parse(cssQuery, mode == QueryMode.Web), root); 394 if (mode == QueryMode.Check) 395 QueryCompat.check("selectFirst", cssQuery, result, evaluator -> Collector.findFirst(evaluator, root)); 396 return result; 397 } 398 399 /** 400 Find the first element matching the query, across multiple roots. 401 402 @param cssQuery CSS selector 403 @param roots root elements to descend into 404 @return the first matching element, or {@code null} if none 405 @since 1.19.1 406 */ 407 public static @Nullable Element selectFirst(String cssQuery, Iterable<Element> roots) { 408 Validate.notEmpty(cssQuery); 409 Validate.notNull(roots); 410 QueryMode mode = QueryMode.current(); 411 Element result = selectFirst(QueryParser.parse(cssQuery, mode == QueryMode.Web), roots); 412 if (mode == QueryMode.Check && roots instanceof Collection) 413 QueryCompat.check("selectFirst", cssQuery, result, evaluator -> selectFirst(evaluator, roots)); 414 return result; 415 } 416 417 /** Finds the first result across roots using an already compiled query. */ 418 private static @Nullable Element selectFirst(Evaluator evaluator, Iterable<Element> roots) { 419 for (Element root : roots) { 420 Element first = Collector.findFirst(evaluator, root); 421 if (first != null) return first; 422 } 423 424 return null; 425 } 426 427 /** 428 Given a CSS identifier (such as a tag, ID, or class), escape any CSS special characters that would otherwise not be 429 valid in a selector. 430 431 @see <a href="https://www.w3.org/TR/cssom-1/#serialize-an-identifier">CSS Object Model, serialize an identifier</a> 432 @since 1.20.1 433 */ 434 public static String escapeCssIdentifier(String in) { 435 return TokenQueue.escapeCssIdentifier(in); 436 } 437 438 /** 439 Consume a CSS identifier (ID or class) off the queue. 440 <p>Note: For backwards compatibility this method supports improperly formatted CSS identifiers, e.g. {@code 1} instead 441 of {@code \31}.</p> 442 443 @return The unescaped identifier. 444 @throws IllegalArgumentException if an invalid escape sequence was found. 445 @see <a href="https://www.w3.org/TR/css-syntax-3/#consume-name">CSS Syntax Module Level 3, Consume an ident sequence</a> 446 @see <a href="https://www.w3.org/TR/css-syntax-3/#typedef-ident-token">CSS Syntax Module Level 3, ident-token</a> 447 @since 1.20.1 448 */ 449 public static String unescapeCssIdentifier(String in) { 450 try (TokenQueue tq = new TokenQueue(in)) { 451 return tq.consumeCssIdentifier(); 452 } 453 } 454 455 /** 456 Parse a CSS query into an Evaluator. If you are evaluating the same query repeatedly, it may be more efficient to 457 parse it once and reuse the Evaluator. The evaluator captures the current value of 458 {@code jsoup.queryMode}; later property changes do not alter it. 459 460 @param css CSS query 461 @return Evaluator 462 @see Selector selector query syntax 463 @throws Selector.SelectorParseException if the CSS query is invalid 464 @since 1.21.1 465 */ 466 public static Evaluator evaluatorOf(String css) { 467 return QueryParser.parse(css); 468 } 469 470 public static class SelectorParseException extends IllegalStateException { 471 public SelectorParseException(String msg) { 472 super(msg); 473 } 474 475 public SelectorParseException(String msg, Object... msgArgs) { 476 super(String.format(msg, msgArgs)); 477 } 478 479 public SelectorParseException(Throwable cause, String msg, Object... msgArgs) { 480 super(String.format(msg, msgArgs), cause); 481 } 482 } 483}