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&amp;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&amp;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>&lt;dc:name&gt;</code> and <code>&lt;name&gt;</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>&lt;dc:name&gt;</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>&lt;dc:p&gt;</code> and <code>&lt;dc:img&gt;</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(&gt; 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) &gt; :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 &gt; 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 &gt; ::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 &gt; ::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>&lt;?xml-stylesheet href="style.xsl"?&gt;</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>&lt;?marker name="country-options"?&gt;</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}