[
  {
    "path": ".gitattributes",
    "content": "*   text=auto eol=lf\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: maven\n    directory: /\n    schedule:\n      interval: weekly\n    cooldown:\n      default-days: 12\n    ignore:\n      # Jetty 9.x needed for JDK8 compatibility; it still receives security updates. Only used in tests.\n      - dependency-name: \"org.eclipse.jetty:jetty-server\"\n        update-types: [\"version-update:semver-major\"]\n      - dependency-name: \"org.eclipse.jetty:jetty-servlet\"\n        update-types: [\"version-update:semver-major\"]\n      # Et tu, junit? Keep us on 5, as 6 has min JDK17 - https://docs.junit.org/6.0.0-RC3/release-notes/#release-notes-6.0.0-M1\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [\"version-update:semver-major\"]\n\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: weekly\n    cooldown:\n      default-days: 12\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n        # choosing to run a reduced set of LTS, current, and next, to balance coverage and execution time\n        java: [8, 17, 25]\n      fail-fast: false\n    name: Test JDK ${{ matrix.java }}, ${{ matrix.os }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Set up JDK ${{ matrix.java }}\n        uses: actions/setup-java@v5\n        with:\n          java-version: ${{ matrix.java }}\n          distribution: 'zulu'\n          cache: 'maven'\n\n      - name: Maven Compile\n        run: mvn -X compile -B --file pom.xml\n\n      - name: Maven Verify\n        run: mvn -X verify -B --file pom.xml\n...\n"
  },
  {
    "path": ".github/workflows/cifuzz.yml",
    "content": "name: CIFuzz\non: [pull_request]\njobs:\n  Fuzzing:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Build Fuzzers\n      id: build\n      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master\n      with:\n        oss-fuzz-project-name: 'jsoup'\n        dry-run: false\n        language: jvm\n    - name: Run Fuzzers\n      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master\n      with:\n        oss-fuzz-project-name: 'jsoup'\n        fuzz-seconds: 600\n        dry-run: false\n        language: jvm\n    - name: Upload Crash\n      uses: actions/upload-artifact@v7\n      if: failure() && steps.build.outcome == 'success'\n      with:\n        name: artifacts\n        path: ./out/artifacts\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: CodeQL\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  codeql:\n    runs-on: ubuntu-latest\n    name: \"CodeQL\"\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          java-version: 17\n          distribution: 'temurin'\n          cache: 'maven'\n      - name: CodeQL Initialization\n        uses: github/codeql-action/init@v4\n        with:\n          languages: java\n          queries: +security-and-quality\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v4\n      - name: CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\njsoup.iml\njsoup.ipr\njsoup.iws\ntarget/\n.classpath\n.project\n.settings/\n*Thrash*\nbin/\n.vscode/\n.java-version\n.DS_Store\n"
  },
  {
    "path": "CHANGES.md",
    "content": "# jsoup Changelog\n\n## 1.22.2 (PENDING)\n\n### Improvements\n* Expanded and clarified `NodeTraversor` support for in-place DOM rewrites during `NodeVisitor.head()`. Current-node edits such as `remove`, `replace`, and `unwrap` now recover more predictably, while traversal stays within the original root subtree. This makes single-pass tree cleanup and normalization visitors easier to write, for example when unwrapping presentational elements or replacing text nodes as you walk the DOM. [#2472](https://github.com/jhy/jsoup/issues/2472)\n* Documentation: clarified that a configured `Cleaner` may be reused across concurrent threads, and that shared `Safelist` instances should not be mutated while in use. [#2473](https://github.com/jhy/jsoup/issues/2473)\n\n### Bug Fixes\n* Android (R8/ProGuard): added a rule to ignore the optional `re2j` dependency when not present. [#2459](https://github.com/jhy/jsoup/issues/2459)\n* Fixed a `NodeTraversor` regression in 1.21.2 where removing or replacing the current node during `head()` could revisit the replacement node and loop indefinitely. The traversal docs now also clarify which inserted nodes are visited in the current pass. [#2472](https://github.com/jhy/jsoup/issues/2472)\n* Parsing during charset sniffing no longer fails if an advisory `available()` call throws `IOException`, as seen on JDK 8 `HttpURLConnection`. [#2474](https://github.com/jhy/jsoup/issues/2474)\n* `Cleaner` no longer makes relative URL attributes in the input document absolute when cleaning or validating a `Document`. URL normalization now applies only to the cleaned output, and `Safelist.isSafeAttribute()` is side effect free. [#2475](https://github.com/jhy/jsoup/issues/2475)\n* `Cleaner` no longer duplicates enforced attributes when the input `Document` preserves attribute case. A case-variant source attribute is now replaced by the enforced attribute in the cleaned output. [#2476](https://github.com/jhy/jsoup/issues/2476)\n* If a per-request SOCKS proxy is configured, jsoup now avoids using the JDK `HttpClient`, because the JDK would silently ignore that proxy and attempt to connect directly. Those requests now fall back to the legacy `HttpURLConnection` transport instead, which does support SOCKS. [#2468](https://github.com/jhy/jsoup/issues/2468)\n\n## 1.22.1 (2026-Jan-01)\n\n### Improvements\n* Added support for using the `re2j` regular expression engine for regex-based CSS selectors (e.g. `[attr~=regex]`, `:matches(regex)`), which ensures linear-time performance for regex evaluation. This allows safer handling of arbitrary user-supplied query regexes. To enable, add the `com.google.re2j` dependency to your classpath, e.g.:\n```xml\n  <dependency>\n    <groupId>com.google.re2j</groupId>\n    <artifactId>re2j</artifactId>\n    <version>1.8</version>\n  </dependency>\n ```\n  (If you already have that dependency in your classpath, but you want to keep using the Java regex engine, you can disable re2j via `System.setProperty(\"jsoup.useRe2j\", \"false\")`.) You can confirm that the re2j engine has been enabled correctly by calling `org.jsoup.helper.Regex.usingRe2j()`. [#2407](https://github.com/jhy/jsoup/pull/2407)\n\n* Added an instance method `Parser#unescape(String, boolean)` that unescapes HTML entities using the parser's configuration (e.g. to support error tracking), complementing the existing static utility `Parser.unescapeEntities(String, boolean)`. [#2396](https://github.com/jhy/jsoup/pull/2396)\n* Added a configurable maximum parser depth (to limit the number of open elements on stack) to both HTML and XML parsers. The HTML parser now defaults to a depth of 512 to match browser behavior, and protect against unbounded stack growth, while the XML parser keeps unlimited depth by default, but can opt into a limit via `org.jsoup.parser.Parser#setMaxDepth`. [#2421](https://github.com/jhy/jsoup/issues/2421)\n* Build: added CI coverage for JDK 25 [#2403](https://github.com/jhy/jsoup/pull/2403)\n* Build: added a CI fuzzer for contextual fragment parsing (in addition to existing full body HTML and XML fuzzers). [oss-fuzz #14041](https://github.com/google/oss-fuzz/pull/14041)\n\n### Changes\n* Set a removal schedule of jsoup 1.24.1 for previously deprecated APIs.\n\n### Bug Fixes\n* Previously cached child `Elements` of an `Element` were not correctly invalidated in `Node#replaceWith(Node)`, which could lead to incorrect results when subsequently calling `Element#children()`. [#2391](https://github.com/jhy/jsoup/issues/2391)\n* Attribute selector values are now compared literally without trimming. Previously, jsoup trimmed whitespace from selector values and from element attribute values, which could cause mismatches with browser behavior (e.g. `[attr=\" foo \"]`). Now matches align with the CSS specification and browser engines. [#2380](https://github.com/jhy/jsoup/issues/2380)\n* When using the JDK HttpClient, any system default proxy (`ProxySelector.getDefault()`) was ignored. Now, the system proxy is used if a per-request proxy is not set. [#2388](https://github.com/jhy/jsoup/issues/2388), [#2390](https://github.com/jhy/jsoup/pull/2390)\n* A `ValidationException` could be thrown in the adoption agency algorithm with particularly broken input. Now logged as a parse error. [#2393](https://github.com/jhy/jsoup/issues/2393)\n* Null characters in the HTML body were not consistently removed; and in foreign content were not correctly replaced. [#2395](https://github.com/jhy/jsoup/issues/2395)\n* An `IndexOutOfBoundsException` could be thrown when parsing a body fragment with crafted input. Now logged as a parse error. [#2397](https://github.com/jhy/jsoup/issues/2397), [#2406](https://github.com/jhy/jsoup/issues/2406)\n* When using StructuralEvaluators (e.g., a `parent child` selector) across many retained threads, their memoized results could also be retained, increasing memory use. These results are now cleared immediately after use, reducing overall memory consumption. [#2411](https://github.com/jhy/jsoup/issues/2411)\n* Cloning a `Parser` now preserves any custom `TagSet` applied to the parser. [#2422](https://github.com/jhy/jsoup/issues/2422), [#2423](https://github.com/jhy/jsoup/pull/2423)\n* Custom tags marked as `Tag.Void` now parse and serialize like the built-in void elements: they no longer consume following content, and the XML serializer emits the expected self-closing form. [#2425](https://github.com/jhy/jsoup/issues/2425)\n* The `<br>` element is once again classified as an inline tag (`Tag.isBlock() == false`), matching common developer expectations and its role as phrasing content in HTML, while pretty-printing and text extraction continue to treat it as a line break in the rendered output. [#2387](https://github.com/jhy/jsoup/issues/2387), [#2439](https://github.com/jhy/jsoup/issues/2439)\n* Fixed an intermittent truncation issue when fetching and parsing remote documents via `Jsoup.connect(url).get()`. On responses without a charset header, the initial charset sniff could sometimes (depending on buffering / `available()` behavior) be mistaken for end-of-stream and a partial parse reused, dropping trailing content. [#2448](https://github.com/jhy/jsoup/issues/2448)\n* `TagSet` copies no longer mutate their template during lazy lookups, preventing cross-thread `ConcurrentModificationException` when parsing with shared sessions. [#2453](https://github.com/jhy/jsoup/pull/2453)\n* Fixed parsing of `<svg>` `foreignObject` content nested within a `<p>`, which could incorrectly move the HTML subtree outside the SVG. [#2452](https://github.com/jhy/jsoup/issues/2452)\n\n### Internal Changes\n* Deprecated internal helper `org.jsoup.internal.Functions` (for removal in v1.23.1). This was previously used to support older Android API levels without full `java.util.function` coverage; jsoup now requires core library desugaring so this indirection is no longer necessary. [#2412](https://github.com/jhy/jsoup/pull/2412)\n\n## 1.21.2 (2025-Aug-25)\n\n### Changes\n* Deprecated internal (yet visible) methods `Normalizer#normalize(String, bool)` and `Attribute#shouldCollapseAttribute(Document.OutputSettings)`. These will be removed in a future version.\n* Deprecated `Connection#sslSocketFactory(SSLSocketFactory)` in favor of the new `Connection#sslContext(SSLContext)`. Using `sslSocketFactory` will force the use of the legacy `HttpUrlConnection` implementation, which does not support HTTP/2. [#2370](https://github.com/jhy/jsoup/pull/2370)\n\n### Improvements\n* When pretty-printing, if there are consecutive text nodes (via DOM manipulation), the non-significant whitespace between them will be collapsed. [#2349](https://github.com/jhy/jsoup/pull/2349).\n* Updated `Connection.Response#statusMessage()` to return a simple loggable string message (e.g. \"OK\") when using the `HttpClient` implementation, which doesn't otherwise return any server-set status message. [#2356](https://github.com/jhy/jsoup/issues/2346) \n* `Attributes#size()` and `Attributes#isEmpty()` now exclude any internal attributes (such as user data) from their count. This aligns with the attributes' serialized output and iterator. [#2369](https://github.com/jhy/jsoup/pull/2369)\n* Added `Connection#sslContext(SSLContext)` to provide a custom SSL (TLS) context to requests, supporting both the `HttpClient` and the legacy `HttUrlConnection` implementations. [#2370](https://github.com/jhy/jsoup/pull/2370)\n* Performance optimizations for DOM manipulation methods including when repeatedly removing an element's first child (`element.child(0).remove()`, and when using `Parser#parseBodyFragement()` to parse a large number of direct children. [#2373](https://github.com/jhy/jsoup/pull/2373).\n\n### Bug Fixes\n* When parsing from an InputStream and a multibyte character happened to straddle a buffer boundary, the stream would not be completely read. [#2353](https://github.com/jhy/jsoup/issues/2353).\n* In `NodeTraversor`, if a last child element was removed during the `head()` call, the parent would be visited twice. [#2355](https://github.com/jhy/jsoup/issues/2355).\n* Cloning an Element that has an Attributes object would add an empty internal user-data attribute to that clone, which would cause unexpected results for `Attributes#size()` and `Attributes#isEmpty()`. [#2356](https://github.com/jhy/jsoup/issues/2356)\n* In a multithreaded application where multiple threads are calling `Element#children()` on the same element concurrently, a race condition could happen when the method was generating the internal child element cache (a filtered view of its child nodes). Since concurrent reads of DOM objects should be threadsafe without external synchronization, this method has been updated to execute atomically. [#2366](https://github.com/jhy/jsoup/issues/2366)\n* When parsing HTML with svg:script elements in SVG elements, don't enter the Text insertion mode, but continue to parse as foreign content. Otherwise, misnested HTML could then cause an IndexOutOfBoundsException. [#2374](https://github.com/jhy/jsoup/issues/2374)\n* Malformed HTML could throw an IndexOutOfBoundsException during the adoption agency. [#2377](https://github.com/jhy/jsoup/pull/2377).\n\n## 1.21.1 (2025-Jun-23)\n\n### Changes\n\n* Removed previously deprecated methods. [#2317](https://github.com/jhy/jsoup/pull/2317)\n* Deprecated the `:matchText` pseduo-selector due to its side effects on the DOM; use the new `::textnode` selector and the `Element#selectNodes(String css, Class type)` method instead. [#2343](https://github.com/jhy/jsoup/pull/2343)\n* Deprecated `Connection.Response#bufferUp()` in lieu of `Connection.Response#readFully()` which can throw a checked IOException.\n* Deprecated internal methods `Validate#ensureNotNull` (replaced by typed `Validate#expectNotNull`); protected HTML appenders from Attribute and Node.\n* If you happen to be using any of the deprecated methods, please take the opportunity now to migrate away from them, as they will be removed in a future release.\n\n### Improvements\n* Enhanced the `Selector` to support direct matching against nodes such as comments and text nodes. For example, you can now find an element that follows a specific comment: `::comment:contains(prices) + p` will select `p` elements immediately after a `<!-- prices: -->` comment. Supported types include `::node`, `::leafnode`, `::comment`, `::text`, `::data`, and `::cdata`. Node contextual selectors like `::node:contains(text)`, `:matches(regex)`, and `:blank` are also supported. Introduced `Element#selectNodes(String css)` and `Element#selectNodes(String css, Class nodeType)` for direct node selection. [#2324](https://github.com/jhy/jsoup/pull/2324)\n* Added `TagSet#onNewTag(Consumer<Tag> customizer)`: register a callback that’s invoked for each new or cloned Tag when it’s inserted into the set. Enables dynamic tweaks of tag options (for example, marking all custom tags as self-closing, or everything in a given namespace as preserving whitespace).\n* Made `TokenQueue` and `CharacterReader` autocloseable, to ensure that they will release their buffers back to the buffer pool, for later reuse.\n* Added `Selector#evaluatorOf(String css)`, as a clearer way to obtain an Evaluator from a CSS query. An alias of `QueryParser.parse(String css)`.\n* Custom tags (defined via the `TagSet`) in a foreign namespace (e.g. SVG) can be configured to parse as data tags.\n* Added `NodeVisitor#traverse(Node)` to simplify node traversal calls (vs. importing `NodeTraversor`).\n* Updated the default user-agent string to improve compatibility. [#2341](https://github.com/jhy/jsoup/issues/2341) \n* The HTML parser now allows the specific text-data type (Data, RcData) to be customized for known tags. (Previously, that was only supported on custom tags.) [#2326](https://github.com/jhy/jsoup/issues/2326).\n* Added `Connection#readFully()` as a replacement for `Connection#bufferUp()` with an explicit IOException. Similarly, added `Connection#readBody()` over `Connection#body()`. Deprecated `Connection#bufferUp()`. [#2327](https://github.com/jhy/jsoup/pull/2327) \n* When serializing HTML, the `<` and `>` characters are now escaped in attributes. This helps prevent a class of mutation XSS attacks. [#2337](https://github.com/jhy/jsoup/pull/2337)\n* Changed `Connection` to prefer using the JDK's HttpClient over HttpUrlConnection, if available, to enable HTTP/2 support by default. Users can disable via `-Djsoup.useHttpClient=false`. [#2340](https://github.com/jhy/jsoup/pull/2340)\n\n### Bug Fixes\n* The contents of a `script` in a `svg` foreign context should be parsed as script data, not text. [#2320](https://github.com/jhy/jsoup/issues/2320)\n* `Tag#isFormSubmittable()` was updating the Tag's options. [#2323](https://github.com/jhy/jsoup/issues/2323)\n* The HTML pretty-printer would incorrectly trim whitespace when text followed an inline element in a block element. [#2325](https://github.com/jhy/jsoup/issues/2325)\n* Custom tags with hyphens or other non-letter characters in their names now work correctly as Data or RcData tags. Their closing tags are now tokenized properly. [#2332](https://github.com/jhy/jsoup/issues/2332)\n* When cloning an Element, the clone would retain the source's cached child Element list (if any), which could lead to incorrect results when modifying the clone's child elements. [#2334](https://github.com/jhy/jsoup/issues/2334)\n\n## 1.20.1 (2025-Apr-29)\n\n### Changes\n\n* To better follow the HTML5 spec and current browsers, the HTML parser no longer allows self-closing tags (`<foo />`)\n  to close HTML elements by default. Foreign content (SVG, MathML), and content parsed with the XML parser, still\n  supports self-closing tags. If you need specific HTML tags to support self-closing, you can register a custom tag via\n  the `TagSet` configured in `Parser.tagSet()`, using `Tag#set(Tag.SelfClose)`. Standard void tags (such as `<img>`,\n  `<br>`, etc.) continue to behave as usual and are not affected by this\n  change. [#2300](https://github.com/jhy/jsoup/issues/2300).\n* The following internal components have been **deprecated**. If you do happen to be using any of these, please take the opportunity now to migrate away from them, as they will be removed in jsoup 1.21.1.\n  * `ChangeNotifyingArrayList`, `Document.updateMetaCharsetElement()`, `Document.updateMetaCharsetElement(boolean)`, `HtmlTreeBuilder.isContentForTagData(String)`, `Parser.isContentForTagData(String)`, `Parser.setTreeBuilder(TreeBuilder)`, `Tag.formatAsBlock()`, `Tag.isFormListed()`, `TokenQueue.addFirst(String)`, `TokenQueue.chompTo(String)`, `TokenQueue.chompToIgnoreCase(String)`, `TokenQueue.consumeToIgnoreCase(String)`, `TokenQueue.consumeWord()`, `TokenQueue.matchesAny(String...)`\n\n### Functional Improvements\n\n* Rebuilt the HTML pretty-printer, to simplify and consolidate the implementation, improve consistency, support custom\n  Tags, and provide a cleaner path for ongoing improvements. The specific HTML produced by the pretty-printer may be\n  different from previous versions. [#2286](https://github.com/jhy/jsoup/issues/2286).\n* Added the ability to define custom tags, and to modify properties of known tags, via the `TagSet` tag collection.\n  Their properties can impact both the parse and how content is\n  serialized (output as HTML or XML). [#2285](https://github.com/jhy/jsoup/issues/2285).\n* `Element.cssSelector()` will prefer to return shorter selectors by using ancestor IDs when available and unique. E.g.\n  `#id > div > p` instead of  `html > body > div > div > p` [#2283](https://github.com/jhy/jsoup/pull/2283).\n* Added `Elements.deselect(int index)`, `Elements.deselect(Object o)`, and `Elements.deselectAll()` methods to remove\n  elements from the `Elements` list without removing them from the underlying DOM. Also added `Elements.asList()` method\n  to get a modifiable list of elements without affecting the DOM. (Individual Elements remain linked to the\n  DOM.) [#2100](https://github.com/jhy/jsoup/issues/2100).\n* Added support for sending a request body from an InputStream with\n  `Connection.requestBodyStream(InputStream stream)`. [#1122](https://github.com/jhy/jsoup/issues/1122).\n* The XML parser now supports scoped xmlns: prefix namespace declarations, and applies the correct namespace to Tags and\n  Attributes. Also, added `Tag#prefix()`, `Tag#localName()`, `Attribute#prefix()`, `Attribute#localName()`, and\n  `Attribute#namespace()` to retrieve these. [#2299](https://github.com/jhy/jsoup/issues/2299).\n* CSS identifiers are now escaped and unescaped correctly to the CSS spec. `Element#cssSelector()` will emit\n  appropriately escaped selectors, and the QueryParser supports those. Added `Selector.escapeCssIdentifier()` and\n  `Selector.unescapeCssIdentifier()`. [#2297](https://github.com/jhy/jsoup/pull/2297), [#2305](https://github.com/jhy/jsoup/pull/2305)\n\n### Structure and Performance Improvements\n\n* Refactored the CSS `QueryParser` into a clearer recursive descent\n  parser. [#2310](https://github.com/jhy/jsoup/pull/2310).\n* CSS selectors with consecutive combinators (e.g. `div >> p`) will throw an explicit parse\n  exception. [#2311](https://github.com/jhy/jsoup/pull/2311).\n* Performance: reduced the shallow size of an Element from 40 to 32 bytes, and the NodeList from 32 to 24. \n  [#2307](https://github.com/jhy/jsoup/pull/2307).\n* Performance: reduced GC load of new StringBuilders when tokenizing input\n  HTML. [#2304](https://github.com/jhy/jsoup/pull/2304).\n* Made `Parser` instances threadsafe, so that inadvertent use of the same instance across threads will not lead to\n  errors. For actual concurrency, use `Parser#newInstance()` per\n  thread. [#2314](https://github.com/jhy/jsoup/pull/2314).\n\n### Bug Fixes\n\n* Element names containing characters invalid in XML are now normalized to valid XML names when\n  serializing. [#1496](https://github.com/jhy/jsoup/issues/1496).\n* When serializing to XML, characters that are invalid in XML 1.0 should be removed (not\n  encoded). [#1743](https://github.com/jhy/jsoup/issues/1743).\n* When converting a `Document` to the W3C DOM in `W3CDom`, elements with an attribute in an undeclared namespace now\n  get a declaration of `xmlns:prefix=\"undefined\"`. This allows subsequent serialization to XML via `W3CDom.asString()`\n  to succeed. [#2087](https://github.com/jhy/jsoup/issues/2087).\n* The `StreamParser` could emit the final elements of a document twice, due to how `onNodeCompleted` was fired when closing out the stack. [#2295](https://github.com/jhy/jsoup/issues/2295).\n* When parsing with the XML parser and error tracking enabled, the trailing `?` in `<?xml version=\"1.0\"?>` would\n  incorrectly emit an error. [#2298](https://github.com/jhy/jsoup/issues/2298).\n* Calling `Element#cssSelector()` on an element with combining characters in the class or ID now produces the correct output. [#1984](https://github.com/jhy/jsoup/issues/1984). \n\n## 1.19.1 (2025-Mar-04)\n\n### Changes\n\n* Added support for **http/2** requests in `Jsoup.connect()`, when running on Java 11+, via the Java HttpClient\n  implementation. [#2257](https://github.com/jhy/jsoup/pull/2257).\n  * In this version of jsoup, the default is to make requests via the HttpUrlConnection implementation: use\n    **`System.setProperty(\"jsoup.useHttpClient\", \"true\");`** to enable making requests via the HttpClient instead ,\n    which will enable http/2 support, if available. This will become the default in a later version of jsoup, so now is\n    a good time to validate it.\n  * If you are repackaging the jsoup jar in your deployment (i.e. creating a shaded- or a fat-jar), make sure to specify\n    that as a Multi-Release\n    JAR.\n  * If the `HttpClient` impl is not available in your JRE, requests will continue to be made via\n    `HttpURLConnection` (in `http/1.1` mode).\n* Updated the minimum Android API Level validation from 10 to **21**. As with previous jsoup versions, Android\n  developers need to enable core library desugaring. The minimum Java version remains Java 8.\n  [#2173](https://github.com/jhy/jsoup/pull/2173)\n* Removed previously deprecated class: `org.jsoup.UncheckedIOException` (replace with `java.io.UncheckedIOException`);\n  moved previously deprecated method `Element Element#forEach(Consumer)` to\n  `void Element#forEach(Consumer())`. [#2246](https://github.com/jhy/jsoup/pull/2246)\n* Deprecated the methods `Document#updateMetaCharsetElement(boolean)` and `Document#updateMetaCharsetElement()`, as the\n  setting had no effect. When `Document#charset(Charset)` is called, the document's meta charset or XML encoding\n  instruction is always set. [#2247](https://github.com/jhy/jsoup/pull/2247)\n\n### Improvements\n\n* When cleaning HTML with a `Safelist` that preserves relative links, the `isValid()` method will now consider these\n  links valid. Additionally, the enforced attribute `rel=nofollow` will only be added to external links when configured\n  in the safelist. [#2245](https://github.com/jhy/jsoup/pull/2245)\n* Added `Element#selectStream(String query)` and `Element#selectStream(Evaluator)` methods, that return a `Stream` of\n  matching elements. Elements are evaluated and returned as they are found, and the stream can be\n  terminated early. [#2092](https://github.com/jhy/jsoup/pull/2092)\n* `Element` objects now implement `Iterable`, enabling them to be used in enhanced for loops.\n* Added support for fragment parsing from a `Reader` via\n  `Parser#parseFragmentInput(Reader, Element, String)`. [#1177](https://github.com/jhy/jsoup/issues/1177)\n* Reintroduced CLI executable examples, in `jsoup-examples.jar`. [#1702](https://github.com/jhy/jsoup/issues/1702)\n* Optimized performance of selectors like `#id .class` (and other similar descendant queries) by around 4.6x, by better\n  balancing the Ancestor evaluator's cost function in the query\n  planner. [#2254](https://github.com/jhy/jsoup/issues/2254)\n* Removed the legacy parsing rules for `<isindex>` tags, which would autovivify a `form` element with labels. This is no\n  longer in the spec.\n* Added `Elements.selectFirst(String cssQuery)` and `Elements.expectFirst(String cssQuery)`, to select the first\n  matching element from an `Elements` list.  [#2263](https://github.com/jhy/jsoup/pull/2263/)\n* When parsing with the XML parser, XML Declarations and Processing Instructions are directly handled, vs bouncing\n  through the HTML parser's bogus comment handler. Serialization for non-doctype declarations no longer end with a\n  spurious `!`. [#2275](https://github.com/jhy/jsoup/pull/2275)\n* When converting parsed HTML to XML or the W3C DOM, element names containing `<` are normalized to `_` to ensure valid\n  XML. For example, `<foo<bar>` becomes `<foo_bar>`, as XML does not allow `<` in element names, but HTML5\n  does. [#2276](https://github.com/jhy/jsoup/pull/2276)\n* Reimplemented the HTML5 Adoption Agency Algorithm to the current spec. This handles mis-nested formating / structural elements. [#2278](https://github.com/jhy/jsoup/pull/2278)\n\n### Bug Fixes\n\n* If an element has an `;` in an attribute name, it could not be converted to a W3C DOM element, and so subsequent XPath\n  queries could miss that element. Now, the attribute name is more completely\n  normalized. [#2244](https://github.com/jhy/jsoup/issues/2244)\n* For backwards compatibility, reverted the internal attribute key for doctype names to \n  \"name\". [#2241](https://github.com/jhy/jsoup/issues/2241)\n* In `Connection`, skip cookies that have no name, rather than throwing a validation\n  exception. [#2242](https://github.com/jhy/jsoup/issues/2242)\n* When running on JDK 1.8, the error `java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;`\n  could be thrown when calling `Response#body()` after parsing from a URL and the buffer size was\n  exceeded. [#2250](https://github.com/jhy/jsoup/pull/2250)\n* For backwards compatibility, allow `null` InputStream inputs to `Jsoup.parse(InputStream stream, ...)`, by returning\n  an empty `Document`. [#2252](https://github.com/jhy/jsoup/issues/2252)\n* A `template` tag containing an `li` within an open `li` would be parsed incorrectly, as it was not recognized as a\n  \"special\" tag (which have additional processing rules). Also, added the SVG and MathML namespace tags to the list of\n  special tags. [#2258](https://github.com/jhy/jsoup/issues/2258)\n* A `template` tag containing a `button` within an open `button` would be parsed incorrectly, as the \"in button scope\"\n  check was not aware of the `template` element. Corrected other instances including MathML and SVG elements,\n  also. [#2271](https://github.com/jhy/jsoup/issues/2271)\n* An `:nth-child` selector with a negative digit-less step, such as `:nth-child(-n+2)`, would be parsed incorrectly as a\n  positive step, and so would not match as expected. [#1147](https://github.com/jhy/jsoup/issues/1147)\n* Calling `doc.charset(charset)` on an empty XML document would throw an\n  `IndexOutOfBoundsException`. [#2266](https://github.com/jhy/jsoup/issues/2266)\n* Fixed a memory leak when reusing a nested `StructuralEvaluator` (e.g., a selector ancestor chain like `A B C`) by\n  ensuring cache reset calls cascade to inner members. [#2277](https://github.com/jhy/jsoup/issues/2277)\n* Concurrent calls to `doc.clone().append(html)` were not supported. When a document was cloned, its `Parser` was not cloned but was a shallow copy of the original parser. [#2281](https://github.com/jhy/jsoup/issues/2281)\n\n## 1.18.3 (2024-Dec-02)\n\n### Bug Fixes\n\n* When serializing to XML, attribute names containing `-`, `.`, or digits were incorrectly marked as invalid and\n  removed. [2235](https://github.com/jhy/jsoup/issues/2235)\n\n## 1.18.2 (2024-Nov-27)\n\n### Improvements\n\n* Optimized the throughput and memory use throughout the input read and parse flows, with heap allocations and GC \n  down between -6% and -89%, and throughput improved up to +143% for small inputs. Most inputs sizes will see \n  throughput increases of ~ 20%. These performance improvements come through recycling the backing `byte[]` and `char[]` \n  arrays used to read and parse the input. [2186](https://github.com/jhy/jsoup/pull/2186) \n* Speed optimized `html()` and `Entities.escape()` when the input contains UTF characters in a supplementary plane, by\n  around 49%. [2183](https://github.com/jhy/jsoup/pull/2183)\n* The form associated elements returned by `FormElement.elements()` now reflect changes made to the DOM,\n  subsequently to the original parse. [2140](https://github.com/jhy/jsoup/issues/2140)\n* In the `TreeBuilder`, the `onNodeInserted()` and `onNodeClosed()` events are now also fired for the outermost /\n  root `Document` node. This enables source position tracking on the Document node (which was previously unset). And\n  it also enables the node traversor to see the outer Document node. [2182](https://github.com/jhy/jsoup/pull/2182)\n* Selected Elements can now be position swapped inline using\n  `Elements#set()`. [2212](https://github.com/jhy/jsoup/issues/2212)\n\n### Bug Fixes\n\n* `Element.cssSelector()` would fail if the element's class contained a `*`\n  character. [2169](https://github.com/jhy/jsoup/issues/2169)\n* When tracking source ranges, a text node following an invalid self-closing element may be left\n  untracked. [2175](https://github.com/jhy/jsoup/issues/2175)\n* When a document has no doctype, or a doctype not named `html`, it should be parsed in Quirks\n  Mode. [2197](https://github.com/jhy/jsoup/issues/2197)\n* With a selector like `div:has(span + a)`, the `has()` component was not working correctly, as the inner combining\n  query caused the evaluator to match those against the outer's siblings, not\n  children. [2187](https://github.com/jhy/jsoup/issues/2187)\n* A selector query that included multiple `:has()` components in a nested `:has()` might incorrectly\n  execute. [2131](https://github.com/jhy/jsoup/issues/2131)\n* When cookie names in a response are duplicated, the simple view of cookies available via\n  `Connection.Response#cookies()` will provide the last one set. Generally it is better to use\n  the [Jsoup.newSession](https://jsoup.org/cookbook/web/request-session) method to maintain a cookie jar, as that\n  applies appropriate path selection on cookies when making requests. [1831](https://github.com/jhy/jsoup/issues/1831)\n* When parsing named HTML entities, base entities should resolve if they are a prefix of the input token (and not in an\n  attribute). [2207](https://github.com/jhy/jsoup/issues/2207)\n* Fixed incorrect tracking of source ranges for attributes merged from late-occurring elements that were implicitly\n  created (`html` or `body`). [2204](https://github.com/jhy/jsoup/issues/2204)\n* Follow the current HTML specification in the tokenizer to allow `<` as part of a tag name, instead of emitting it as a\n  character node. [2230](https://github.com/jhy/jsoup/issues/2230)\n* Similarly, allow a `<` as the start of an attribute name, vs creating a new element. The previous behavior was\n  intended to parse closer to what we anticipated the author's intent to be, but that does not align to the spec or to\n  how browsers behave. [1483](https://github.com/jhy/jsoup/issues/1483)\n\n## 1.18.1 (2024-Jul-10)\n\n### Improvements\n\n* **Stream Parser**: A `StreamParser` provides a progressive parse of its input. As each `Element` is completed, it is\n  emitted via a `Stream` or `Iterator` interface. Elements returned will be complete with all their children, and an\n  (empty) next sibling, if applicable. Elements (or their children) may be removed from the DOM during the parse,\n  for e.g. to conserve memory, providing a mechanism to parse an input document that would otherwise be too large to fit\n  into memory, yet still providing a DOM interface to the document and its elements. Additionally, the parser provides\n  a `selectFirst(String query)` / `selectNext(String query)`, which will run the parser until a hit is found, at which\n  point the parse is suspended. It can be resumed via another `select()` call, or via the `stream()` or `iterator()`\n  methods. [2096](https://github.com/jhy/jsoup/pull/2096)\n* **Download Progress**: added a Response Progress event interface, which reports progress and URLs are downloaded (and\n  parsed). Supported on both a session and a single connection\n  level. [2164](https://github.com/jhy/jsoup/pull/2164), [656](https://github.com/jhy/jsoup/issues/656)\n* Added `Path` accepting parse methods: `Jsoup.parse(Path)`, `Jsoup.parse(path, charsetName, baseUri, parser)`,\n  etc. [2055](https://github.com/jhy/jsoup/pull/2055)\n* Updated the `button` tag configuration to include a space between multiple button elements in the `Element.text()`\n  method. [2105](https://github.com/jhy/jsoup/issues/2105)\n* Added support for the `ns|*` all elements in namespace Selector. [1811](https://github.com/jhy/jsoup/issues/1811)\n* When normalising attribute names during serialization, invalid characters are now replaced with `_`, vs being\n  stripped. This should make the process clearer, and generally prevent an invalid attribute name being coerced\n  unexpectedly. [2143](https://github.com/jhy/jsoup/issues/2143)\n\n### Changes\n\n* Removed previously deprecated internal classes and methods. [2094](https://github.com/jhy/jsoup/pull/2094)\n* Build change: the built jar's OSGi manifest no longer imports itself. [2158](https://github.com/jhy/jsoup/issues/2158)\n\n### Bug Fixes\n\n* When tracking source positions, if the first node was a TextNode, its position was incorrectly set\n  to `-1.` [2106](https://github.com/jhy/jsoup/issues/2106)\n* When connecting (or redirecting) to URLs with characters such as `{`, `}` in the path, a Malformed URL exception would\n  be thrown (if in development), or the URL might otherwise not be escaped correctly (if in\n  production). The URL encoding process has been improved to handle these characters\n  correctly. [2142](https://github.com/jhy/jsoup/issues/2142)\n* When using `W3CDom` with a custom output Document, a Null Pointer Exception would be\n  thrown. [2114](https://github.com/jhy/jsoup/pull/2114)\n* The `:has()` selector did not match correctly when using sibling combinators (like\n  e.g.: `h1:has(+h2)`). [2137](https://github.com/jhy/jsoup/issues/2137)\n* The `:empty` selector incorrectly matched elements that started with a blank text node and were followed by \n  non-empty nodes, due to an incorrect short-circuit. [2130](https://github.com/jhy/jsoup/issues/2130) \n* `Element.cssSelector()` would fail with \"Did not find balanced marker\" when building a selector for elements that had\n  a `(` or `[` in their class names. And selectors with those characters escaped would not match as\n  expected. [2146](https://github.com/jhy/jsoup/issues/2146)\n* Updated `Entities.escape(string)` to make the escaped text suitable for both text nodes and attributes (previously was\n  only for text nodes). This does not impact the output of `Element.html()` which correctly applies a minimal escape\n  depending on if the use will be for text data or in a quoted\n  attribute. [1278](https://github.com/jhy/jsoup/issues/1278)\n* Fuzz: a Stack Overflow exception could occur when resolving a crafted `<base href>` URL, in the normalizing regex.\n  [2165](https://github.com/jhy/jsoup/issues/2165)\n\n---\n\n## 1.17.2 (2023-Dec-29)\n\n### Improvements\n\n* **Attribute object accessors**: Added `Element.attribute(String)` and `Attributes.attribute(String)` to more simply\n  obtain an `Attribute` object. [2069](https://github.com/jhy/jsoup/issues/2069)\n* **Attribute source tracking**: If source tracking is on, and an Attribute's key is changed (\n  via `Attribute.setKey(String)`), the source range is now still tracked\n  in `Attribute.sourceRange()`. [2070](https://github.com/jhy/jsoup/issues/2070)\n* **Wildcard attribute selector**: Added support for the `[*]` element with any attribute selector. And also restored\n  support for selecting by an empty attribute name prefix (`[^]`). [2079](https://github.com/jhy/jsoup/issues/2079)\n\n### Bug Fixes\n\n* **Mixed-cased source position**: When tracking the source position of attributes, if the source attribute name was\n  mix-cased but the parser was lower-case normalizing attribute names, the source position for that attribute was not\n  tracked correctly. [2067](https://github.com/jhy/jsoup/issues/2067)\n* **Source position NPE**: When tracking the source position of a body fragment parse, a null pointer\n  exception was thrown. [2068](https://github.com/jhy/jsoup/issues/2068)\n* **Multi-point emoji entity**: A multi-point encoded emoji entity may be incorrectly decoded to the replacement\n  character. [2074](https://github.com/jhy/jsoup/issues/2074)\n* **Selector sub-expressions**: (Regression) in a selector like `parent [attr=va], other`, the `, OR` was binding\n  to `[attr=va]` instead of `parent [attr=va]`, causing incorrect selections. The fix includes a EvaluatorDebug class\n  that generates a sexpr to represent the query, allowing simpler and more thorough query parse\n  tests. [2073](https://github.com/jhy/jsoup/issues/2073)\n* **XML CData output**: When generating XML-syntax output from parsed HTML, script nodes containing (pseudo) CData\n  sections would have an extraneous CData section added, causing script execution errors. Now, the data content is\n  emitted in a HTML/XML/XHTML polyglot format, if the data is not already within a CData\n  section. [2078](https://github.com/jhy/jsoup/issues/2078)\n* **Thread safety**: The `:has` evaluator held a non-thread-safe Iterator, and so if an Evaluator object was\n  shared across multiple concurrent threads, a NoSuchElement exception may be thrown, and the selected results may be\n  incorrect. Now, the iterator object is a thread-local. [2088](https://github.com/jhy/jsoup/issues/2088)\n\n---\nOlder changes for versions 0.1.1 (2010-Jan-31) through 1.17.1 (2023-Nov-27) may be found in\n[change-archive.txt](./change-archive.txt).\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License\n\nCopyright (c) 2009-2026 Jonathan Hedley <https://jsoup.org/>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# jsoup: Java HTML Parser\n\n**jsoup** is a Java library that makes it easy to work with real-world HTML and XML. It offers an easy-to-use API for URL fetching, data parsing, extraction, and manipulation using DOM API methods, CSS, and xpath selectors.\n\n**jsoup** implements the [WHATWG HTML5](https://html.spec.whatwg.org/multipage/) specification, and parses HTML to the same DOM as modern browsers.\n\n* scrape and [parse](https://jsoup.org/cookbook/input/parse-document-from-string) HTML from a URL, file, or string\n* find and [extract data](https://jsoup.org/cookbook/extracting-data/selector-syntax), using DOM traversal or CSS selectors\n* manipulate the [HTML elements](https://jsoup.org/cookbook/modifying-data/set-html), attributes, and text\n* [clean](https://jsoup.org/cookbook/cleaning-html/safelist-sanitizer) user-submitted content against a safe-list, to prevent XSS attacks\n* output tidy HTML\n\njsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating, to invalid tag-soup; jsoup will create a sensible parse tree.\n\nSee [**jsoup.org**](https://jsoup.org/) for downloads and the full [API documentation](https://jsoup.org/apidocs/).\n\n[![Build Status](https://github.com/jhy/jsoup/workflows/Build/badge.svg)](https://github.com/jhy/jsoup/actions?query=workflow%3ABuild)\n\n## Example\nFetch the [Wikipedia](https://en.wikipedia.org/wiki/Main_Page) homepage, parse it to a [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction), and select the headlines from the *In the News* section into a list of [Elements](https://jsoup.org/apidocs/org/jsoup/select/Elements.html):\n\n```java\nDocument doc = Jsoup.connect(\"https://en.wikipedia.org/\").get();\nlog(doc.title());\nElements newsHeadlines = doc.select(\"#mp-itn b a\");\nfor (Element headline : newsHeadlines) {\n  log(\"%s\\n\\t%s\", \n    headline.attr(\"title\"), headline.absUrl(\"href\"));\n}\n```\n[Online sample](https://try.jsoup.org/~LGB7rk_atM2roavV0d-czMt3J_g), [full source](https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/examples/Wikipedia.java).\n\n## Open source\njsoup is an open source project distributed under the liberal [MIT license](https://jsoup.org/license). The source code is available on [GitHub](https://github.com/jhy/jsoup).\n\n## Getting started\n1. [Download](https://jsoup.org/download) the latest jsoup jar (or add it to your Maven/Gradle build)\n2. Read the [cookbook](https://jsoup.org/cookbook/)\n3. Enjoy!\n\n### Android support\nWhen used in Android projects, [core library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) with the [NIO specification](https://developer.android.com/studio/write/java11-nio-support-table) should be enabled to support Java 8+ features.\n\n## Development and support\nIf you have any questions on how to use jsoup, or have ideas for future development, please get in touch via [jsoup Discussions](https://github.com/jhy/jsoup/discussions).\n\nIf you find any issues, please file a [bug](https://jsoup.org/bugs) after checking for duplicates.\n\nThe [colophon](https://jsoup.org/colophon) talks about the history of and tools used to build jsoup.\n\n## Status\njsoup is in general, stable release.\n\n## Author\njsoup was created and is maintained by [Jonathan Hedley](//jhedley.com), its primary author.\n\njsoup is an open-source project, and many contributors have helped improve it over the years. You can see their contributions and join the development on [GitHub](https://github.com/jhy/jsoup/graphs/contributors).\n\n## Citing jsoup\nIf you use jsoup in research or technical documentation, you can cite it as:\n\n> **Jonathan Hedley & jsoup contributors. jsoup: Java HTML Parser (2009–present).** Available at: https://jsoup.org\n\n```plaintext\n@misc{jsoup,\n  author = {Jonathan Hedley and jsoup contributors},\n  title = {jsoup: Java HTML Parser},\n  year = {2025},\n  url = {https://jsoup.org}\n}\n```\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nSecurity fixes are not back-ported. Please make sure you are running at least the latest [release version](https://jsoup.org/download) of jsoup.\n\nPlease remember that jsoup is an Open Source library and is provided without any warranty. Before using jsoup in a critical environment, you should satisfy yourself that it works correctly and securely for your needs.\n\n## Reporting a Vulnerability\n\nIf you believe or suspect you have identified a security vulnerability, please [report it](https://github.com/jhy/jsoup/security/advisories)\nvia the \"Report a Vulnerability\" button in Security Advisories. \n([Details](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability))\n\nWe follow [Coordinated Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/about-coordinated-disclosure-of-security-vulnerabilities) practices and ask that you do too.\n\nPlease provide as much detail as possible in your report, including the steps to reproduce the vulnerability and sample code.\n\nAlternatively to using GitHub, or if you have a security question, please email `security@jsoup.org`.\n\n## Fixing Vulnerabilities\n\nWe take all vulnerability reports seriously and strive to fix them as quickly as possible. Once we receive a report, we will verify the vulnerability and its impact. We will then work to develop and test a fix for the vulnerability, and release it as soon as possible.\n"
  },
  {
    "path": "change-archive.txt",
    "content": "jsoup Changelog Archive\n\nContains change notes for versions 0.1.1 (2010-Jan-31) through 1.17.1 (2023-Nov-27).\nMore recent changes may be found in CHANGES.md.\n\nRelease 1.17.1 [27-Nov-2023]\n  * Improvement: in Jsoup.connect(), added support for request-level authentication, supporting authentication to\n    proxies and to servers.\n    <https://github.com/jhy/jsoup/pull/2046>\n\n  * Improvement: in the Elements list, added direct support for `#set(index, element)`, `#remove(index)`,\n    `#remove(object)`, `#clear()`, `#removeAll(collection)`, `#retainAll(collection)`, `#removeIf(filter)`,\n    `#replaceAll(operator)`. These methods update the original DOM, as well as the Elements list.\n    <https://github.com/jhy/jsoup/pull/2017>\n\n  * Improvement: added the NodeIterator class, to efficiently traverse a node tree using the Iterator interface. And\n    added Stream Element#stream() and Node#nodeStream() methods, to enable fluent composable stream pipelines of node\n    traversals.\n    <https://github.com/jhy/jsoup/pull/2051>\n\n  * Improvement: when changing the OutputSettings syntax to XML, the xhtml EscapeMode is automatically set by default.\n\n  * Improvement: added the `:is(selector list)` pseudo-selector, which finds elements that match any of the selectors in\n    the selector list. Useful for making large ORed selectors more readable.\n\n  * Improvement: repackaged the library with native (vs automatic) JPMS module support.\n    <https://github.com/jhy/jsoup/pull/2025>\n\n  * Improvement: better fidelity of source positions when tracking is enabled. And implicitly created or closed elements\n    are tracked and detectable via Range.isImplicit().\n    <https://github.com/jhy/jsoup/pull/2056>\n\n  * Improvement: when source tracking is enabled, the source position for attribute names and values is now available.\n    Attribute#sourceRange() provides the ranges.\n    <https://github.com/jhy/jsoup/pull/2057>\n\n  * Improvement: when running concurrently under Java 21+ Virtual Threads, virtual threads could be pinned to their\n    carrier platform thread when parsing an input stream. To improve performance, particularly when parsing fetched\n    URLs, the internal ConstrainableInputStream has been replaced by ControllableInputStream, which avoids the locking\n    which caused that pinning.\n    <https://github.com/jhy/jsoup/issues/2054>\n\n  * Improvement: in Jsoup.Connect, allow any XML mimetype as a supported mimetype. Was previously limited to\n    `{application|text}/xml`. This enables for e.g. fetching SVGs with a image/svg+xml mimetype, without having to\n    disable mimetype validation.\n    <https://github.com/jhy/jsoup/issues/2059>\n\n  * Bugfix: when outputting with XML syntax, HTML elements that were parsed as data nodes (<script> and <style>) should\n    be emitted as CDATA nodes, so that they can be parsed correctly by an XML parser.\n    <https://github.com/jhy/jsoup/pull/1720>\n\n  * Bugfix: the Immediate Parent selector `>` could match elements above the root context element, causing incorrect\n    elements to be returned when used on elements other than the root document.\n    <https://github.com/jhy/jsoup/issues/2018>\n\n  * Bugfix: in a sub-query such as `p:has(> span, > i)`, combinators following the `,` Or combinator would be\n    incorrectly skipped, such that the sub-query was parsed as `i` instead of `> i`.\n    <https://github.com/jhy/jsoup/issues/1707>\n\n  * Bugfix: in W3CDom, if the jsoup input document contained an empty doctype, the conversion would fail with a\n    DOMException. Now, said doctype is discarded, and the conversion continues.\n\n  * Bugfix: when cleaning a document containing SVG elements (or other foreign elements that have preserved case names),\n    the cleaned output would be incorrectly nested if the safelist had a different case than the input document.\n    <https://github.com/jhy/jsoup/issues/2049>\n\n  * Bugfix: when cleaning a document, the output style of unknown self-closing tags from the input was not preserved in\n    the output. (So a <foo /> in the input, if safe-listed, would be output as <foo></foo>.)\n    <https://github.com/jhy/jsoup/issues/2049>\n\n  * Build Improvement: added a local test proxy implementation, for proxy integration tests.\n    <https://github.com/jhy/jsoup/pull/2029>\n\n  * Build Improvement: added tests for HTTPS request support, using a local self-signed cert. Includes proxy tests.\n    <https://github.com/jhy/jsoup/pull/2032>\n\n  * Change: the InputStream returned in Connection.Response.bodyStream() is no longer a ConstrainedInputStream, and\n    so is not subject to settings such as timeout or maximum size. It is now a plain BufferedInputStream around the\n    response stream. Whilst this behaviour was not documented, you may have been inadvertently relying on those\n    constraints. The constraints are still applied to other methods such as .parse() and .bufferUp(). So if you do want\n    a constrained BufferedInputStream, you may do Connection.Response.bufferUp().bodyStream().\n    <https://github.com/jhy/jsoup/issues/2054>\n\nRelease 1.16.2 [20-Oct-2023]\n  * Improvement: optimized the performance of complex CSS selectors, by adding a cost-based query planner. Evaluators\n    are sorted by their relative execution cost, and executed in order of lower to higher cost. This speeds the\n    matching process by ensuring that simpler evaluations (such as a tag name match) are conducted prior to more\n    complex evaluations (such as an attribute regex, or a deep child scan with a :has).\n\n  * Improvement: added support for <svg> and <math> tags (and their children). This includes tag namespaces and case\n    preservation on applicable tags and attributes.\n    <https://github.com/jhy/jsoup/pull/2008>\n\n  * Improvement: when converting jsoup Documents to W3C Documents in W3CDom, HTML documents will be placed in the\n    `http://www.w3.org/1999/xhtml` namespace by default, per the HTML5 spec. This can be controlled by setting\n    `W3CDom#namespaceAware(false)`.\n    <https://github.com/jhy/jsoup/pull/1848>\n\n  * Improvement: speed optimized the Structural Evaluators by memoizing previous evaluations. Particularly the `~`\n    (any preceding sibling) and `:nth-of-type` selectors are improved.\n    <https://github.com/jhy/jsoup/issues/1956>\n\n  * Improvement: tweaked the performance of the Element nextElementSibling, previousElementSibling, firstElementSibling,\n    lastElementSibling, firstElementChild, and lastElementChild. They now inplace filter/skip in the child-node list, vs\n    having to allocate and scan a complete Element filtered list.\n\n  * Improvement: optimized internal methods that previously called Element.children() to use filter/skip child-node list\n    accessors instead, reducing new Element List allocations.\n\n  * Improvement: tweaked the performance of parsing :pseudo selectors.\n\n  * Improvement: when using the `:empty` pseudo-selector, blank textnodes are now considered empty. Previously,\n    an element containing any whitespace was not considered empty.\n    <https://github.com/jhy/jsoup/issues/1976>\n\n  * Improvement: in forms, <input type=\"image\"> should be excluded from formData() (and hence from form submissions).\n    <https://github.com/jhy/jsoup/pull/2010>\n\n  * Improvement: in Safelist, made isSafeTag and isSafeAttribute public methods, for extensibility.\n    <https://github.com/jhy/jsoup/issues/1780>\n\n  * Bugfix: `form` elements and empty elements (such as `img`) did not have their attributes de-duplicated.\n    <https://github.com/jhy/jsoup/pull/1950>\n\n  * Bugfix: if Document.OutputSettings was cloned from a clone, an NPE would be thrown when used.\n    <https://github.com/jhy/jsoup/pull/1964>\n\n  * Bugfix: in Jsoup.connect(url), URL paths containing a %2B were incorrectly recoded to a '+', or a '+' was recoded\n    to a ' '. Fixed by reverting to the previous behavior of not encoding supplied paths, other than normalizing to\n    ASCII.\n    <https://github.com/jhy/jsoup/issues/1952>\n\n  * Bugfix: in Jsoup.connect(url), strings containing supplemental characters (e.g. emoji) were not URL escaped\n    correctly.\n\n  * Bugfix: in Jsoup.connect(url), the ConstrainableInputStream would clear Thread interrupts when reading the body.\n    This precluded callers from spawning a thread, running a number of requests for a length of time, then joining that\n    thread after interrupting it.\n    <https://github.com/jhy/jsoup/issues/1991>\n\n  * Bugfix: when tracking HTML source positions, the closing tags for H1...H6 elements were not tracked correctly.\n    <https://github.com/jhy/jsoup/issues/1987>\n\n  * Bugfix: in Jsoup.connect(), a DELETE method request did not support a request body.\n    <https://github.com/jhy/jsoup/issues/1972>\n\n  * Bugfix: when calling Element.cssSelector() on an extremely deeply nested element, a StackOverflowError could occur.\n    Further, a StackOverflowError may occur when running the query.\n    <https://github.com/jhy/jsoup/issues/2001>\n\n  * Bugfix: appending a node back to its original Element after empty() would throw an Index out of bounds exception.\n    Also, now the child nodes that were removed have their parent node cleared, fully detaching them from the original\n    parent.\n    <https://github.com/jhy/jsoup/issues/2013>\n\n  * Bugfix: in Jsoup.Connection when adding headers, the value may have been assumed to be an incorrectly decoded\n    ISO_8859_1 string, and re-encoded as UTF-8. The value is now left as-is.\n\n  * Change: removed previously deprecated methods Document#normalise, Element#forEach(org.jsoup.helper.Consumer<>),\n    Node#forEach(org.jsoup.helper.Consumer<>), and the org.jsoup.helper.Consumer interface; the latter being a\n    previously required compatibility shim prior to Android's de-sugaring support.\n\n  * Change: the previous compatibility shim org.jsoup.UncheckedIOException is deprecated in favor of the now supported\n    java.io.UncheckedIOException. If you are catching the former, modify your code to catch the latter instead.\n    <https://github.com/jhy/jsoup/pull/1989>\n\n  * Change: blocked noscript tags from being added to Safelists, due to incompatibilities between parsers with and\n    without script-mode enabled.\n\nRelease 1.16.1 [29-Apr-2023]\n  * Improvement: in Jsoup.connect(url), natively support URLs with Unicode characters in the path or query string,\n    without having to be escaped by the caller.\n    <https://github.com/jhy/jsoup/issues/1914>\n\n  * Improvement: Calling Node.remove() on a node with no parent is now a no-op, vs a validation error.\n    <https://github.com/jhy/jsoup/issues/1898>\n\n  * Bugfix: aligned the HTML Tree Builder processing steps for AfterBody and AfterAfterBody to the updated WHATWG\n    standard, to not pop the stack to close <body> or <html> elements. This prevents an errant </html> closing preceding\n    structure. Also added appropriate error message outputs in this case.\n    <https://github.com/jhy/jsoup/issues/1851>\n\n  * Bugfix: Corrected support for ruby elements (<ruby>, <rp>, <rt>, and <rtc>) to current spec.\n    <https://github.com/jhy/jsoup/issues/1294>\n\n  * Bugfix: When using Node.before(node) or Node.after(node), if the incoming node was a sibling of the context node,\n    the incoming node may be inserted into the wrong relative location.\n    <https://github.com/jhy/jsoup/issues/1898>\n\n  * Bugfix: In Jsoup.connect(url), if the input URL had components that were already % escaped, they would be escaped\n    again, causing errors when fetched.\n    <https://github.com/jhy/jsoup/issues/1902>\n\n  * Bugfix: when tracking input source positions, text in tables that was fostered had invalid positions.\n    <https://github.com/jhy/jsoup/issues/1927>\n\n  * Bugfix: If the Document.OutputSettings class was initialized, and then Entities.escape(String) called, an NPE may be\n    thrown due to a class loading circular dependency.\n    <https://github.com/jhy/jsoup/issues/1910>\n\n  * Bugfix: when pretty-printing, the first inline Element or Comment in a block would not be wrap-indented if it were\n    preceded by a blank text node.\n    <https://github.com/jhy/jsoup/issues/1906>\n\n  * Bugfix: when pretty-printing a <pre> containing block tags, those tags were incorrectly indented.\n    <https://github.com/jhy/jsoup/issues/1891>\n\n  * Bugfix: when pretty-printing nested inlineable blocks (such as a <p> in a <td>), the inner element should be\n    indented.\n    <https://github.com/jhy/jsoup/issues/1926>\n\n  * Bugfix: <br> tags should be wrap-indented when in block tags (and not when in inline tags).\n    <https://github.com/jhy/jsoup/issues/1911>\n\n  * Bugfix: the contents of a sufficiently large <textarea> with un-escaped HTML closing tags may be incorrectly parsed\n    to an empty node.\n    <https://github.com/jhy/jsoup/issues/1929>\n\nRelease 1.15.4 [18-Feb-2023]\n  * Improvement: added the ability to escape CSS selectors (tags, IDs, classes) to match elements that don't follow\n    regular CSS syntax. For example, to match by classname <p class=\"one.two\">, use document.select(\"p.one\\\\.two\");\n    <https://github.com/jhy/jsoup/issues/838>\n\n  * Improvement: when pretty-printing, wrap text that follows a <br> tag.\n    <https://github.com/jhy/jsoup/issues/1858>\n\n  * Improvement: when pretty-printing, normalize newlines that follow self-closing tags in custom tags.\n    <https://github.com/jhy/jsoup/issues/1852>\n\n  * Improvement: when pretty-printing, collapse non-significant whitespace between a block and an inline tag.\n    <https://github.com/jhy/jsoup/issues/1802>\n\n  * Improvement: in Element#forEach and Node#forEachNode, use java.util.function.Consumer instead of the previous\n    Android compatibility shim org.jsoup.helper.Consumer. Subsequently, the latter has been deprecated.\n    <https://github.com/jhy/jsoup/pull/1870>\n\n  * Improvement: added a new method Document#forms(), to conveniently retrieve a List<FormElement> containing the <form>\n    elements in a document.\n\n  * Improvement: added a new method Document#expectForm(query), to find the first matching FormElement, or blow up\n    trying.\n    \n  * Bugfix: URLs containing characters such as [ and ] were not escaped correctly, and would throw a\n    MalformedURLException when fetched.\n    <https://github.com/jhy/jsoup/issues/1873>\n\n  * Bugfix: Element.cssSelector would create invalid selectors for elements where the tag name, ID, or classnames needed\n    to be escaped (e.g. if a class name contained a ':' or '.').\n    <https://github.com/jhy/jsoup/issues/1742>\n\n  * Bugfix: element.text() should have a space between a block and an inline element.\n    <https://github.com/jhy/jsoup/issues/1877>\n\n  * Bugfix: if a Node or an Element was replaced with itself, that node would incorrectly be orphaned.\n    <https://github.com/jhy/jsoup/issues/1843>\n\n  * Bugfix: form data on a previous request was copied to a new request in newRequest(), resulting in an accumulation of\n    form data when executing multi-step form submissions, or data sent to later requests incorrectly. Now, newRequest()\n    only copies session related settings (cookies, proxy settings, user-agent, etc) but not the request data nor the\n    body.\n    <https://github.com/jhy/jsoup/issues/1778>\n\n  * Bugfix: fixed an issue in Safelist.removeAttributes which could throw a ConcurrentModificationException when using\n    the \":all\" pseudo-attribute.\n\n  * Bugfix: given extremely deeply nested HTML, a number of methods in Element could throw a StackOverflowError due\n    to excessive recursion. Namely: #data(), #hasText(), #parents(), and #wrap(html).\n    <https://github.com/jhy/jsoup/issues/1864>\n\n  * Change: deprecated the unused Document#normalise() method. Normalization occurs during the HTML tree construction,\n    and no longer as a distinct phase.\n\nRelease 1.15.3 [2022-Aug-24]\n  * Security: fixed an issue where the jsoup cleaner may incorrectly sanitize crafted XSS attempts if\n    SafeList.preserveRelativeLinks is enabled.\n    <https://github.com/jhy/jsoup/security/advisories/GHSA-gp7f-rwcx-9369>\n\n  * Improvement: the Cleaner will preserve the source position of cleaned elements, if source tracking is enabled in the\n    original parse.\n\n  * Improvement: the error messages output from Validate are more descriptive. Exceptions are now ValidationExceptions\n    (extending IllegalArgumentException). Stack traces do not include the Validate class, to make it simpler to see\n    where the exception originated. Common validation errors including malformed URLs and empty selector results have\n    more explicit error messages.\n\n  * Bugfix: the DataUtil would incorrectly read from InputStreams that emitted reads less than the requested size. This\n    lead to incorrect results when parsing from chunked server responses, for example.\n    <https://github.com/jhy/jsoup/issues/1807>\n\n  * Build Improvement: added implementation version and related fields to the jar manifest.\n    <https://github.com/jhy/jsoup/issues/1809>\n\n*** Release 1.15.2 [2022-Jul-04]\n  * Improvement: added the ability to track the position (line, column, index) in the original input source from where\n    a given node was parsed. Accessible via Node.sourceRange() and Element.endSourceRange().\n    <https://github.com/jhy/jsoup/pull/1790>\n\n  * Improvement: added Element.firstElementChild(), Element.lastElementChild(), Node.firstChild(), Node.lastChild(),\n    as convenient accessors to those child nodes and elements.\n\n  * Improvement: added Element.expectFirst(cssQuery), which is just like Element.selectFirst(), but instead of returning\n    a null if there is no match, will throw an IllegalArgumentException. This is useful if you want to simply abort\n    processing if an expected match is not found.\n\n  * Improvement: when pretty-printing HTML, doctypes are emitted on a newline if there is a preceding comment.\n    <https://github.com/jhy/jsoup/pull/1664>\n\n  * Improvement: when pretty-printing, trim the leading and trailing spaces of textnodes in block tags when possible,\n    so that they are indented correctly.\n    <https://github.com/jhy/jsoup/issues/1798>\n\n  * Improvement: in Element#selectXpath(), disable namespace awareness. This makes it possible to always select elements\n    by their simple local name, regardless of whether an xmlns attribute was set.\n    <https://github.com/jhy/jsoup/issues/1801>\n\n  * Bugfix: when using the readToByteBuffer method, such as in Connection.Response.body(), if the document has not\n    already been parsed and must be read fully, and there is any maximum buffer size being applied, only the default\n    internal buffer size is read.\n    <https://github.com/jhy/jsoup/issues/1774>\n\n  * Bugfix: when serializing HTML, newlines in elements descending from a pre tag were incorrectly skipped. That caused\n    what should have been preformatted output to instead be a run of text.\n    <https://github.com/jhy/jsoup/issues/1776>\n\n  * Bugfix: when pretty-print serializing HTML, newlines separating phrasing content (e.g. a <span> tag within a <p> tag\n    would be incorrectly skipped, instead of normalized to a space. Additionally, improved space normalization between\n    other end of line occurrences, and whitespace handling after a closing </body>\n    <https://github.com/jhy/jsoup/issues/1787>\n\n*** Release 1.15.1 [2022-May-15]\n  * Change: removed previously deprecated methods and classes (including org.jsoup.safety.Whitelist; use\n    org.jsoup.safety.Safelist instead).\n\n  * Improvement: when converting jsoup Documents to W3C Documents in W3CDom, preserve HTML valid attribute names if the\n    input document is using the HTML syntax. (Previously, would always coerce using the more restrictive XML syntax.)\n    <https://github.com/jhy/jsoup/pull/1648>\n\n  * Improvement: added the :containsWholeText(text) selector, to match against non-normalized Element text. That can be\n    useful when elements can only be distinguished by e.g. specific case, or leading whitespace, etc.\n    <https://github.com/jhy/jsoup/issues/1636>\n\n  * Improvement: added Element#wholeOwnText() to retrieve the original (non-normalized) ownText of an Element. Also\n    added the :containsWholeOwnText(text) selector, to match against that. BR elements are now treated as newlines\n    in the wholeText methods.\n    <https://github.com/jhy/jsoup/issues/1636>\n\n  * Improvement: added the :matchesWholeText(regex) and :matchesWholeOwnText(regex) selectors, to match against whole\n    (non-normalized, case sensitive) element text and own text, respectively.\n    <https://github.com/jhy/jsoup/issues/1636>\n\n  * Improvement: when evaluating an XPath query against a context element, the complete document is now visible to the\n    query, vs only the context element's sub-tree. This enables support for queries outside (parent or sibling) the\n    element, e.g. ancestor-or-self::*.\n    <https://github.com/jhy/jsoup/issues/1652>\n\n  * Improvement: allow a maxPaddingWidth on the indent level in OutputSettings when pretty printing. This defaults to\n    30 to limit the indent level for very deeply nested elements, and may be disabled by setting to -1.\n    <https://github.com/jhy/jsoup/pull/1655>\n\n  * Improvement: when cloning a Node or an Element, the clone gets a cloned OwnerDocument containing only that clone, so\n    as to preserve applicable settings, such as the Pretty Print settings.\n    <https://github.com/jhy/jsoup/issues/763>\n\n  * Improvement: added a convenience method Jsoup.parse(File).\n    <https://github.com/jhy/jsoup/issues/1693>\n\n  * Improvement: in the NodeTraversor, added default implementations for NodeVisitor.tail() and NodeFilter.tail(), so\n    that code using only head() methods can be written as lambdas.\n\n  * Improvement: in NodeTraversor, added support for removing nodes via Node.remove() during NodeVisitor.head().\n    <https://github.com/jhy/jsoup/issues/1699>\n\n  * Improvement: added Node.forEachNode(Consumer<Node>) and Element.forEach(Consumer<Element) methods, to efficiently\n    traverse the DOM with a functional interface.\n    <https://github.com/jhy/jsoup/issues/1700>\n\n  * Bugfix: boolean attribute names should be case-insensitive, but were not when the parser was configured to preserve\n    case.\n    <https://github.com/jhy/jsoup/issues/1656>\n\n  * Bugfix: when reading from SequenceInputStreams across the buffer, the input stream was closed too early, resulting\n    in missed content.\n    <https://github.com/jhy/jsoup/pull/1671>\n\n  * Bugfix: a comment with all dashes (<!----->) should not emit a parse error.\n    <https://github.com/jhy/jsoup/issues/1667>\n\n  * Bugfix: when throwing a SelectorParseException for an invalid selector, don't try to String.format the input, as\n    that could throw an IllegalFormatException.\n    <https://github.com/jhy/jsoup/issues/1691>\n\n  * Bugfix: when serializing HTML with Pretty Print enabled, extraneous whitespace may be added on closing tags, or\n    extra newlines may be added at the end of script blocks.\n    <https://github.com/jhy/jsoup/issues/1688>\n    <https://github.com/jhy/jsoup/issues/1689>\n\n  * Bugfix: when copy-creating a Safelist from another, perform a deep-copy of the original's settings, so that changes\n    to the original after creation do not affect the copy.\n    <https://github.com/jhy/jsoup/pull/1763>\n\n  * Bugfix [Fuzz]: speed improvement when parsing constructed HTML containing very deeply incorrectly stacked formatting\n    elements with many attributes.\n    <https://github.com/jhy/jsoup/issues/1695>\n\n  * Bugfix [Fuzz]: during parsing, a StackOverflowException was possible given crafted HTML with hundreds of nested\n    table elements followed by invalid formatting elements.\n    <https://github.com/jhy/jsoup/issues/1697>\n\n*** Release 1.14.3 [2021-Sep-30]\n  * Improvement: added native XPath support in Element#selectXpath(String)\n    <https://github.com/jhy/jsoup/pull/1629>\n\n  * Improvement: added full support for the <template> tag to the HTML5 parser spec.\n    <https://github.com/jhy/jsoup/issues/1634>\n\n  * Improvement: added support in CharacterReader to track newlines, so that parse errors can be reported more\n    intuitively.\n    <https://github.com/jhy/jsoup/pull/1624>\n\n  * Improvement: tracked parse errors now have more details, including the erroneous token, to help clarify the errors.\n\n  * Improvement: speed and memory optimizations for the :has(subquery) selector.\n\n  * Improvement: the :contains(text) and :containsOwn(text) selectors are now whitespace normalized, aligning to the\n    document text that they are matching against.\n    <https://github.com/jhy/jsoup/issues/876>\n\n  * Improvement: in Element, speed optimized adopting all of an element's child nodes into a currently empty element.\n    Improves the HTML adoption agency algorithm when adopting elements with many children.\n    <https://github.com/jhy/jsoup/issues/1638>\n\n  * Improvement: increased the parse speed when in RCData (e.g. <title>) and unescaped <tag> tokens are found, by\n    memoizing the </title> scan and reducing GC.\n    <https://github.com/jhy/jsoup/issues/1644>\n\n  * Improvement: when parsing custom tags (in HTML or XML), added a flyweight cache on Tag.valueOf(name) to reduce\n    memory overhead when many tags are repeated. Also tuned other areas of the parser when many very deeply stacked\n    custom elements were present.\n    <https://github.com/jhy/jsoup/issues/1646>\n\n  * Bugfix: when tracking errors or checking for validity in the Cleaner, errors were incorrectly raised for missing\n    optional closing tags.\n\n  * Bugfix: the OSGi bundle meta-data incorrectly set a version on the import of javax.annotation (used as a build-time\n    dependency for nullability assertions).\n    <https://github.com/jhy/jsoup/issues/1616>\n\n  * Bugfix: the Attributes::equals() method was sensitive to the order of its contents, but it should not be.\n    <https://github.com/jhy/jsoup/issues/1492>\n\n  * Bugfix: when the HTML parser was configured to preserve case, Element text methods would miss adding whitespace for\n    \"BR\" tags.\n\n  * Bugfix: attribute names are now normalized & validated correctly for the specific output syntax (HTML or XML).\n    Previously, syntactically invalid attribute names could be output by the html() methods. Such attributes are still\n    available in the DOM, and will be normalized if possible on output.\n    <https://github.com/jhy/jsoup/issues/1474>\n\n  * Bugfix [Fuzz]: fixed an IOOB when an empty select tag was followed by a body tag that needed reparenting.\n    <https://github.com/jhy/jsoup/issues/1639>\n\n  * Build Improvement: fixed nullability annotations for Node.equals(other) and other equals methods.\n    <https://github.com/jhy/jsoup/issues/1628>\n\n  * Build Improvement: added JDK 17 to the CI builds.\n    <https://github.com/jhy/jsoup/pull/1641>\n\n*** Release 1.14.2 [2021-Aug-15]\n  * Improvement: support Pattern.quote \\Q and \\E escapes in the selector regex matchers.\n    <https://github.com/jhy/jsoup/pull/1536>\n\n  * Improvement: Element.absUrl() now supports tel: URLs, and other URLs that are already absolute but that Java does\n    not have input stream handlers for.\n    <https://github.com/jhy/jsoup/issues/1610>\n\n  * Bugfix: when serializing output, escape characters that are in the < 0x20 range. This improves XML output\n    compatibility, and makes HTML output with these characters easier to read (as they're otherwise invisible).\n    <https://github.com/jhy/jsoup/issues/1556>\n\n  * Bugfix: the *|el wildcard namespace selector now also matches elements with no namespace.\n    <https://github.com/jhy/jsoup/issues/1565>\n    \n  * Bugfix: corrected a potential case of the parser input stream not being closed immediately on a read exception.\n\n  * Bugfix: when making a HTTP POST, if the request write fails, make sure the connection is immediately cleaned up.\n\n  * Bugfix: in the XML parser, XML processing instructions without attributes would be serialized as if they did.\n    <https://github.com/jhy/jsoup/issues/770>\n\n  * Bugfix: updated the HtmlTreeParser resetInsertionMode to the current spec for supported elements.\n    <https://github.com/jhy/jsoup/issues/1491>\n\n  * Bugfix: fixed an NPE when parsing fragment HTML into a standalone table element.\n    <https://github.com/jhy/jsoup/issues/1603>\n\n  * Bugfix: fixed an NPE when parsing fragment heading HTML into a standalone p element.\n    <https://github.com/jhy/jsoup/issues/1601>\n\n  * Bugfix: fixed an IOOB when parsing a formatting fragment into a standalone p element.\n    <https://github.com/jhy/jsoup/issues/1602>\n\n  * Bugfix: tag names must start with an ascii-alpha character.\n    <https://github.com/jhy/jsoup/issues/1006>\n\n  * Bugfix [Fuzz]: fixed a slow parse when a tag or an attribute name has thousands of null characters in it.\n    <https://github.com/jhy/jsoup/issues/1580>\n\n  * Bugfix [Fuzz]: the adoption agency algorithm can have an incorrect bookmark position\n    <https://github.com/jhy/jsoup/issues/1576>\n\n  * Bugfix [Fuzz]: malformed HTML could result in null elements on stack\n    <https://github.com/jhy/jsoup/issues/1579>\n\n  * Bugfix [Fuzz]: malformed deeply nested table elements could create a stack overflow.\n    <https://github.com/jhy/jsoup/issues/1577>\n\n  * Bugfix [Fuzz]: Speed optimized malformed HTML creating elements with thousands of elements - limit the attribute\n    count per element when parsing to 512 (in real-world HTML, P99 is ~ 8).\n    <https://github.com/jhy/jsoup/issues/1578>\n\n  * Bugfix [Fuzz]: Speed improvement for the foster formatting elements algo, by limiting how far up a crafted stack\n    to scan.\n    <https://github.com/jhy/jsoup/issues/1593>\n\n  * Bugfix [Fuzz]: Speed improvement when parsing crafted HTML when transferring form attributes.\n    <https://github.com/jhy/jsoup/issues/1595>\n\n  * Bugfix [Fuzz]: Speed improvement when the stack was thousands of items deep, and non-matching close tags sent.\n    <https://github.com/jhy/jsoup/issues/1596>\n\n  * Bugfix [Fuzz]: Speed improvement when an attribute name is 600K of quote characters or otherwise needs accumulation\n    vs being able to read in one hit.\n    <https://github.com/jhy/jsoup/issues/1605>\n\n  * Bugfix [Fuzz]: Speed improvement when closing missing empty tags (in XML comment processed as HTML) when thousands\n    deep in stack.\n    <https://github.com/jhy/jsoup/issues/1606>\n\n  * Bugfix [Fuzz]: Fix a potential stack-overflow in the parser given crafted HTML, when the parser looped in the\n    InSelectInTable state.\n\n  * Bugfix [Fuzz]: Fix an IOOB when the HTML root was cleared from the stack and then attributes were merged onto it.\n    <https://github.com/jhy/jsoup/issues/1611>\n\n  * Bugfix [Fuzz]: Improved the speed of parsing when crafted HTML contains hundreds of active formatting elements\n    that were copied for all new elements (similar to an amplification attack). The number of considered active\n    formatting elements that will be cloned when mis-nested is now capped to 12.\n    <https://github.com/jhy/jsoup/issues/1613>\n\n*** Release 1.14.1 [2021-Jul-10]\n  * Change: updated the minimum supported Java version from Java 7 to Java 8.\n\n  * Change: updated the minimum Android API level from 8 to 10.\n\n  * Change: although Node#childNodes() returns an UnmodifiableList as a view into its children, it was still\n    directly backed by the internal child list. That made some uses, such as looping and moving those children to\n    another element, throw a ConcurrentModificationException. Now this method returns its own list so that they are\n    separated and changes to the parent's contents will not impact the children view. This aligns with similar methods\n    such as Element#children(). If you have code that iterates this list and makes parenting changes to its contents,\n    you may need to make a code update.\n    <https://github.com/jhy/jsoup/issues/1431>\n\n  * Change: the org.jsoup.Connection interface has been modified to introduce new methods for sessions and the cookie\n    store. If you have a custom implementation of this interface, you will need to add implementations of these methods.\n\n  * Improvement: added HTTP request session management support with Jsoup.newSession(). This extends the Connection\n    implementation to support (optional) sessions, which allow request defaults (timeout, proxy, etc) to be set once and\n    then applied to all requests within that session.\n\n    Cookies are re-implemented to correctly support path and domain filtering when used within a session. A default\n    in-memory cookie store is used for the session, or a custom implementation (perhaps disk-persistent, or pre-set)\n    can be used instead.\n\n    Forms submitted using the FormElement#submit() use the same session that was used to fetch the document and so pass\n    cookies and other defaults appropriately.\n\n    The session is multi-thread safe and can execute multiple requests concurrently. If the user accidentally tries to\n    execute the same request object across multiple threads (vs calling Connection#newRequest()),\n    that is detected cleanly and a clear exception is thrown (vs weird blowups in input stream reading, or forcing\n    everything through a synchronized bottleneck.\n    <https://github.com/jhy/jsoup/pull/1476>\n\n  * Improvement: renamed the Whitelist class to Safelist, with the goal of more inclusive language. A shim is provided\n    for backwards compatibility (source and binary). This shim is marked as deprecated and will be removed in the\n    jsoup 1.15.1 release.\n    <https://github.com/jhy/jsoup/pull/1464>\n\n  * Improvement: added support for Internationalized Domain Names (IDNs) in Jsoup.Connect.\n    <https://github.com/jhy/jsoup/issues/1300>\n\n  * Improvement: added support for loading and parsing gzipped HTML files in Jsoup.parse(File in, charset, baseUri).\n\n  * Improvement: reduced thread contention in HttpConnection and Document.\n    <https://github.com/jhy/jsoup/pull/1455>\n\n  * Improvement: better parsing performance when under high thread concurrency\n    <https://github.com/jhy/jsoup/pull/1402>\n\n  * Improvement: added Element#id(String) ID attribute setter.\n\n  * Improvement: in Document, #body() and #head() accessors will now automatically create those elements, if they were\n    missing (e.g. if the Document was not parsed from HTML). Additionally, the #body() method returns the frameset\n    element (instead of null) for frameset documents.\n\n  * Improvement: when cleaning a document, the output settings of the original document are cloned into the cleaned\n    document.\n    <https://github.com/jhy/jsoup/issues/1417>\n\n  * Improvement: when parsing XML, disable pretty-printing by default.\n    <https://github.com/jhy/jsoup/issues/1168>\n\n  * Improvement: much better performance in Node#clone() for large and deeply nested documents. Complexity was O(n^2) or\n    worse, now O(n).\n\n  * Improvement: during traversal using the NodeTraversor, nodes may now be replaced with Node#replaceWith(Node).\n    <https://github.com/jhy/jsoup/issues/1289>\n\n  * Improvement: added Element#insertChildren and Element#prependChildren, as convenience methods in addition to\n    Element#insertChildren(index, children), for bulk moving nodes.\n\n  * Improvement: clean up relative URLs with too many .. segments better.\n    <https://github.com/jhy/jsoup/pull/1482>\n\n  * Build Improvement: integrated jsoup into the OSS Fuzz project, which semi-randomly generates millions of different\n    HTML and XML input files, searching for areas to improve in the parser for increased robustness and throughput.\n    <https://github.com/jhy/jsoup/issues/1502>\n\n  * Build Improvement: integrated with GitHub's CodeQL static code analyzer.\n    <https://github.com/jhy/jsoup/pull/1494>\n\n  * Build Improvement: moved to GitHub Workflows for build verification.\n\n  * Build Improvement: updated Jetty (used for integration tests; not bundled) to 9.4.42.\n\n  * Build Improvement: added nullability annotations and initial settings.\n    <https://github.com/jhy/jsoup/pull/1467>\n\n  * Bugfix: corrected the adoption agency algorithm, to handle cases where e.g. a <a> tag incorrectly nests further <a>\n    tags.\n    <https://github.com/jhy/jsoup/pull/1517> <https://github.com/jhy/jsoup/issues/845>\n\n  * Bugfix: when parsing HTML, could throw NPEs on some tags (isindex or table>input).\n    <https://github.com/jhy/jsoup/issues/1404>\n\n  * Bugfix: in HttpConnection.Request, headers beginning with \"sec-\" (e.g. Sec-Fetch-Mode) were silently discarded by\n    the underlying Java HttpURLConnection. These are now settable correctly.\n    <https://github.com/jhy/jsoup/issues/1461>\n\n  * Bugfix: when adding child Nodes to a Node, could incorrectly reparent all nodes if the first parent had the same\n    length of children as the incoming node list.\n\n  * Bugfix: when wrapping an orphaned element, would throw an NPE.\n\n  * Bugfix: when wrapping an element with HTML that included multiple sibling elements, those siblings were incorrectly\n    added as children of the wrapper instead of siblings.\n\n  * Bugfix: when setting the content of a script or style tag via the Element#html(String) method, the content is now\n    treated as a DataNode, not a TextNode. This means that characters like '<' will no longer be incorrectly escaped.\n    As a related ergonomic improvement, the same behavior applies for Element#text(String) (i.e. the content will be\n    treated as a DataNode, despite calling the text() method.\n    <https://github.com/jhy/jsoup/issues/1419>\n\n  * Bugfix: when wrapping HTML around an existing element with Element#wrap(String), will now take the content as\n    provided and ignore normal HTML tree-building rules. This allows for e.g. a div tag to be placed inside of p tags.\n\n  * Bugfix: the Elements#forms() method should return the selected immediate elements that are Forms, not children.\n    <https://github.com/jhy/jsoup/pull/1403>\n\n  * Bugfix: when creating a selector for an element with Element#cssSelector, if the element used a non-unique ID\n    attribute, the returned selector may not match the desired element.\n    <https://github.com/jhy/jsoup/issues/1085>\n\n  * Bugfix: corrected the toString() methods of the Evaluator classes.\n\n  * Bugfix: when converting a jsoup document to a W3C document (in W3CDom#convert), if a tag had XML illegal characters,\n    a DOMException would be thrown. Now instead, that tag is represented as a text node.\n    <https://github.com/jhy/jsoup/issues/1093>\n\n  * Bugfix: if a HTML file ended with an open noscript tag, an \"EOF\" string would appear in the HTML output.\n\n  * Bugfix: when parsing a document as XML, automatically set the output syntax to XML, and ensure that \"<\" characters\n    in attributes are escaped as \"&lt\" (which is not required in HTML as the quoted attribute contents are safe, but is\n    required in XML).\n    <https://github.com/jhy/jsoup/issues/1420>\n\n  * Bugfix: [Fuzz] when parsing an attribute key containing \"abs:abs\", a validation error would be incorrectly\n    thrown.\n    <https://github.com/jhy/jsoup/issues/1541>\n\n  * Bugfix: [Fuzz] could NPE while parsing in resetInsertionMode().\n    <https://github.com/jhy/jsoup/issues/1538>\n\n  * Bugfix: [Fuzz] when parsing XML, could Stack Overflow when parsing XML declarations.\n    <https://github.com/jhy/jsoup/issues/1539>\n\n  * Bugfix: [Fuzz] fixed a potential Stack Overflow when parsing mis-nested tfoot tags, and updated the tree parser for\n    this situation to match the updated HTML5 spec.\n    <https://github.com/jhy/jsoup/issues/1543>\n\n  * Bugfix: [Fuzz] fixed a potentially slow HTML parse when tags are nested extremely deep (e.g. 88K depth), by limiting\n    the formatting tag search depth to 256. In practice, it's generally between 4 - 8.\n    <https://github.com/jhy/jsoup/issues/1544>\n\n  * Bugfix: [Fuzz] when parsing an unterminated RCDATA token (e.g. a <title> tag), could throw an IO Exception \"No\n    buffer left to unconsume\" when trying to rewind the buffer.\n    <https://github.com/jhy/jsoup/issues/1542>\n\n*** Release 1.13.1 [2020-Feb-29]\n  * Improvement: added Element#closest(selector), which walks up the tree to find the nearest element matching the\n    selector.\n    <https://github.com/jhy/jsoup/issues/1326>\n\n  * Improvement: memory optimizations, reducing the retained size of a Document by ~ 39%, and allocations by ~ 9%:\n      1. Attributes holder in Elements is only created if the element has attributes\n      2. Only track the baseUri in an element when it is set via DOM to a new value for a given tree\n      3. After parsing, do not retain the input character reader (and associated buffers) in the Document#parser\n\n  * Improvement: substantial parse speed improvements vs 1.12.x (bringing back to par with previous releases).\n    <https://github.com/jhy/jsoup/issues/1327>\n\n  * Improvement: when pretty-printing, comments in inline tags are not pushed to a newline\n\n  * Improvement: added Attributes#hasDeclaredValueForKey(key) and Attribute#hasDeclaredValueForKeyIgnoreCase(), to check\n    if an attribute is set but has no value. Useful in place of the deprecated and removed BooleanAttribute class and\n    instanceof test.\n\n  * Improvement: removed old methods and classes that were marked deprecated in previous releases.\n\n  * Improvement: added Element#select(Evaluator) and Element#selectFirst(Evaluator), to allow re-use of a parsed CSS\n    selector if using the same evaluator many times.\n    <https://github.com/jhy/jsoup/issues/1319>\n\n  * Improvement: added Elements#forms(), Elements#textNodes(), Elements#dataNodes(), and Elements#comments(), as a\n    convenient way to get access to these node types directly from an element selection.\n\n  * Improvement: preserve whitespace before html and head tag, if pretty-printing is off.\n\n  * Bugfix: in a <select> tag, a second <optgroup> would not automatically close an earlier open <optgroup>\n    <https://github.com/jhy/jsoup/issues/1313>\n\n  * Bugfix: in CharacterReader when parsing an input stream, could throw a Mark Invalid exception if the reader was\n    marked, a bufferUp occurred, and then the reader was rewound.\n    <https://github.com/jhy/jsoup/issues/1324>\n\n  * Bugfix: empty tags and form tags did not have their attributes normalized (lower-cased by default)\n    <https://github.com/jhy/jsoup/pull/1323>\n\n  * Bugfix: when preserve case was set to on, the HTML pretty-print formatter didn't indent capitalized tags correctly.\n\n  * Bugfix: ensure that script and style contents are parsed into DataNodes, not TextNodes, when in case-sensitive\n    parse mode.\n\n**** Release 1.12.2 [2020-Feb-08]\n  * Improvement: the :has() selector now supports relative selectors. For example, the query\n    \"div:has(> a)\" will select all \"div\" elements that have at least one direct child \"a\" element.\n    <https://github.com/jhy/jsoup/pull/1214>\n\n  * Improvement: added Element chaining methods for various overridden methods on Node.\n    <https://github.com/jhy/jsoup/issues/1193>\n\n  * Improvement: ensure HTTP keepalives work when fetching content via body() and bodyAsBytes().\n    <https://github.com/jhy/jsoup/issues/1232>\n\n  * Improvement: set the default max body size in Jsoup.Connection to 2MB (up from 1MB) so fewer people get trimmed\n    content if they have not set it, but still in sensible bounds. Also updated the default user-agent to improve\n    default compatibility.\n\n  * Improvement: dramatic speed improvement when bulk inserting child nodes into an element (wrapping contents).\n    <https://github.com/jhy/jsoup/issues/1281>\n\n  * Improvement: added Element#childrenSize() as a convenience to get the size of an element's element children.\n    <https://github.com/jhy/jsoup/pull/1291>\n\n  * Improvement: in W3CDom.asString, allow the output mode to be specified as HTML or as XML. It will default to\n    checking the content, and automatically selecting.\n\n  * Improvement: added a Document#documentType() method, to get a doc's doctype.\n\n  * Improvement: To DocumentType, added #name(), #publicID(), and #systemId() methods to fetch those fields.\n\n  * Improvement: in W3CDom conversions from jsoup documents, retain the DocumentType, and be able to serialize it.\n    <https://github.com/jhy/jsoup/issues/1183>\n\n  * Bugfix: on pages fetch by Jsoup.Connection, a \"Mark Invalid\" exception might be incorrectly thrown, or the page may\n    miss some data. This occurred on larger pages when the file transfer was chunked, and an invalid HTML entity\n    happened to cross a chunk boundary.\n    <https://github.com/jhy/jsoup/issues/1218>\n\n  * Bugfix: if duplicate attributes in an element exist, retain the first vs the last attribute with the same name. Case\n    aware (HTML case-insensitive names, XML are case-sensitive).\n    <https://github.com/jhy/jsoup/issues/1219>\n\n  * Bugfix: don't submit input type=button form elements.\n    <https://github.com/jhy/jsoup/issues/1231>\n\n  * Bugfix: handle error position reporting correctly and don't blow up in some edge cases.\n    <https://github.com/jhy/jsoup/issues/1251>\n    <https://github.com/jhy/jsoup/pull/1253>\n\n  * Bugfix: handle the ^= (starts with) selector correctly when the prefix starts with a space.\n    <https://github.com/jhy/jsoup/pull/1280>\n\n  * Bugfix: don't strip out zero-width-joiners (or zero-width-non-joiners) when normalizing text. That breaks combined\n    emoji (and other text semantics). 🤦‍♂️\n    <https://github.com/jhy/jsoup/issues/1269>\n\n  * Bugfix: Evaluator.TagEndsWith (namespaced elements) and Tag disagreed in case-sensitivity. Now correctly matches\n    case-insensitively.\n    <https://github.com/jhy/jsoup/issues/1257>\n\n  * Bugfix: Don't throw an exception if a selector ends in a space, just trim it.\n    <https://github.com/jhy/jsoup/issues/1274>\n    \n  * Bugfix: HTML parser adds redundant text when parsing self-closing textarea.\n    <https://github.com/jhy/jsoup/issues/1220>\n\n  * Bugfix: Don't add spurious whitespace or newlines to HTML or text for inline tags.\n    <https://github.com/jhy/jsoup/issues/1305>\n    <https://github.com/jhy/jsoup/issues/731>\n\n  * Bugfix: TextNode.outerHtml() wouldn't normalize correctly without a parent.\n    <https://github.com/jhy/jsoup/issues/1309>\n\n  * Bugfix: Removed binary input detection as it was causing too many false positives.\n    <https://github.com/jhy/jsoup/issues/1250>\n\n  * Bugfix: when cloning a TextNode, if .attributes() was hit before the clone() method, the text value would only be a\n    shallow clone.\n    <https://github.com/jhy/jsoup/issues/1176>\n\n  * Various code hygiene updates.\n\n**** Release 1.12.1 [2019-May-12]\n  * Change: removed deprecated method to disable TLS cert checking Connection.validateTLSCertificates().\n\n  * Change: some internal methods have been rearranged; if you extended any of the Jsoup internals you may need to make\n    updates.\n\n  * Improvement: documents now remember their parser, so when later manipulating them, the correct HTML or XML tree\n    builder is reused, as are the parser settings like case preservation.\n    <https://github.com/jhy/jsoup/issues/769>\n\n  * Improvement: Jsoup now detects the character set of the input if specified in an XML Declaration, when using the\n    HTML parser. Previously that only happened when the XML parser was specified.\n    <https://github.com/jhy/jsoup/issues/1009>\n\n  * Improvement: if the document's input character set does not support encoding, flip it to one that does.\n    <https://github.com/jhy/jsoup/issues/1007>\n\n  * Improvement: if a start tag is missing a > and a new tag is seen with a <, treat that as a new tag. (This differs\n    from the HTML5 spec, which would make at attribute with a name beginning with <, but in practice this impacts too\n    many pages.\n    <https://github.com/jhy/jsoup/issues/797>\n\n  * Improvement: performance tweaks when parsing start tags, data, tables.\n\n  * Improvement: added Element.nextElementSiblings() and Element.previousElementSiblings()\n    <https://github.com/jhy/jsoup/pull/1054>\n\n  * Improvement: treat center tags as block tags.\n    <https://github.com/jhy/jsoup/pull/1113>\n\n  * Improvement: allow forms to be submitted with Content-Type=multipart/form-data without requiring a file upload;\n    automatically set the mime boundary.\n    <https://github.com/jhy/jsoup/pull/1058>\n\n  * Improvement: Jsoup will now detect if an input file or URL is binary, and will refuse to attempt to parse it, with\n    an IO exception. This prevents runaway processing time and wasted effort creating meaningless parsed DOM trees.\n    <https://github.com/jhy/jsoup/issues/1192>\n\n  * Bugfix: when using the tag case preserving parsing settings, certain HTML tree building rules where not followed\n    for upper case tags.\n    <https://github.com/jhy/jsoup/issues/1149>\n\n  * Bugfix: when converting a Jsoup document to a W3C DOM, if an element is namespaced but not in a defined namespace,\n    set it to the global namespace.\n    <https://github.com/jhy/jsoup/issues/848>\n\n  * Bugfix: attributes created with the Attribute constructor with just spaces for names would incorrectly pass\n    validation.\n    <https://github.com/jhy/jsoup/issues/1159>\n\n  * Bugfix: some pseudo XML Declarations were incorrectly handled when using the XML Parser, leading to an IOOB\n    exception when parsing.\n    <https://github.com/jhy/jsoup/issues/1139>\n\n  * Bugfix: when parsing URL parameter names in an attribute that is not correctly HTML encoded, and near the end of the\n    current buffer, those parameters may be incorrectly dropped. (Improved CharacterReader mark/reset support.)\n    <https://github.com/jhy/jsoup/pull/1154>\n\n  * Bugfix: boolean attribute values would be returned as null, vs an empty string, when accessed via the\n    Attribute#getValue() method.\n    <https://github.com/jhy/jsoup/issues/1065>\n\n  * Bugfix: orphan Attribute objects (i.e. created outside of a parse or an Element) would throw an NPE on\n    Attribute#setValue(val)\n    <https://github.com/jhy/jsoup/issues/1107>\n\n  * Bugfix: Element.shallowClone() was not making a clone of its attributes.\n    <https://github.com/jhy/jsoup/issues/1201>\n\n  * Bugfix: fixed an ArrayIndexOutOfBoundsException in HttpConnection.looksLikeUtf8 when testing small strings in\n    specific ranges.\n    <https://github.com/jhy/jsoup/issues/1172>\n\n  * Updated jetty-server (which is used for integration tests) to latest 9.2 series (9.2.28).\n\n*** Release 1.11.3 [2018-Apr-15]\n  * Improvement: CDATA sections are now treated as whitespace preserving (regardless of the containing element), and are\n    round-tripped into output HTML.\n    <https://github.com/jhy/jsoup/issues/406>\n    <https://github.com/jhy/jsoup/issues/965>\n\n  * Improvement: added support for Deflate encoding.\n    <https://github.com/jhy/jsoup/pull/982>\n\n  * Improvement: when parsing <pre> tags, skip the first newline if present.\n    <https://github.com/jhy/jsoup/issues/825>\n\n  * Improvement: support nested quotes for attribute selection queries.\n    <https://github.com/jhy/jsoup/pull/988>\n\n  * Improvement: character references from Windows-1252 that are not valid Unicode are mapped to the appropriate\n    Unicode replacement.\n    <https://github.com/jhy/jsoup/pull/1046>\n\n  * Improvement: accept a custom SSL socket factory in Jsoup.Connection.\n    <https://github.com/jhy/jsoup/pull/1038>\n\n  * Bugfix: \"Mark has been invalidated\" exception was thrown when parsing some URLs on Android <= 6.\n    <https://github.com/jhy/jsoup/issues/990>\n\n  * Bugfix: The Element.text() for <div>One</div>Two was \"OneTwo\", not \"One Two\".\n    <https://github.com/jhy/jsoup/issues/812>\n\n  * Bugfix: boolean attributes with empty string values were not collapsing in HTML output.\n    <https://github.com/jhy/jsoup/issues/985>\n\n  * Bugfix: when using the XML Parser set to lowercase normalize tags, uppercase closing tags were not correctly\n    handled.\n    <https://github.com/jhy/jsoup/issues/998>\n\n  * Bugfix: when parsing from a URL, an end tag could be read incorrectly if it started on a buffer boundary.\n    <https://github.com/jhy/jsoup/issues/995>\n\n  * Bugfix: when parsing from a URL, if the remote server failed to complete its write (i.e. it writes less than the\n    Content Length header promised on a gzipped stream), the parse method would incorrectly throw an unchecked\n    exception. It now throws the declared IOException.\n    <https://github.com/jhy/jsoup/issues/980>\n\n  * Bugfix: leaf nodes (such as text nodes) where throwing an unsupported operation exception on childNodes(), instead\n    of just returning an empty list.\n    <https://github.com/jhy/jsoup/issues/1032>\n\n  * Bugfix: documents with a leading UTF-8 BOM did not have that BOM consumed, so it acted as a zero width no-break\n    space, which could impact the parse tree.\n    <https://github.com/jhy/jsoup/issues/1003>\n\n  * Bugfix: when parsing an invalid XML declaration, the parse would fail.\n    <https://github.com/jhy/jsoup/issues/1015>\n\n*** Release 1.11.2 [2017-Nov-19]\n  * Improvement: added a new pseudo selector :matchText, which allows text nodes to match as if they were elements.\n    This enables finding text that is only marked by a \"br\" tag, for example.\n    <https://github.com/jhy/jsoup/issues/550>\n\n  * Change: marked Connection.validateTLSCertificates() as deprecated.\n\n  * Improvement: normalize invisible characters (like soft-hyphens) in Element.text().\n    <https://github.com/jhy/jsoup/issues/978>\n\n  * Improvement: added Element.wholeText(), to easily get the un-normalized text value of an element and its children.\n    <https://github.com/jhy/jsoup/pull/564>\n    \n  * Bugfix: in a deep DOM stack, a StackOverFlow exception could occur when generating implied end tags.\n    <https://github.com/jhy/jsoup/issues/966>\n\n  * Bugfix: when parsing attribute values that happened to cross a buffer boundary, a character was dropped.\n    <https://github.com/jhy/jsoup/issues/967>\n\n  * Bugfix: fixed an issue that prevented using infinite timeouts in Jsoup.Connection.\n    <https://github.com/jhy/jsoup/issues/968>\n\n  * Bugfix: whitespace preserving tags were not honoured when nested deeper than two levels deep.\n    <https://github.com/jhy/jsoup/issues/722>\n\n  * Bugfix: an unterminated comment token at the end of the HTML input would cause an out of bounds exception.\n    <https://github.com/jhy/jsoup/issues/972>\n\n  * Bugfix: an NPE in the Cleaner which would occur if an <a href> attribute value was missing.\n    <https://github.com/jhy/jsoup/issues/973>\n\n  * Bugfix: when serializing the same document in a multiple threads, on Android, with a character set that is not ascii\n    or UTF-8, an encoding exception could occur.\n    <https://github.com/jhy/jsoup/issues/970>\n\n  * Bugfix: removing a form value from the DOM would not remove it from FormData.\n    <https://github.com/jhy/jsoup/pull/969>\n\n  * Bugfix: in the W3CDom transformer, siblings were incorrectly inheriting namespaces defined on previous siblings.\n    <https://github.com/jhy/jsoup/issues/977>\n\n*** Release 1.11.1 [2017-Nov-06]\n  * Updated language level to Java 7 from Java 5. To maintain Android support (of minversion 8), try-with-resources are\n    not used.\n    <https://github.com/jhy/jsoup/issues/899>\n\n  * When loading content from a URL or a file, the content is now parsed as it streams in from the network or disk,\n    rather than being fully buffered before parsing. This substantially reduces memory consumption & large garbage\n    objects when loading large files. Note that this change means that a response, once parsed, may not be parsed\n    again from the same response object unless you call response.bufferUp() first, which will buffer the full response\n    into memory.\n    <https://github.com/jhy/jsoup/issues/904>\n\n  * Added Connection.Response.bodyStream(), a method to get the response body as an input stream. This is useful for\n    saving a large response straight to a file, without buffering fully into memory first.\n\n  * Performance improvements in text and HTML generation (through less GC).\n\n  * Reduced memory consumption of text, scripts, and comments in the DOM by 40%, by refactoring the node\n    hierarchy to not track childnodes or attributes by default for lead nodes. For the average document, that's about a\n    30% memory reduction.\n    <https://github.com/jhy/jsoup/issues/911>\n\n  * Reduced memory consumption of Elements by refactoring their Attributes to be a simple pair of arrays, vs a\n    LinkedHashSet.\n    <https://github.com/jhy/jsoup/issues/911>\n\n  * Added support for Element.selectFirst(query), to efficiently find the first matching element.\n\n  * Added Element.appendTo(parent) to simplify slinging elements about.\n    <https://github.com/jhy/jsoup/pull/662>\n\n  * Added support for multiple headers with the same name in Jsoup.Connect\n\n  * Added Element.shallowClone() and Node.shallowClone(), to allow cloning nodes without getting all their children.\n    <https://github.com/jhy/jsoup/issues/900>\n\n  * Updated Element.text() and the :contains(text) selector to consider &nbsp; character as spaces.\n\n  * Updated Jsoup.connect().timeout() to implement a total connect + combined read timeout. Previously it specified\n    connect and buffer read times only, so to implement a combined total timeout, you had to have another thread send\n    an interrupt.\n\n  * Improved performance of Node.addChildren (was quadratic)\n    <https://github.com/jhy/jsoup/pull/930>\n\n  * Added missing support for template tags in tables\n    <https://github.com/jhy/jsoup/pull/901>\n\n  * In Jsoup.connect file uploads, added the ability to set the uploaded files' mimetype.\n     <https://github.com/jhy/jsoup/issues/936>\n\n  * Improved Node traversal, including less object creation, and partial and filtering traversor support.\n    <https://github.com/jhy/jsoup/pull/849>\n\n  * Bugfix: if a document was re-decoded after character set detection, the HTML parser was not reset correctly,\n    which could lead to an incorrect DOM.\n    <https://github.com/jhy/jsoup/issues/877>\n\n  * Bugfix: attributes with the same name but different case would be incorrectly treated as different attributes.\n    <https://github.com/jhy/jsoup/pull/903>\n\n  * Bugfix: self-closing tags for known empty elements were incorrectly treated as errors.\n    <https://github.com/jhy/jsoup/issues/868>\n\n  * Bugfix: fixed an issue where a self-closing title, noframes, or style tag would cause the rest of the page to be\n    incorrectly parsed as data or text.\n    <https://github.com/jhy/jsoup/issues/906>\n\n  * Bugfix: fixed an issue with unknown mixed-case tags\n    <https://github.com/jhy/jsoup/pull/942>\n\n  * Bugfix: fixed an issue where the entity resources were left open after startup, causing a warning.\n    <https://github.com/jhy/jsoup/pull/928>\n\n  * Bugfix: fixed an issue where Element.getElementsByIndexLessThan(index) would incorrectly provide the root element\n    <https://github.com/jhy/jsoup/pull/918>\n\n  * Improved parse time for pages with exceptionally deeply nested tags.\n    <https://github.com/jhy/jsoup/issues/955>\n\n  * Improvement / workaround: modified the Entities implementation to load its data from a .class vs from a jar resource.\n    Faster, and safer on Android.\n    <https://github.com/jhy/jsoup/issues/959>\n\n*** Release 1.10.3 [2017-Jun-11]\n  * Added Elements.eachText() and Elements.eachAttr(name), which return a list of Element's text or attribute values,\n    respectively. This makes it simpler to for example get a list of each URL on a page:\n    List<String> urls = doc.select(\"a\").eachAttr(\"abs:href\"\");\n\n  * Improved selector validation for :contains(...) with unbalanced quotes.\n    <https://github.com/jhy/jsoup/issues/803>\n\n  * Improved the speed of index based CSS selectors and other methods that use elementSiblingIndex, by a factor of 34x.\n    <https://github.com/jhy/jsoup/pull/862>\n\n  * Added Node.clearAttributes(), to simplify removing of all attributes of a Node / Element.\n    <https://github.com/jhy/jsoup/issues/829>\n\n  * Bugfix: if an attribute name started or ended with a control character, the parse would fail with a validation\n    exception.\n    <https://github.com/jhy/jsoup/issues/793>\n\n  * Bugfix: Element.hasClass() and the \".classname\" selector would not find the class attribute case-insensitively.\n    <https://github.com/jhy/jsoup/issues/814>\n\n  * Bugfix: In Jsoup.Connection, if a redirect contained a query string with %xx escapes, they would be double escaped\n    before the redirect was followed, leading to fetching an incorrect location.\n\n  * Bugfix: In Jsoup.Connection, if a request body was set and the connection was redirected, the body would incorrectly\n    still be sent.\n    <https://github.com/jhy/jsoup/pull/881>\n\n  * Bugfix: In DataUtil when detecting the character set from meta data, and there are two Content-Types defined, use\n    the one that defines a character set.\n    <https://github.com/jhy/jsoup/pull/835>\n\n  * Bugfix: when parsing unknown tags in case-sensitive HTML mode, end tags would not close scope correctly.\n    <https://github.com/jhy/jsoup/issues/819>\n\n  * In Jsoup.Connection, ensure there is no Content-Type set when being redirected to a GET.\n    <https://github.com/jhy/jsoup/pull/895>\n\n  * Bugfix: in certain locales (Turkey specifically), lowercasing and case insensitivity could fail for specific items.\n    <https://github.com/jhy/jsoup/pull/820>\n\n  * Bugfix: after an element was cloned, changes to its child list where not notifying the element correctly.\n    <https://github.com/jhy/jsoup/issues/951>\n\n*** Release 1.10.2 [2017-Jan-02]\n * Improved startup time, particularly on Android, by reducing garbage generation and CPU execution time when loading\n   the HTML entity files. About 1.72x faster in this area.\n\n * Added Element.is(query) to check if an element matches this CSS query.\n\n * Added new methods to Elements: next(query), nextAll(query), prev(query), prevAll(query) to select next and previous\n   element siblings from a current selection, with optional selectors.\n\n * Added Node.root() to get the topmost ancestor of a Node.\n\n * Added the new selector :containsData(), to find elements that hold data, like script and style tags.\n\n * Changed Jsoup.isValid(bodyHtml) to validate that the input contains only body HTML that is safe according to the\n   safelist, and does not include HTML errors. And in the Jsoup.Cleaner.isValid(Document) method, make sure the doc\n   only includes body HTML.\n   <https://github.com/jhy/jsoup/issues/245>\n   <https://github.com/jhy/jsoup/issues/632>\n\n * In Safelists, validate that a removed protocol exists before removing said protocol.\n\n * Allow the Jsoup.Connect thread to be interrupted when reading the input stream; helps when reading from a long stream\n   of data that doesn't read timeout.\n   <https://github.com/jhy/jsoup/pull/712>\n\n * Jsoup.Connect now uses a desktop user agent by default. Many developers were getting caught by not specifying the\n   user agent, and sending the default 'Java'. That causes many servers to return different content than what they would\n   to a desktop browser, and what the developer was expecting.\n\n * Increased the default connect/read timeout in Jsoup.Connect to 30 seconds.\n\n * Jsoup.Connect now detects if a header value is actually in UTF-8 vs the HTTP spec of ISO-8859, and converts\n   the header value appropriately. This improves compatibility with servers that are configured incorrectly.\n\n * Bugfix: in Jsoup.Connect, URLs containing non-URL-safe characters were not encoded to URL safe correctly.\n   <https://github.com/jhy/jsoup/issues/706>\n\n * Bugfix: a \"SYSTEM\" flag in doctype tags would be incorrectly removed.\n   <https://github.com/jhy/jsoup/issues/408>\n\n * Bugfix: removing attributes from an Element with removeAttr() would cause a ConcurrentModificationException.\n\n * Bugfix: the contents of Comment nodes were not returned by Element.data()\n\n * Bugfix: if source checked out on Windows with git autocrlf=true, Entities.load would fail because of the \\r char.\n\n*** Release 1.10.1 [2016-Oct-23]\n * New feature: added the option to preserve case for tags and/or attributes, with ParseSettings. By default, the HTML\n   parser will continue to normalize tag names and attribute names to lower case, and the XML parser will now preserve\n   case, according to the relevant spec. The CSS selectors for tags and attributes remain case insensitive, per the CSS\n   spec.\n\n * Improved support for extended HTML entities, including supplemental characters and multiple character references.\n   Also reduced memory consumption of the entity tables.\n   <https://github.com/jhy/jsoup/issues/602>\n   <https://github.com/jhy/jsoup/issues/603>\n\n * Added support for *|E wildcard namespace selectors.\n   <https://github.com/jhy/jsoup/pull/724>\n\n * Added support for setting multiple connection headers at once with Connection.headers(Map)\n   <https://github.com/jhy/jsoup/pull/725>\n\n * Added support for setting/overriding the response character set in Connection.Response, for cases where the charset\n   is not defined by the server, or is defined incorrectly.\n   <https://github.com/jhy/jsoup/issues/743>\n\n * Improved performance of class selectors by reducing memory allocation and garbage collection.\n   <https://github.com/jhy/jsoup/pull/753>\n\n * Improved performance of HTML output by reducing the creation of temporary attribute list iterators.\n   <https://github.com/jhy/jsoup/pull/755>\n\n * Fixed an issue when converting to the W3CDom XML, where valid (but ugly) HTML attribute names containing characters\n   like '\"' could not be converted into valid XML attribute names. These attribute names are now normalized if possible,\n   or not added to the XML DOM.\n   <https://github.com/jhy/jsoup/issues/721>\n\n * Fixed an OOB exception when loading an empty-body URL and parsing with the XML parser.\n   <https://github.com/jhy/jsoup/issues/727>\n\n * Fixed an issue where attribute names starting with a slash would be parsed incorrectly.\n   <https://github.com/jhy/jsoup/pull/748>\n\n * Don't reuse charset encoders from OutputSettings, to make threadsafe.\n   <https://github.com/jhy/jsoup/issues/740>\n\n * Fixed an issue in connections with a requestBody where a custom content-type header could be ignored.\n   <https://github.com/jhy/jsoup/issues/756>\n\n*** Release 1.9.2 [2016-May-17]\n * Fixed an issue where tag names that contained non-ascii characters but started with an ascii character\n   would cause the parser to get stuck in an infinite loop.\n   <https://github.com/jhy/jsoup/issues/704>\n\n * In XML documents, detect the charset from the XML prolog - <?xml encoding=\"UTF-8\"?>\n   <https://github.com/jhy/jsoup/issues/701>\n\n * Fixed an issue where created XML documents would have an incorrect prolog.\n   <https://github.com/jhy/jsoup/issues/652>\n\n * Fixed an issue where you could not use an attribute selector to find values containing unbalanced braces or\n   parentheses.\n   <https://github.com/jhy/jsoup/issues/611>\n\n * Fixed an issue where namespaced tags (like <fb:comment>) would cause Element.cssSelector() to fail.\n   <https://github.com/jhy/jsoup/pull/677>\n\n*** Release 1.9.1 [2016-Apr-16]\n * Added support for HTTP and SOCKS request proxies, specifiable per connection.\n   <https://github.com/jhy/jsoup/pull/570>\n\n * Added support for sending plain HTTP request bodies in POST and PUT requests, with Connection.requestBody(String).\n\n * Added support in Jsoup.Connect for HEAD, OPTIONS, TRACE.\n   <https://github.com/jhy/jsoup/issues/613>\n\n * Added support for HTTP 307 Temporary Redirect (replays posts, if applicable).\n   <https://github.com/jhy/jsoup/pull/666>\n\n * Performance improvements when parsing HTML, particularly for Android Dalvik.\n\n * Added support for writing HTML into Appendable objects (like OutputStreamWriter), to enable stream serialization.\n   <https://github.com/jhy/jsoup/pull/470/>\n\n * Added support for XML namespaces when converting jsoup documents to W3C documents.\n   <https://github.com/jhy/jsoup/pull/672>\n\n * Added support for UTF-16 and UTF-32 character set detection from byte-order-marks (BOM).\n   <https://github.com/jhy/jsoup/issues/695>\n\n * Added support for tags with non-ascii (unicode) letters.\n   <https://github.com/jhy/jsoup/issues/667>\n\n * Added Connection.data(key) to retrieve a data KeyVal by its key. Useful to update form data before submission.\n\n * Fixed an issue in the Parent selector where it would not match against the root element it was applied to.\n   <https://github.com/jhy/jsoup/pull/619>\n\n * Fix an issue where elements.select(query) would not return every matching element if they had the same content.\n   <https://github.com/jhy/jsoup/issues/614>\n\n * Added not-null validators to Element.appendText() and Element.prependText()\n   <https://github.com/jhy/jsoup/issues/690>\n\n * Fixed an issue when moving nodes using Element.insert(index, children) where the sibling index would be set\n   incorrectly, leading to the original loads being lost.\n   <https://github.com/jhy/jsoup/issues/689>\n\n * Reverted Node.equals() and Node.hashCode() back to identity (object) comparisons, as deep content inspection\n   had negative performance impacts and hashkey stability problems. Functionality replaced with Node.hasSameContent().\n   <https://github.com/jhy/jsoup/issues/688>\n\n * In Jsoup.Connect, if the same header key is seen multiple times, combine their values with a comma per the HTTP RFC,\n   instead of keeping just one value. Also fixes an issue where header values could be out of order.\n   <https://github.com/jhy/jsoup/issues/618>\n\n*** Release 1.8.3 [2015-Aug-02]\n * Added support for custom boolean attributes.\n   <https://github.com/jhy/jsoup/pull/555>\n\n * When fetching XML URLs, automatically switch to the XML parser instead of the HTML parser.\n   <https://github.com/jhy/jsoup/pull/574>\n\n * Performance improvement on parsing larger HTML pages. On Android KitKat, around 1.7x times faster. On Android\n   Lollipop, ~ 1.3x faster. Improvements largely from re-ordering the HtmlTreeBuilder methods based on analysis of\n   various websites; also from further memory reduction for nodes with no children, and other tweaks.\n   \n * Fixed an issue in Element.getElementSiblingIndex (and related methods) where sibling elements with the same content\n   would incorrectly have the same sibling index.\n   <https://github.com/jhy/jsoup/issues/554>\n\n * Fixed an issue where unexpected elements in a badly nested table could be moved to the wrong location in the\n   document.\n   <https://github.com/jhy/jsoup/issues/552>\n\n * Fixed an issue where a table nested within a TH cell would parse to an incorrect tree.\n   <https://github.com/jhy/jsoup/issues/575>\n\n * When serializing a document using the XHTML encoding entities, if the character set did not support &nbsp; chars\n   (such as Shift_JIS), the character would be skipped. For visibility, will now always output &xa0; when using XHTML\n   encoding entities (as &nbsp; is not defined), regardless of the output character set.\n   <https://github.com/jhy/jsoup/issues/523>\n\n * Fixed an issue when resolving URLs, if the absolute URL had no path, the relative URL was not normalized correctly.\n   Also fixed an issue where connections that were redirected to a relative URL did not have the same normalization\n   rules as a URL read from Nodes.absUrl(String).\n   <https://github.com/jhy/jsoup/issues/585>\n\n * When serialising XML, ensure that '<' characters in attributes are escaped, per spec. Not required in HTML.\n   <https://github.com/jhy/jsoup/issues/528>\n\n*** Release 1.8.2 [2015-Apr-13]\n * Performance improvements for parsing HTML on Android, of 1.5x to 1.9x, with larger parses getting a bigger\n   speed increase. For non-Android JREs, around 1.1x to 1.2x.\n\n * Dramatic performance improvement in HTML serialization on Android (KitKat and later), of 115x. Improvement by working\n   around a character set encoding speed regression in Android.\n   <https://github.com/jhy/jsoup/issues/383>\n\n * Performance improvement for the class name selector on Android (.class) of 2.5x to 14x. Around 1.2x\n   on non-Android JREs.\n\n * File upload support. Added the ability to specify input streams for POST data, which will upload content in\n   MIME multipart/form-data encoding.\n\n * Add a meta-charset element to documents when setting the character set, so that the document's charset is\n   unambiguous.\n   <https://github.com/jhy/jsoup/pull/486>\n\n * Added ability to disable TLS (SSL) certificate validation. Helpful if you're hitting a host with a bad cert,\n   or your JDK doesn't support SNI.\n   <https://github.com/jhy/jsoup/pull/343>\n\n * Added ability to further tweak the canned Cleaner Safelists by removing existing settings.\n   <https://github.com/jhy/jsoup/pull/449>\n\n * Added option in Cleaner Safelist to allow linking to in-page anchors (#)\n   <https://github.com/jhy/jsoup/pull/441>\n\n * Use a lowercase doctype tag for HTML5 documents.\n\n * Add support for 201 Created with redirect, and other status codes. Treats any HTTP status code 2xx or 3xx as an OK\n   response, and follow redirects whenever there is a Location header.\n   <https://github.com/jhy/jsoup/issues/312>\n\n * Added support for HTTP method verbs PUT, DELETE, and PATCH.\n\n * Added support for overriding the default POST character of UTF-8\n   <https://github.com/jhy/jsoup/pull/491>\n\n * W3C DOM support: added ability to convert from a jsoup document to a W3C document, with the W3Dom helper class.\n\n * In the HtmlToPlainText example program, added the ability to filter using a CSS selector. Also clarified\n   the usage documentation.\n\n * Fixed validation of cookie names in HttpConnection cookie methods.\n   <https://github.com/jhy/jsoup/pull/377>\n\n * Fixed an issue where <option> tags would be missed when preparing a form for submission if missing a selected\n   attribute.\n\n * Fixed an issue where submitting a form would incorrectly include radio and checkbox values without the checked\n   attribute.\n\n * Fixed an issue where Element.classNames() would return a set containing an empty class; and may have extraneous\n   whitespace.\n   <https://github.com/jhy/jsoup/pull/469>\n\n * Fixed an issue where attributes selected by value were not correctly space normalized.\n   <https://github.com/jhy/jsoup/pull/526>\n\n * In head+noscript elements, treat content as character data, instead of jumping out of head parsing.\n   <https://github.com/jhy/jsoup/pull/540>\n\n * Fixed performance issue when parsing HTML with elements with many children that need re-parenting.\n   <https://github.com/jhy/jsoup/pull/506>\n\n * Fixed an issue where a server returning an unsupported character set response would cause a runtime\n   UnsupportedCharsetException, instead of falling back to the default UTF-8 charset.\n   <https://github.com/jhy/jsoup/pull/509>\n\n * Fixed an issue where Jsoup.Connection would throw an IO Exception when reading a page with zero content-length.\n   <https://github.com/jhy/jsoup/issues/538>\n\n * Improved the equals() and hashcode() methods in Node, to consider all their child content, for DOM tree comparisons.\n   <https://github.com/jhy/jsoup/issues/537>\n\n * Improved performance in Selector when searching multiple roots.\n   <https://github.com/jhy/jsoup/issues/518>\n\n*** Release 1.8.1 [2014-Sep-27]\n * Introduced the ability to chose between HTML and XML output, and made HTML the default. This means img tags are\n   output as <img>, not <img />. XML is the default when using the XmlTreeBuilder. Control this with the\n   Document.OutputSettings.syntax() method.\n\n * Improved the performance of Element.text() by 3.2x\n\n * Improved the performance of Element.html() by 1.7x\n\n * Improved file read time by 2x, giving around a 10% speed improvement to file parses.\n   <https://github.com/jhy/jsoup/issues/248>\n\n * Tightened the scope of what characters are escaped in attributes and textnodes, to align with the spec. Also, when\n   using the extended escape entities map, only escape a character if the current output charset does not support it.\n   This produces smaller, more legible HTML, with greater control over the output (by setting charset and escape mode).\n\n * If pretty-print is disabled, don't trim outer whitespace in Element.html()\n   <https://github.com/jhy/jsoup/issues/368>\n\n * In the HTML Cleaner, allow span tags in the basic safelist, and span and div tags in the relaxed safelist.\n\n  * Added Element.cssSelector(), which returns a unique CSS selector/path for an element.\n    <https://github.com/jhy/jsoup/pull/459>\n\n * Fixed an issue where <svg><img/></svg> was parsed as <svg><image/></svg>\n   <https://github.com/jhy/jsoup/issues/364>\n\n * Fixed an issue where a UTF-8 BOM character was not detected if the HTTP response did not specify a charset, and\n   the HTML body did, leading to the head contents incorrectly being parsed into the body. Changed the behavior so that\n   when the UTF-8 BOM is detected, it will take precedence for determining the charset to decode with.\n   <https://github.com/jhy/jsoup/issues/348>\n\n * Relaxed doctype validation, allowing doctypes to not specify a name.\n   <https://github.com/jhy/jsoup/issues/460>\n\n * Fixed an issue in parsing a base URI when loading a URL containing a http-equiv element.\n   <https://github.com/jhy/jsoup/issues/440>\n\n * Fixed an issue for Java 1.5 / Android 2.2 compatibility, and verify it doesn't regress.\n   <https://github.com/jhy/jsoup/issues/375>\n   <https://github.com/jhy/jsoup/pull/403>\n\n * Fixed an issue that would throw an NPE when trying to set invalid HTML into a title element.\n   <https://github.com/jhy/jsoup/pull/410>\n\n * Added support for quoted attribute values in CSS Selectors\n   <https://github.com/jhy/jsoup/pull/400>\n\n * Fixed support for nth-of-type selectors with unknown tags.\n   <https://github.com/jhy/jsoup/pull/402>\n\n * Added support for 'application/*+xml' mimetypes.\n   <https://github.com/jhy/jsoup/pull/444>\n\n * Fixed support for allowing script tags in cleaner Safelists.\n   <https://github.com/jhy/jsoup/issues/299>\n   <https://github.com/jhy/jsoup/issues/388>\n\n * In FormElements, don't submit disabled inputs, and use 'on' as checkbox value default.\n   <https://github.com/jhy/jsoup/issues/489>\n\n*** Release 1.7.3 [2013-Nov-10]\n * Introduced FormElement, providing easy access to form controls and their data, and the ability to submit forms\n   with Jsoup.Connect.\n\n * Reduced GC impact during HTML parsing, with 17% fewer objects created, and 3% faster parses.\n\n * Reduced CSS selection time by 26% for common queries.\n\n * Improved HTTP character set detection.\n   <https://github.com/jhy/jsoup/pull/325> <https://github.com/jhy/jsoup/issues/321>\n\n * Added Document.location, to get the URL the document was retrieved from. Helpful if connection was redirected.\n   <https://github.com/jhy/jsoup/pull/306>\n\n * Fixed support for self-closing script tags.\n   <https://github.com/jhy/jsoup/issues/305>\n\n * Fixed a crash when reading an unterminated CDATA section.\n   <https://github.com/jhy/jsoup/issues/349>\n\n * Fixed an issue where elements added via the adoption agency algorithm did not preserve their attributes.\n   <https://github.com/jhy/jsoup/issues/313>\n\n * Fixed an issue when cloning a document with extremely nested elements that could cause a stack-overflow.\n   <https://github.com/jhy/jsoup/issues/290>\n\n * Fixed an issue when connecting or redirecting to a URL that contains a space.\n   <https://github.com/jhy/jsoup/pull/354> <https://github.com/jhy/jsoup/issues/114>\n\n * Added support for the HTTP/1.1 Temporary Redirect (307) status code.\n   <https://github.com/jhy/jsoup/issues/452>\n\n*** Release 1.7.2 [2013-Jan-27]\n * Added support for supplementary characters outside of the Basic Multilingual Plane.\n   <https://github.com/jhy/jsoup/issues/288> <https://github.com/jhy/jsoup/pull/289>\n\n * Added support for structural pseudo CSS selectors, including :first-child, :last-child, :nth-child, :nth-last-child,\n   :first-of-type, :last-of-type, :nth-of-type, :nth-last-of-type, :only-child, :only-of-type, :empty, and :root\n   <https://github.com/jhy/jsoup/pull/208>\n\n * Added a maximum body response size to Jsoup.Connection, to prevent running out of memory when trying to read\n   extremely large documents. The default is 1MB.\n\n * Refactored the Cleaner to traverse rather than recurse child nodes, to avoid the risk of overflowing the stack.\n   <https://github.com/jhy/jsoup/issues/246>\n\n * Added Element.insertChildren(), to easily insert a list of child nodes at a specific index.\n   <https://github.com/jhy/jsoup/issues/239>\n\n * Added Node.childNodesCopy(), to create an independent copy of a Node's children.\n\n * When parsing in XML mode, preserve XML declarations (<?xml ... ?>).\n   <https://github.com/jhy/jsoup/issues/242>\n\n * Introduced Parser.parseXmlFragment(), to allow easy parsing of XML fragments.\n   <https://github.com/jhy/jsoup/issues/279>\n\n * Allow Safelist test methods to be extended\n   <https://github.com/jhy/jsoup/issues/85>\n\n * Added Document.OutputSettings.outline mode, to aid HTML debugging by printing out in outline mode, similar to\n   browser HTML inspectors.\n   <https://github.com/jhy/jsoup/issues/273>\n\n * When parsing, allow all tags to self-close. Tags that aren't expected to self-close will get an end tag.\n   <https://github.com/jhy/jsoup/issues/258>\n\n * Fixed an issue when parsing <textarea>/RCData tags containing unescaped closing tags that would drop the trailing >.\n\n * Corrected the javadoc for Element#child() to note that it throws IndexOutOfBounds.\n   <https://github.com/jhy/jsoup/issues/277>\n\n * When cloning an Element, reset the classnames set so as not to hold a pointer to the source's.\n   <https://github.com/jhy/jsoup/issues/278>\n\n * Limit how far up the stack the formatting adoption agency algorithm will travel, to prevent the chance of a run-away\n   parse when the HTML stack is hopelessly deep.\n   <https://github.com/jhy/jsoup/issues/234>\n\n * Modified Element.text() to build text by traversing child nodes rather than recursing. This avoids stack-overflow\n   errors when the DOM is very deep and the VM stack-size is low.\n   <https://github.com/jhy/jsoup/issues/271>\n\n*** Release 1.7.1 [2012-Sep-23]\n * Improved parse time, now 2.3x faster than previous release, with lower memory consumption.\n\n * Reduced memory consumption when selecting elements.\n\n * Introduced finer granularity of exceptions in Jsoup.connect, including HttpStatusException and\n   UnsupportedMimeTypeException.\n   <https://github.com/jhy/jsoup/issues/229>\n\n * Fixed an issue when determining the Windows-1254 character-set from a meta tag when run in the Turkish locale.\n   <https://github.com/jhy/jsoup/issues/191>\n\n * Fixed whitespace preservation in <textarea> tags.\n   <https://github.com/jhy/jsoup/issues/167>\n\n * In jsoup.connect, fail faster if the return content type is not supported.\n   <https://github.com/jhy/jsoup/issues/153>\n\n * In jsoup.clean, allow custom OutputSettings, to control pretty printing, character set, and entity escaping.\n   <https://github.com/jhy/jsoup/issues/148>\n\n * Fixed an issue that prevented frameset documents to be cleaned by the Cleaner.\n   <https://github.com/jhy/jsoup/issues/154>\n\n * Fixed an issue when normalising whitespace for strings containing high-surrogate characters.\n   <https://github.com/jhy/jsoup/issues/214>\n\n * If a server doesn't specify a content-type header, treat that as OK.\n   <https://github.com/jhy/jsoup/issues/213>\n\n * If a server returns an unsupported character-set header, attempt to decode the content with the default charset\n   (UTF8), instead of bailing with an unsupported charset exception.\n   <https://github.com/jhy/jsoup/issues/215>\n\n * Removed an unnecessary synchronisation in Tag.valueOf, allowing multi-threaded parsing to run faster.\n   <https://github.com/jhy/jsoup/issues/238>\n\n * Made entity decoding less greedy, so that non-entities are less likely to be incorrectly treated as entities.\n   <https://github.com/jhy/jsoup/issues/224>\n\n * Whitespace normalise document.title() output.\n   <https://github.com/jhy/jsoup/issues/168>\n\n * In Jsoup.connection, enforce a connection disconnect after every connect. This precludes keep-alive connections to\n   the same host, but in practise many implementations will leak connections, particularly on error.\n\n*** Release 1.6.3 [2012-May-28]\n * Fixed parsing of group-or commas in CSS selectors, to correctly handle sub-queries containing commas.\n   <https://github.com/jhy/jsoup/issues/179>\n\n * If a node has no parent, return null on previousSibling and nextSibling instead of throwing a null pointer exception.\n   <https://github.com/jhy/jsoup/issues/184>\n\n * Updated Node.siblingNodes() and Element.siblingElements() to exclude the current node (a node is not its own sibling).\n\n * Fixed HTML entity parser to correctly parse entities like frac14 (letter + number combo).\n   <https://github.com/jhy/jsoup/issues/145>\n\n * Fixed issue where contents of a script tag within a comment could be incorrectly parsed.\n   <https://github.com/jhy/jsoup/issues/115>\n\n * Fixed GAE support: load HTML entities from a file on startup, instead of embedding in the class.\n\n * Fixed NPE when HTML fragment parsing a <style> tag\n   <https://github.com/jhy/jsoup/issues/189>\n\n * Fixed issue with :all pseudo-tag in HTML sanitizer when cleaning tags previously defined in safelist\n   <https://github.com/jhy/jsoup/issues/156>\n\n * Fixed NPE in Parser.parseFragment() when context parameter is null.\n   <https://github.com/jhy/jsoup/issues/195>\n\n * In HTML Safelists, when defining allowed attributes for a tag, automatically add the tag to the allowed list.\n\n*** Release 1.6.2 [2012-Mar-27]\n * Added a simplified XML parsing mode, which can usefully parse valid and invalid XML, but does not enforce any HTML\n   document structure or special tag behaviour.\n\n * Added the optional ability to track errors when tokenising and parsing.\n\n * Added jsoup.connect.cookies(Map) method, to set multiple cookies at once, possibly from a prior request.\n\n * Added Element.textNodes() and Element.dataNodes(), to easily access an element's children text nodes and data nodes.\n\n * Added an example program that demonstrates how to format HTML as plain-text, and the use of the NodeVisitor interface.\n\n * Added Node.traverse() and Elements.traverse() methods, to iterate through a node's descendants.\n \n * Updated jsoup.connect so that when requests made as POSTs are redirected, the redirect is followed as a GET.\n   <https://github.com/jhy/jsoup/issues/120>\n\n * Updated the Cleaner and Safelists to optionally preserve related links in elements, instead of converting them\n   to absolute links.\n\n * Updated the Cleaner to support custom allowed protocols such as \"cid:\" and \"data:\".\n   <https://github.com/jhy/jsoup/issues/127>\n\n * Updated handling of <base href> tags, to act on only the first one seen when parsing, to align with modern browsers.\n\n * Updated Node.setBaseUri(), to recursively set on all the node's descendants.\n\n * Fixed handling of null characters within comments.\n   <https://github.com/jhy/jsoup/issues/121>\n\n * Tweaked escaped entity detection in attributes to not treat &entity_... as an entity form.\n   <https://github.com/jhy/jsoup/issues/129>\n\n * Fixed doctype tokeniser to allow whitespace between name and public identifier.\n\n * Fixed issue where comments within a table tag would be duplicate-fostered into body.\n   <https://github.com/jhy/jsoup/pull/165>\n\n * Fixed an issue where a spurious byte-order-mark at the start of a document would cause the parser to miss head\n   contents.\n   <https://github.com/jhy/jsoup/issues/134>\n\n * Fixed an issue where content after a frameset could cause a NPE crash. Now correctly implements spec and ignores\n   the trailing content.\n   <https://github.com/jhy/jsoup/issues/162>\n\n * Tweaked whitespace checks to align with HTML spec\n   <https://github.com/jhy/jsoup/pull/175>\n\n * Tweaked HTML output of closing script and style tags to not add an extraneous newline when pretty-printing.\n\n * Substantially reduced default memory allocation within Node.outerHtml, to reduce memory pressure when serialising\n   smaller DOMs.\n   <https://github.com/jhy/jsoup/issues/143>\n\n*** Release 1.6.1 [2011-Jul-02]\n * Fixed Java 1.5 compatibility.\n   <https://github.com/jhy/jsoup/issues/103>\n\n * Fixed an issue when parsing <script> tags in body where the tokeniser wouldn't switch to the InScript state, which\n   meant that data wasn't parsed correctly.\n   <https://github.com/jhy/jsoup/issues/104>\n\n * Fixed an issue with a missing quote when serialising DocumentType nodes.\n   <https://github.com/jhy/jsoup/issues/109>\n\n * Fixed issue where a single 0 character was lexed incorrectly as a null character.\n   <https://github.com/jhy/jsoup/issues/107>\n\n * Fixed normalisation of carriage returns to newlines on input HTML.\n   <https://github.com/jhy/jsoup/issues/110>\n\n * Disabled memory mapped files when loading files from disk, to improve compatibility in Windows environments.\n\n*** Release 1.6.0 [2011-Jun-13]\n * HTML5 conformant parser. Complete reimplementation of HTML tokenisation and parsing, to implement the\n   http://whatwg.org/html spec. This ensures jsoup parses HTML identically to current modern browsers.\n\n * When parsing files from disk, files are loaded via memory mapping, to increase parse speed.\n\n * Reduced memory overhead and lowered garbage collector pressure with Attribute, Node and Element model optimisations.\n\n * Improved \"abs:\" absolute URL handling in Elements.attr(\"abs:href\") and Node.hasAttr(\"abs:href\").\n   <https://github.com/jhy/jsoup/issues/97>\n\n * Fixed cookie handling issue in jsoup.Connect where empty cookies would cause a validation exception.\n   <https://github.com/jhy/jsoup/issues/87>\n\n * Added jsoup.Connect configuration options to allow HTTP errors to be ignored, and the content-type to be ignored.\n   Contributed by Jesse Piascik (piascikj)\n   <https://github.com/jhy/jsoup/pull/78>\n\n * Added Node.before(node) and Node.after(node), to allow existing nodes to be moved, or new nodes to be inserted, into\n   precise DOM positions.\n\n * Added Node.unwrap() and Elements.unwrap(), to remove a node but keep its contents. Useful for e.g. removing unwanted\n   formatting tags.\n   <https://github.com/jhy/jsoup/issues/100>\n\n * Now handles unclosed <title> tags in document by breaking out of the title at the next start tag, instead of\n   eating up to the end of the document.\n   <https://github.com/jhy/jsoup/issues/82>\n\n * Added OSGi bundle support to the jsoup package jar.\n   <https://github.com/jhy/jsoup/issues/98>\n\n*** Release 1.5.2 [2011-Feb-27]\n * Fixed issue with selector parser where some boolean AND + OR combined queries (e.g. \"meta[http-equiv], meta[content]\")\n   were being parsed incorrectly as OR only queries (e.g. former as \"meta, [http-equiv], meta[content]\")\n\n * Fixed issue where a content-type specified in a meta tag may not be reliably detected, due to the above issue.\n\n * Updated Element.text() and Element.ownText() methods to ensure <br> tags output as whitespace.\n\n * Tweaked Element.outerHtml() method to not generate initial newline on first output element.\n\n *** Release 1.5.1 [2011-Feb-19]\n\n * Integrated new single-pass selector evaluators, contributed by knz (Anton Kazennikov). This significantly speeds up\n   the execution of combined selector queries.\n\n * Implemented workaround to fix Scala support. Contributed by bbeck (Brandon Beck).\n\n * Added ability to change an element's tag with Element.tagName(String), and to change many at once\n   with Elements.tagName(String).\n\n * Added Node.wrap(html), Node.before(html), and Node.after(html), to allow HTML to be easily added to all nodes. These\n   functions were previously supported on Elements only.\n\n * Added TextNode.splitText(index), which allows a text node to be split into two nodes at a specified index point.\n   This is convenient if you need to surround some text in an element.\n\n * Updated Jsoup.Connection so that cookies set on a redirect response will be included on both the redirected request\n   and response.\n\n * Infinite redirection loops in Jsoup.Connect are now prevented.\n\n * Allow Jsoup.Connect to parse application/xml and application/xhtml+xml responses.\n\n * Modified Jsoup.Connect to always follow relative links, regardless of the underlying HTTP sub-system.\n\n * Defined U (underline) element as an inline tag.\n\n * Force strict entity matching (must be &xxx; and not &xxx) in element attributes.\n\n * Implemented clone method for Elements (contributed by knz).\n\n * Fixed tokeniser optimisation when scanning for missing data element close tags.\n\n * Fixed issue when using descendant regex attribute selectors. \n\n  *** Release 1.4.1 [2010-Nov-23]\n\n * Added ability to load and parse HTML from an input stream.\n\n * Implemented Node.clone() to create deep, independent copies of Nodes, Elements, and Documents.\n\n * Added :not() selector, to find elements that do not match the selector. E.g. div:not(.logo) finds divs that\n   do not have the \"logo\" class name.\n\n * Added Elements.not(selector) method, to remove undesired results from selector results.\n\n * Implemented DataNode.setWholeData() to allow updating of script and style data contents.\n\n * Relaxed parse rules of H1 - H6, to allow nested content. This is against spec, but matches browser and publisher\n   behaviour.\n\n * Relaxed parse rule of SPAN to treat as block, to allow nested block content.\n\n * Fixed issue in jsoup.connect when extracting character set from content-type header; now supports quoted\n   charset declaration.\n   \n * Fixed support for jsoup.connect to follow redirects between http & https URLs.\n\n * Document normalisation now more enthusiastically enforces the correct document structure.\n\n * Support node.outerHtml() method when node has no parent (e.g. when it has been removed from its DOM tree)\n\n * Fixed support for HTML entities with numbers in name (e.g. &frac34, &sup1).\n\n * Fixed absolute URL generation from relative URLs which are only query strings.\n\n*** Release 1.3.3 [2010-Sep-19]\n * Implemented Elements.empty() and Elements.remove(). This allows easy element removal, like:\n    doc.select(\"iframe\").remove();\n    \n * Fixed issue in Entities when unescaping &#36; (\"$\")\n   <http://github.com/jhy/jsoup/issues/issue/34>\n\n * Added restricted XHTML output entity option\n   <http://github.com/jhy/jsoup/issues/issue/35>\n\n*** Release 1.3.2 [2010-Aug-30]\n * Treat HTTP headers as case insensitive in Jsoup.Connection. Improves compatibility for HTTP responses.\n\n * Improved malformed table parsing by implementing ignorable end tags.\n\n*** Release 1.3.1 [2010-Aug-23]\n * Removed dependency on Apache Commons-lang. Jsoup now has no external dependencies.\n\n * Added new Connection implementation, to enable easier and richer HTTP requests that parse to Documents. This includes\n   support for gzip responses, cookies, headers, data parameters, user-agent, referrer, etc.\n  \n * Added Element.ownText() method, to get only the direct text of an element, not including the text of its children.\n \n * Added support for selectors :containsOwn(text) and :matchesOwn(regex), to supplement Element.ownText().\n\n * Added support for non-pretty-printed HTML output, to more closely mirror the input HTML.\n\n * Further speed optimisations for parsing and output generation.\n \n * Fixed support for case-sensitive HTML escape entities.\n   <http://github.com/jhy/jsoup/issues/issue/31>\n \n * Fixed issue when parsing tags with keyless attributes.\n   <http://github.com/jhy/jsoup/issues/issue/32>\n\n*** Release 1.2.3 [2010-Aug-04]\n * Added support for automatic input character set detection and decoding. Jsoup now automatically detects the encoding\n   character set when parsing HTML from a File or URL. The parser checks the content-type header, then the\n   <meta http-equiv> or <meta charset> tag, and finally falls back to UTF-8.\n\n * Added ability to configure the document's output charset, to control which characters are HTML escaped, and which\n   are kept intact. The output charset defaults to the document's input charset. This simplifies non-ascii output.\n\n * Added full support for all new HTML5 tags.\n\n * Added support for HTML5 dataset custom data attributes, with the Element.dataset() map.\n\n * Added support for the [^attributePrefix] selector query, to find elements with attributes starting with a prefix.\n   Useful for finding elements with datasets: [^data-] matches <p data-name=\"jsoup\">\n\n * Added support for namespaced elements (<fb:name>) and selectors to find them (fb|name)\n\n * Implemented Node.ownerDocument DOM method\n\n * Improved implicit table element handling (particularly around thead, tbody, and tfoot).\n\n * Improved HTML output format for empty elements and auto-detected self closing tags\n\n * Changed DT & DD tags to block-mode tags, to follow practice over spec\n\n * Added support for tag names with - and _ (<abc_foo>, <abc-foo>)\n\n * Handle tags with internal trailing space (<foo >)\n \n * Fixed support for character class regular expressions in [attr=~regex] selector\n\n*** Release 1.2.2 [2010-Jul-11]\n \n * Performance optimisation:\n    - core HTML parser engine now 3.5 times faster\n    - HTML generator now 2.5 times faster\n    - much lower memory use and garbage collection time\n    \n * Added support for :matches(regex) selector, to find elements containing text matching regular expression\n \n * Added support for [key~=regex] attribute selector, to find elements with attribute values matching regular expression\n\n * Upgraded the selector query parser to allow nested selectors like 'div:has(p:matches(regex))'\n\n*** Release 1.2.1 [2010-Jun-21]\n * Added .before(html) and .after(html) methods to Element and Elements, to insert sibling HTML\n \n * Added :contains(text) selector, to search for elements containing the specified text\n\n * Added :has(selector) pseudo-selector\n     <http://github.com/jhy/jsoup/issues/issue/20>\n\n * Added Element#parents and Elements#parents to retrieve an element's ancestor chain\n     <http://github.com/jhy/jsoup/issues/issue/20>\n\n * Fixes an issue where appending / prepending rows to a table (or  to similar implicit\n    element structures) would create a redundant wrapping elements\n       <http://github.com/jhy/jsoup/issues/issue/21>\n\n * Improved implicit close tag heuristic detection when parsing malformed HTML\n\n * Fixes an issue where text content after a script (or other data-node) was\n     incorrectly added to the data node.\n       <http://github.com/jhy/jsoup/issues/issue/22>\n\n * Fixes an issue where text order was incorrect when parsing pre-document\n    HTML.\n      <http://github.com/jhy/jsoup/issues/issue/23>\n\n*** Release 1.1.1 [2010-Jun-08]\n * Added selector support for :eq, :lt, and :gt\n \t<http://github.com/jhy/jsoup/issues/issue/16>\n\n * Added TextNode#text and TextNode#text(String)\n \t<http://github.com/jhy/jsoup/issues/issue/18>\n\n * Throw exception if trying to parse non-text content\n \t<http://github.com/jhy/jsoup/issues/issue/17>\n\n * Added Node#remove and Node#replaceWith\n \t<http://github.com/jhy/jsoup/issues/issue/19>\n\n * Allow _ and - in CSS ID selectors (per CSS spec).\n \t<http://github.com/jhy/jsoup/issues/issue/10>\n \n * Relative links are resolved to absolute when cleaning, to normalize\n    output and to verify safe protocol. (Were previously discarded.)\n      <http://github.com/jhy/jsoup/issues/issue/12>\n \n * Allow combinators at start of selector query, for query refinements\n \t  <http://github.com/jhy/jsoup/issues/issue/13>\n\n * Added Element#val() and #val(String) methods, for form values\n \t  <http://github.com/jhy/jsoup/issues/issue/14>\n\n * Changed textarea contents to parse as TextNodes, not DataNodes,\n    so contents visible to text() (and val(), as treated as form input)\n\n * Fixed support for Java 1.5\n\n*** Release 0.3.1 (2010-Feb-20)\n * New features: supports Elements#html(), html(String),\n    prepend(String), append(String); bulk methods for corresponding\n    methods in Element.\n\n * New feature: Jsoup.isValid(html, safelist) method for user input\n    form validation.\n \n * Improved Elements.attr(String) to find first matching element\n    with attribute.\n\n * Fixed assertion error when cleaning HTML with empty attribute\n \t  <http://github.com/jhy/jsoup/issues/issue/7>\n\n*** Release 0.2.2 (2010-Feb-07)\n * jsoup packages are now available in the Maven central repository.\n \n * New feature: supports Element#addClass, removeClass, toggleClass;\n    also collection class methods on Elements.\n * New feature: supports Element#wrap(html) and Elements#wrap(html).\n * New selector syntax: supports E + F adjacent sibling selector\n * New selector syntax: supports E ~ F preceding sibling selector\n * New: supports Element#elementSiblingIndex()\n\n * Improved document normalisation.\n * Improved HTML string output format (pretty-print)\n \n * Fixed absolute URL resolution issue when a base tag has no href.\n\n*** Release 0.1.2 (2010-Feb-02)\n * Fixed unrecognised tag handler to be more permissive\n    <http://github.com/jhy/jsoup/issues/issue/1>\n\n\n*** Release 0.1.1 (2010-Jan-31)\n * Initial beta release of jsoup\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <name>jsoup Java HTML Parser</name>\n\n  <groupId>org.jsoup</groupId>\n  <artifactId>jsoup</artifactId>\n  <version>1.22.2-SNAPSHOT</version><!-- remember to update previous version below for japicmp -->\n  <url>https://jsoup.org/</url>\n  <description>jsoup is a Java library that simplifies working with real-world HTML and XML. It offers an easy-to-use API for URL fetching, data parsing, extraction, and manipulation using DOM API methods, CSS, and xpath selectors. jsoup implements the WHATWG HTML5 specification, and parses HTML to the same DOM as modern browsers.</description>\n  <inceptionYear>2009</inceptionYear>\n  <issueManagement>\n    <system>GitHub</system>\n    <url>https://github.com/jhy/jsoup/issues</url>\n  </issueManagement>\n  <licenses>\n    <license>\n      <name>The MIT License</name>\n      <url>https://jsoup.org/license</url>\n      <distribution>repo</distribution>\n    </license>\n  </licenses>\n  <scm>\n    <url>https://github.com/jhy/jsoup</url>\n    <connection>scm:git:https://github.com/jhy/jsoup.git</connection>\n    <!-- <developerConnection>scm:git:git@github.com:jhy/jsoup.git</developerConnection> -->\n    <tag>HEAD</tag>\n  </scm>\n  <organization>\n    <name>Jonathan Hedley</name>\n    <url>https://jhedley.com/</url>\n  </organization>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <jetty.version>9.4.58.v20250814</jetty.version>\n  </properties>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.15.0</version>\n        <configuration>\n          <encoding>UTF-8</encoding>\n          <useIncrementalCompilation>false</useIncrementalCompilation>\n          <compilerArgs>\n            <arg>-Xpkginfo:always</arg>\n          </compilerArgs>\n        </configuration>\n        <executions>\n          <!-- Disable the default compile, so the profiles activated below execute -->\n          <execution>\n            <id>default-compile</id>\n            <phase>none</phase>\n          </execution>\n          <execution>\n            <id>default-testCompile</id>\n            <phase>none</phase>\n          </execution>\n        </executions>\n      </plugin>\n\n      <!-- Ensure Java 8 and Android 10 API compatibility -->\n      <plugin>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>animal-sniffer-maven-plugin</artifactId>\n        <version>1.27</version>\n        <executions>\n          <execution>\n            <id>api-java8</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>check</goal>\n            </goals>\n            <configuration>\n              <signature>\n                <groupId>org.codehaus.mojo.signature</groupId>\n                <artifactId>java18</artifactId>\n                <version>1.0</version>\n              </signature>\n              <ignores>\n                <ignore>java.net.HttpURLConnection</ignore><!-- .setAuthenticator(java.net.Authenticator) in Java 9; only used in multirelease 9+ version -->\n                <ignore>java.net.http.*</ignore><!-- HttpClient in Java 11; only used in multirelease 11+ version -->\n              </ignores>\n            </configuration>\n          </execution>\n          <execution>\n            <id>api-android21</id>\n            <phase>compile</phase>\n            <goals>\n              <goal>check</goal>\n            </goals>\n            <configuration>\n              <signature>\n                <groupId>com.toasttab.android</groupId>\n                <artifactId>gummy-bears-api-21</artifactId>\n                <version>0.10.0</version>\n                <!-- <classifier>coreLib2</classifier> -->\n                <!-- ^^ https://github.com/open-toast/gummy-bears says coreLib2 classifier for desugar should work, but I can't seem to wrangle Animal Sniffer to support that, so reverting to ignores for desugar -->\n              </signature>\n              <ignores>\n                <ignore>java.io.File</ignore> <!-- File#toPath() -->\n                <ignore>java.nio.file.*</ignore>\n                <ignore>java.nio.channels.SeekableByteChannel</ignore>\n                <ignore>java.util.function.*</ignore>\n                <ignore>java.util.stream.*</ignore>\n                <ignore>java.lang.ThreadLocal</ignore>\n                <ignore>java.io.UncheckedIOException</ignore>\n                <ignore>java.util.Comparator</ignore> <!-- Comparator.comparingInt() -->\n                <ignore>java.util.List</ignore> <!-- List#stream() -->\n                <ignore>java.util.ArrayList</ignore> <!-- List / ArrayList #sort() -->\n                <ignore>java.util.LinkedHashMap</ignore> <!-- LinkedHashMap#computeIfAbsent() -->\n                <ignore>java.util.Map</ignore> <!-- Map#computeIfAbsent() -->\n                <ignore>java.util.Objects</ignore>\n                <ignore>java.util.Optional</ignore>\n                <ignore>java.util.Set</ignore> <!-- Set#stream() -->\n                <ignore>java.util.Spliterator</ignore>\n                <ignore>java.util.Spliterators</ignore>\n                <ignore>java.nio.ByteBuffer</ignore> <!-- .flip(); added in API1; possibly due to .flip previously returning Buffer, later ByteBuffer; return unused -->\n                <ignore>java.net.HttpURLConnection</ignore><!-- .setAuthenticator(java.net.Authenticator) in Java 9; only used in multirelease 9+ version -->\n                <!-- HttpClient and following in Java 11; only used in multirelease 11+ version, guarded and not on Android -->\n                <ignore>java.net.http.*</ignore>\n                <ignore>java.time.Duration</ignore>\n                <ignore>java.util.OptionalLong</ignore>\n              </ignores>\n              <!-- ^ Provided by https://developer.android.com/studio/write/java8-support#library-desugaring -->\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-javadoc-plugin</artifactId>\n        <version>3.12.0</version>\n        <configuration>\n          <doclint>none</doclint>\n          <source>8</source>\n          <linksource>true</linksource>\n        </configuration>\n        <executions>\n          <execution>\n            <id>attach-javadoc</id>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n        <version>3.4.0</version>\n        <configuration>\n          <excludes>\n            <exclude>org/jsoup/examples/**</exclude>\n          </excludes>\n        </configuration>\n        <executions>\n          <execution>\n            <id>attach-sources</id>\n            <goals>\n              <goal>jar-no-fork</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <version>3.5.0</version>\n        <configuration>\n          <archive>\n            <manifest>\n              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n            </manifest>\n            <manifestEntries>\n              <Multi-Release>true</Multi-Release>\n            </manifestEntries>\n            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>\n          </archive>\n        </configuration>\n        <executions>\n          <execution>\n            <id>default-jar</id>\n            <phase>package</phase>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n            <configuration>\n              <excludes>\n                <exclude>org/jsoup/examples/**</exclude>\n              </excludes>\n            </configuration>\n          </execution>\n          <execution>\n            <id>jar-examples</id>\n            <phase>package</phase>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n            <configuration>\n              <classifier>examples</classifier>\n              <includes>\n                <include>org/jsoup/**</include>\n                <include>org/jsoup/examples/**</include>\n              </includes>\n              <archive>\n                <manifest>\n                  <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                  <mainClass>org.jsoup.examples.HtmlToPlainText</mainClass>\n                </manifest>\n              </archive>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.felix</groupId>\n        <artifactId>maven-bundle-plugin</artifactId>\n        <version>5.1.9</version>\n        <executions>\n          <execution>\n            <id>bundle-manifest</id>\n            <phase>process-classes</phase>\n            <goals>\n              <goal>manifest</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <instructions>\n            <Bundle-DocURL>https://jsoup.org/</Bundle-DocURL>\n            <Export-Package>org.jsoup.*</Export-Package>\n            <Import-Package>!org.jsoup.*,org.jspecify.annotations;version=!;resolution:=optional,*</Import-Package>\n          </instructions>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-resources-plugin</artifactId>\n        <version>3.4.0</version>\n      </plugin>\n      <plugin>\n        <artifactId>maven-release-plugin</artifactId>\n        <version>3.3.1</version>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <version>3.5.5</version>\n        <configuration>\n          <!-- smaller stack to find stack overflows. Was 256, but Zulu on MacOS ARM needs >= 640 -->\n          <argLine>-Xss640k</argLine>\n        </configuration>\n      </plugin>\n      <plugin>\n        <artifactId>maven-failsafe-plugin</artifactId>\n        <version>3.5.5</version>\n        <executions>\n          <execution>\n            <goals>\n              <goal>integration-test</goal>\n              <goal>verify</goal>\n            </goals>\n          </execution>\n        </executions>\n        <configuration>\n          <parallel>methods</parallel>\n          <threadCount>8</threadCount>\n        </configuration>\n      </plugin>\n      <plugin>\n        <!-- API version compat check - https://siom79.github.io/japicmp/ -->\n        <groupId>com.github.siom79.japicmp</groupId>\n        <artifactId>japicmp-maven-plugin</artifactId>\n        <version>0.25.4</version>\n        <configuration>\n          <!-- hard code previous version; can't detect when running stateless on build server -->\n          <oldVersion>\n            <dependency>\n              <groupId>org.jsoup</groupId>\n              <artifactId>jsoup</artifactId>\n              <version>1.21.2</version>\n              <type>jar</type>\n            </dependency>\n          </oldVersion>\n          <parameter>\n            <!-- jsoup policy is ok to remove deprecated methods on minor but not builds. will need to temp remove on bump to 1.15.1 and manually validate -->\n            <onlyModified>false</onlyModified>\n            <breakBuildOnBinaryIncompatibleModifications>true</breakBuildOnBinaryIncompatibleModifications>\n            <breakBuildOnSourceIncompatibleModifications>true</breakBuildOnSourceIncompatibleModifications>\n            <excludes />\n            <overrideCompatibilityChangeParameters>\n              <!-- allows new default and move to default methods. compatible as long as existing binaries aren't making calls via reflection. if so, they need to catch errors anyway. -->\n              <overrideCompatibilityChangeParameter>\n                <compatibilityChange>METHOD_NEW_DEFAULT</compatibilityChange>\n                <binaryCompatible>true</binaryCompatible>\n                <sourceCompatible>true</sourceCompatible>\n              </overrideCompatibilityChangeParameter>\n              <overrideCompatibilityChangeParameter>\n                <compatibilityChange>METHOD_ABSTRACT_NOW_DEFAULT</compatibilityChange>\n                <binaryCompatible>true</binaryCompatible>\n                <sourceCompatible>true</sourceCompatible>\n              </overrideCompatibilityChangeParameter>\n            </overrideCompatibilityChangeParameters>\n          </parameter>\n        </configuration>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>cmp</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.sonatype.central</groupId>\n        <artifactId>central-publishing-maven-plugin</artifactId>\n        <version>0.10.0</version>\n        <extensions>true</extensions>\n        <configuration>\n          <publishingServerId>central</publishingServerId>\n        </configuration>\n      </plugin>\n    </plugins>\n    <resources>\n      <resource>\n        <directory>src/main/resources</directory>\n        <filtering>false</filtering>\n      </resource>\n      <resource>\n        <directory>./</directory>\n        <targetPath>META-INF/jsoup/</targetPath>\n        <filtering>false</filtering>\n        <includes>\n          <include>LICENSE</include>\n        </includes>\n      </resource>\n    </resources>\n  </build>\n\n  <profiles>\n    <!-- Profile for Java 8 -->\n    <profile>\n      <id>java-8</id>\n      <activation>\n        <jdk>1.8</jdk>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-compiler-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>compile</id>\n                <phase>compile</phase>\n                <goals>\n                  <goal>compile</goal>\n                  <goal>testCompile</goal>\n                </goals>\n                <configuration>\n                  <source>1.8</source>\n                  <target>1.8</target>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n\n    <!-- Compiles the multi-release jar when executed on JDK11+ -->\n    <profile>\n      <id>compile-multi-release</id>\n      <activation>\n        <jdk>[11,2000)</jdk>\n      </activation>\n      <build>\n        <plugins>\n          <!-- Sets up test/java11 -->\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>build-helper-maven-plugin</artifactId>\n            <version>3.6.1</version>\n            <executions>\n              <execution>\n                <id>add-java11-test-source</id>\n                <phase>generate-test-sources</phase>\n                <goals>\n                  <goal>add-test-source</goal>\n                </goals>\n                <configuration>\n                  <sources>\n                    <source>${project.basedir}/src/test/java11</source>\n                  </sources>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-compiler-plugin</artifactId>\n            <executions>\n\n              <execution>\n                <id>compile-java-8</id>\n                <phase>compile</phase>\n                <goals>\n                  <goal>compile</goal>\n                </goals>\n                <configuration>\n                  <release>8</release>\n                </configuration>\n              </execution>\n\n              <execution>\n                <id>testCompile-java-11</id>\n                <phase>test-compile</phase>\n                <goals>\n                  <goal>testCompile</goal>\n                </goals>\n                <configuration>\n                  <useModulePath>false</useModulePath>\n                  <release>11</release>\n                </configuration>\n              </execution>\n\n              <execution>\n                <id>compile-java-11</id>\n                <phase>compile</phase>\n                <goals>\n                  <goal>compile</goal>\n                </goals>\n                <configuration>\n                  <release>11</release>\n                  <compileSourceRoots>\n                    <compileSourceRoot>${project.basedir}/src/main/java11</compileSourceRoot>\n                  </compileSourceRoots>\n                  <multiReleaseOutput>true</multiReleaseOutput>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n\n          <!-- Add the Java 11-specific test directory to the test runtime classpath -->\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-surefire-plugin</artifactId>\n            <configuration>\n              <additionalClasspathElements>\n                <additionalClasspathElement>${project.build.outputDirectory}/META-INF/versions/11</additionalClasspathElement>\n              </additionalClasspathElements>\n              <useModulePath>false</useModulePath>  <!-- tests use classpath -->\n            </configuration>\n          </plugin>\n\n        </plugins>\n      </build>\n    </profile>\n\n    <profile>\n      <id>release-sign-artifacts</id>\n      <activation>\n        <property>\n          <name>performRelease</name>\n          <value>true</value>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>sign-artifacts</id>\n                <phase>package</phase>\n                <goals>\n                  <goal>sign</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>failsafe</id>\n      <build>\n        <plugins>\n          <plugin>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <version>3.5.5</version>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>integration-test</goal>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n\n  <dependencies>\n\n    <!-- junit -->\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter</artifactId>\n      <version>5.14.3</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <!-- gson, to fetch entities from w3.org -->\n      <groupId>com.google.code.gson</groupId>\n      <artifactId>gson</artifactId>\n      <version>2.13.2</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <!-- jetty for webserver integration tests. 9.x is last with Java7 support -->\n      <groupId>org.eclipse.jetty</groupId>\n      <artifactId>jetty-server</artifactId>\n      <version>${jetty.version}</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <!-- jetty for webserver integration tests -->\n      <groupId>org.eclipse.jetty</groupId>\n      <artifactId>jetty-servlet</artifactId>\n      <version>${jetty.version}</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <!-- jetty proxy, for integration tests -->\n      <groupId>org.eclipse.jetty</groupId>\n      <artifactId>jetty-proxy</artifactId>\n      <version>${jetty.version}</version>\n      <scope>test</scope>\n    </dependency>\n\n    <dependency>\n      <!-- org.jspecify.annotations.nonnull, with Apache 2 license. Build time only. -->\n      <groupId>org.jspecify</groupId>\n      <artifactId>jspecify</artifactId>\n      <version>1.0.0</version>\n      <scope>provided</scope>\n    </dependency>\n\n    <dependency>\n      <!-- re2j; linear time regex, with 3-clause BSD license -->\n      <groupId>com.google.re2j</groupId>\n      <artifactId>re2j</artifactId>\n      <version>1.8</version>\n      <optional>true</optional>\n      <scope>compile</scope>\n    </dependency>\n  </dependencies>\n\n  <dependencyManagement>\n    <dependencies>\n    </dependencies>\n  </dependencyManagement>\n\n  <developers>\n    <developer>\n      <id>jhy</id>\n      <name>Jonathan Hedley</name>\n      <email>jonathan@hedley.net</email>\n      <roles>\n        <role>Lead Developer</role>\n      </roles>\n      <timezone>+11</timezone>\n    </developer>\n  </developers>\n\n</project>\n"
  },
  {
    "path": "src/main/java/org/jsoup/Connection.java",
    "content": "package org.jsoup;\n\nimport org.jsoup.helper.RequestAuthenticator;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.jspecify.annotations.Nullable;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UncheckedIOException;\nimport java.net.Authenticator;\nimport java.net.CookieStore;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n The Connection interface is a convenient HTTP client and session object to fetch content from the web, and parse them\n into Documents.\n <p>To start a new session, use either {@link org.jsoup.Jsoup#newSession()} or {@link org.jsoup.Jsoup#connect(String)}.\n Connections contain {@link Connection.Request} and {@link Connection.Response} objects (once executed). Configuration\n settings (URL, timeout, useragent, etc) set on a session will be applied by default to each subsequent request.</p>\n <p>To start a new request from the session, use {@link #newRequest()}.</p>\n <p>Cookies are stored in memory for the duration of the session. For that reason, do not use one single session for all\n requests in a long-lived application, or you are likely to run out of memory, unless care is taken to clean up the\n cookie store. The cookie store for the session is available via {@link #cookieStore()}. You may provide your own\n implementation via {@link #cookieStore(java.net.CookieStore)} before making requests.</p>\n <p>Request configuration can be made using either the shortcut methods in Connection (e.g. {@link #userAgent(String)}),\n or by methods in the {@link Connection.Request} object directly. All request configuration must be made before the request is\n executed. When used as an ongoing session, initialize all defaults prior to making multi-threaded {@link\n#newRequest()}s.</p>\n <p>Note that the term \"Connection\" used here does not mean that a long-lived connection is held against a server for\n the lifetime of the Connection object. A socket connection is only made at the point of request execution ({@link\n#execute()}, {@link #get()}, or {@link #post()}), and the server's response consumed.</p>\n <p>For multi-threaded implementations, it is important to use a {@link #newRequest()} for each request. The session may\n be shared across concurrent threads, but a not a specific request.</p>\n <p><b>HTTP/2</b> support: On JVM 11 and above, requests use {@link java.net.http.HttpClient}, which supports\n HTTP/2. To use the legacy {@link java.net.HttpURLConnection} instead, set\n <code>System.setProperty(\"jsoup.useHttpClient\", \"false\")</code>.</p>\n */\n@SuppressWarnings(\"unused\")\npublic interface Connection {\n\n    /**\n     * GET and POST http methods.\n     */\n    enum Method {\n        GET(false),\n        POST(true),\n        PUT(true),\n        DELETE(true),\n        /**\n         Note that unfortunately, PATCH is not supported in many JDKs.\n         */\n        PATCH(true),\n        HEAD(false),\n        OPTIONS(false),\n        TRACE(false);\n\n        private final boolean hasBody;\n\n        Method(boolean hasBody) {\n            this.hasBody = hasBody;\n        }\n\n        /**\n         * Check if this HTTP method has/needs a request body\n         * @return if body needed\n         */\n        public final boolean hasBody() {\n            return hasBody;\n        }\n    }\n\n    /**\n     Creates a new request, using this Connection as the session-state and to initialize the connection settings (which\n     may then be independently changed on the returned {@link Connection.Request} object).\n     @return a new Connection object, with a shared Cookie Store and initialized settings from this Connection and Request\n     @since 1.14.1\n     */\n    Connection newRequest();\n\n    /**\n     Creates a new request, using this Connection as the session-state and to initialize the connection settings (which\n     may then be independently changed on the returned {@link Connection.Request} object).\n     @return a new Connection object, with a shared Cookie Store and initialized settings from this Connection and Request\n     @param url URL for the new request\n     @since 1.17.1\n     */\n    default Connection newRequest(String url) {\n        return newRequest().url(url);\n    }\n\n    /**\n     Creates a new request, using this Connection as the session-state and to initialize the connection settings (which\n     may then be independently changed on the returned {@link Connection.Request} object).\n     @return a new Connection object, with a shared Cookie Store and initialized settings from this Connection and Request\n     @param url URL for the new request\n     @since 1.17.1\n     */\n    default Connection newRequest(URL url) {\n        return newRequest().url(url);\n    }\n\n    /**\n     * Set the request URL to fetch. The protocol must be HTTP or HTTPS.\n     * @param url URL to connect to\n     * @return this Connection, for chaining\n     */\n    Connection url(URL url);\n\n    /**\n     * Set the request URL to fetch. The protocol must be HTTP or HTTPS.\n     * @param url URL to connect to\n     * @return this Connection, for chaining\n     */\n    Connection url(String url);\n\n    /**\n     * Set the proxy to use for this request. Set to <code>null</code> to disable a previously set proxy.\n     * @param proxy proxy to use\n     * @return this Connection, for chaining\n     */\n    Connection proxy(@Nullable Proxy proxy);\n\n    /**\n     * Set the HTTP proxy to use for this request.\n     * @param host the proxy hostname\n     * @param port the proxy port\n     * @return this Connection, for chaining\n     */\n    Connection proxy(String host, int port);\n\n    /**\n     * Set the request user-agent header.\n     * @param userAgent user-agent to use\n     * @return this Connection, for chaining\n     * @see org.jsoup.helper.HttpConnection#DEFAULT_UA\n     */\n    Connection userAgent(String userAgent);\n\n    /**\n     Set the total maximum request duration. If a timeout occurs, an {@link java.net.SocketTimeoutException} will be\n     thrown.\n     <p>The default timeout is <b>30 seconds</b> (30,000 millis). A timeout of zero is treated as an infinite timeout.</p>\n     <p>This timeout specifies the combined maximum duration of the connection time and the time to read\n     the full response.</p>\n     <p>Implementation note: when this <code>Connection</code> is backed by <code>HttpURLConnection</code> (rather than <code>HttpClient</code>, as used in JVM 11+), this timeout is implemented by setting both the socket connect and read timeouts to half of the specified value.</p>\n\n     @param millis number of milliseconds (thousandths of a second) before timing out connects or reads.\n     @return this Connection, for chaining\n     @see #maxBodySize(int)\n     */\n    Connection timeout(int millis);\n\n    /**\n     * Set the maximum bytes to read from the (uncompressed) connection into the body, before the connection is closed,\n     * and the input truncated (i.e. the body content will be trimmed). <b>The default maximum is 2MB</b>. A max size of\n     * <code>0</code> is treated as an infinite amount (bounded only by your patience and the memory available on your\n     * machine).\n     *\n     * @param bytes number of bytes to read from the input before truncating\n     * @return this Connection, for chaining\n     */\n    Connection maxBodySize(int bytes);\n\n    /**\n     * Set the request referrer (aka \"referer\") header.\n     * @param referrer referrer to use\n     * @return this Connection, for chaining\n     */\n    Connection referrer(String referrer);\n\n    /**\n     * Configures the connection to (not) follow server redirects. By default, this is <b>true</b>.\n     * @param followRedirects true if server redirects should be followed.\n     * @return this Connection, for chaining\n     */\n    Connection followRedirects(boolean followRedirects);\n\n    /**\n     * Set the request method to use, GET or POST. Default is GET.\n     * @param method HTTP request method\n     * @return this Connection, for chaining\n     */\n    Connection method(Method method);\n\n    /**\n     * Configures the connection to not throw exceptions when an HTTP error occurs. (4xx - 5xx, e.g. 404 or 500). By\n     * default, this is <b>false</b>; an IOException is thrown if an error is encountered. If set to <b>true</b>, the\n     * response is populated with the error body, and the status message will reflect the error.\n     * @param ignoreHttpErrors - false (default) if HTTP errors should be ignored.\n     * @return this Connection, for chaining\n     */\n    Connection ignoreHttpErrors(boolean ignoreHttpErrors);\n\n    /**\n     * Ignore the document's Content-Type when parsing the response. By default, this is <b>false</b>, an unrecognised\n     * content-type will cause an IOException to be thrown. (This is to prevent producing garbage by attempting to parse\n     * a JPEG binary image, for example.) Set to true to force a parse attempt regardless of content type.\n     * @param ignoreContentType set to true if you would like the content type ignored on parsing the response into a\n     * Document.\n     * @return this Connection, for chaining\n     */\n    Connection ignoreContentType(boolean ignoreContentType);\n\n    /**\n     Set a custom SSL socket factory for HTTPS connections.\n     <p>Note: if set, the legacy <code>HttpURLConnection</code> will be used instead of the JVM's\n     <code>HttpClient</code>.</p>\n\n     @param sslSocketFactory SSL socket factory\n     @return this Connection, for chaining\n     @see #sslContext(SSLContext)\n     @deprecated use {@link #sslContext(SSLContext)} instead; will be removed in jsoup 1.24.1.\n     */\n    @Deprecated\n    Connection sslSocketFactory(SSLSocketFactory sslSocketFactory);\n\n    /**\n     Set a custom SSL context for HTTPS connections.\n     <p>Note: when using the legacy <code>HttpURLConnection</code>, only the <code>SSLSocketFactory</code> from the\n     context will be used.</p>\n\n     @param sslContext SSL context\n     @return this Connection, for chaining\n     @since 1.21.2\n     */\n    default Connection sslContext(SSLContext sslContext) {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Add a request data parameter. Request parameters are sent in the request query string for GETs, and in the\n     * request body for POSTs. A request may have multiple values of the same name.\n     * @param key data key\n     * @param value data value\n     * @return this Connection, for chaining\n     */\n    Connection data(String key, String value);\n\n    /**\n     * Add an input stream as a request data parameter. For GETs, has no effect, but for POSTS this will upload the\n     * input stream.\n     * <p>Use the {@link #data(String, String, InputStream, String)} method to set the uploaded file's mimetype.</p>\n     * @param key data key (form item name)\n     * @param filename the name of the file to present to the remove server. Typically just the name, not path,\n     * component.\n     * @param inputStream the input stream to upload, that you probably obtained from a {@link java.io.FileInputStream}.\n     * You must close the InputStream in a {@code finally} block.\n     * @return this Connection, for chaining\n     * @see #data(String, String, InputStream, String)\n     */\n    Connection data(String key, String filename, InputStream inputStream);\n\n    /**\n     * Add an input stream as a request data parameter. For GETs, has no effect, but for POSTS this will upload the\n     * input stream.\n     * @param key data key (form item name)\n     * @param filename the name of the file to present to the remove server. Typically just the name, not path,\n     * component.\n     * @param inputStream the input stream to upload, that you probably obtained from a {@link java.io.FileInputStream}.\n     * @param contentType the Content Type (aka mimetype) to specify for this file.\n     * You must close the InputStream in a {@code finally} block.\n     * @return this Connection, for chaining\n     */\n    Connection data(String key, String filename, InputStream inputStream, String contentType);\n\n    /**\n     * Adds all of the supplied data to the request data parameters\n     * @param data collection of data parameters\n     * @return this Connection, for chaining\n     */\n    Connection data(Collection<KeyVal> data);\n\n    /**\n     * Adds all of the supplied data to the request data parameters\n     * @param data map of data parameters\n     * @return this Connection, for chaining\n     */\n    Connection data(Map<String, String> data);\n\n    /**\n     Add one or more request {@code key, val} data parameter pairs.\n     <p>Multiple parameters may be set at once, e.g.:\n     <code>.data(\"name\", \"jsoup\", \"language\", \"Java\", \"language\", \"English\");</code> creates a query string like:\n     <code>{@literal ?name=jsoup&language=Java&language=English}</code></p>\n     <p>For GET requests, data parameters will be sent on the request query string. For POST (and other methods that\n     contain a body), they will be sent as body form parameters, unless the body is explicitly set by\n     {@link #requestBody(String)}, in which case they will be query string parameters.</p>\n\n     @param keyvals a set of key value pairs.\n     @return this Connection, for chaining\n     */\n    Connection data(String... keyvals);\n\n    /**\n     * Get the data KeyVal for this key, if any\n     * @param key the data key\n     * @return null if not set\n     */\n    @Nullable KeyVal data(String key);\n\n    /**\n     * Set a POST (or PUT) request body. Useful when a server expects a plain request body (such as JSON), and not a set\n     * of URL encoded form key/value pairs. E.g.:\n     * <code><pre>Jsoup.connect(url)\n     * .requestBody(json)\n     * .header(\"Content-Type\", \"application/json\")\n     * .post();</pre></code>\n     * If any data key/vals are supplied, they will be sent as URL query params.\n     * @see #requestBodyStream(InputStream)\n     * @return this Request, for chaining\n     */\n    Connection requestBody(String body);\n\n    /**\n     Set the request body. Useful for posting data such as byte arrays or files, and the server expects a single request\n     body (and not a multipart upload). E.g.:\n     <code><pre> Jsoup.connect(url)\n     .requestBody(new ByteArrayInputStream(bytes))\n     .header(\"Content-Type\", \"application/octet-stream\")\n     .post();\n     </pre></code>\n     <p>Or, use a FileInputStream to data from disk.</p>\n     <p>You should close the stream in a finally block.</p>\n\n     @param stream the input stream to send.\n     @return this Request, for chaining\n     @see #requestBody(String)\n     @since 1.20.1\n     */\n    default Connection requestBodyStream(InputStream stream) {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Set a request header. Replaces any existing header with the same case-insensitive name.\n     * @param name header name\n     * @param value header value\n     * @return this Connection, for chaining\n     * @see org.jsoup.Connection.Request#header(String, String)\n     * @see org.jsoup.Connection.Request#headers()\n     */\n    Connection header(String name, String value);\n\n    /**\n     * Sets each of the supplied headers on the request. Existing headers with the same case-insensitive name will be\n     * replaced with the new value.\n     * @param headers map of headers name {@literal ->} value pairs\n     * @return this Connection, for chaining\n     * @see org.jsoup.Connection.Request#headers()\n     */\n    Connection headers(Map<String,String> headers);\n\n    /**\n     * Set a cookie to be sent in the request.\n     * @param name name of cookie\n     * @param value value of cookie\n     * @return this Connection, for chaining\n     */\n    Connection cookie(String name, String value);\n\n    /**\n     * Adds each of the supplied cookies to the request.\n     * @param cookies map of cookie name {@literal ->} value pairs\n     * @return this Connection, for chaining\n     */\n    Connection cookies(Map<String, String> cookies);\n\n    /**\n     Provide a custom or pre-filled CookieStore to be used on requests made by this Connection.\n     @param cookieStore a cookie store to use for subsequent requests\n     @return this Connection, for chaining\n     @since 1.14.1\n     */\n    Connection cookieStore(CookieStore cookieStore);\n\n    /**\n     Get the cookie store used by this Connection.\n     @return the cookie store\n     @since 1.14.1\n     */\n    CookieStore cookieStore();\n\n    /**\n     * Provide a specific parser to use when parsing the response to a Document. If not set, jsoup defaults to the\n     * {@link Parser#htmlParser() HTML parser}, unless the response content-type is XML, in which case the\n     * {@link Parser#xmlParser() XML parser} is used.\n     * @param parser alternate parser\n     * @return this Connection, for chaining\n     */\n    Connection parser(Parser parser);\n\n    /**\n     * Set the character-set used to encode the request body. Defaults to {@code UTF-8}.\n     * @param charset character set to encode the request body\n     * @return this Connection, for chaining\n     */\n    Connection postDataCharset(String charset);\n\n    /**\n     Set the authenticator to use for this connection, enabling requests to URLs, and via proxies, that require\n     authentication credentials.\n     <p>The authentication scheme used is automatically detected during the request execution.\n     Supported schemes (subject to the platform) are {@code basic}, {@code digest}, {@code NTLM},\n     and {@code Kerberos}.</p>\n\n     <p>To use, supply a {@link RequestAuthenticator} function that:\n     <ol>\n     <li>validates the URL that is requesting authentication, and</li>\n     <li>returns the appropriate credentials (username and password)</li>\n     </ol>\n     </p>\n\n     <p>For example, to authenticate both to a proxy and a downstream web server:\n     <code><pre>\n     Connection session = Jsoup.newSession()\n         .proxy(\"proxy.example.com\", 8080)\n         .auth(auth -&gt; {\n             if (auth.isServer()) { // provide credentials for the request url\n                 Validate.isTrue(auth.url().getHost().equals(\"example.com\"));\n                 // check that we're sending credentials were we expect, and not redirected out\n                 return auth.credentials(\"username\", \"password\");\n             } else { // auth.isProxy()\n                 return auth.credentials(\"proxy-user\", \"proxy-password\");\n             }\n         });\n\n     Connection.Response response = session.newRequest(\"https://example.com/adminzone/\").execute();\n     </pre></code>\n     </p>\n\n     <p>The system may cache the authentication and use it for subsequent requests to the same resource.</p>\n\n     <p><b>Implementation notes</b></p>\n     <p>For compatibility, on a Java 8 platform, authentication is set up via the system-wide default\n     {@link java.net.Authenticator#setDefault(Authenticator)} method via a ThreadLocal delegator. Whilst the\n     authenticator used is request specific and thread-safe, if you have other calls to {@code setDefault}, they will be\n     incompatible with this implementation.</p>\n     <p>On Java 9 and above, the preceding note does not apply; authenticators are directly set on the request. </p>\n     <p>If you are attempting to authenticate to a proxy that uses the {@code basic} scheme and will be fetching HTTPS\n     URLs, you need to configure your Java platform to enable that, by setting the\n     {@code jdk.http.auth.tunneling.disabledSchemes} system property to {@code \"\"}.\n     This must be executed prior to any authorization attempts. E.g.:\n     <code><pre>\n     static {\n        System.setProperty(\"jdk.http.auth.tunneling.disabledSchemes\", \"\");\n        // removes Basic, which is otherwise excluded from auth for CONNECT tunnels\n     }</pre></code>\n     </p>\n     * @param authenticator the authenticator to use in this connection\n     * @return this Connection, for chaining\n     * @since 1.17.1\n     */\n    default Connection auth(@Nullable RequestAuthenticator authenticator) {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Execute the request as a GET, and parse the result.\n     * @return parsed Document\n     * @throws java.net.MalformedURLException if the request URL is not an HTTP or HTTPS URL, or is otherwise malformed\n     * @throws HttpStatusException if the response is not OK and HTTP response errors are not ignored\n     * @throws UnsupportedMimeTypeException if the response mime type is not supported and those errors are not ignored\n     * @throws java.net.SocketTimeoutException if the connection times out\n     * @throws IOException on error\n     */\n    Document get() throws IOException;\n\n    /**\n     * Execute the request as a POST, and parse the result.\n     * @return parsed Document\n     * @throws java.net.MalformedURLException if the request URL is not a HTTP or HTTPS URL, or is otherwise malformed\n     * @throws HttpStatusException if the response is not OK and HTTP response errors are not ignored\n     * @throws UnsupportedMimeTypeException if the response mime type is not supported and those errors are not ignored\n     * @throws java.net.SocketTimeoutException if the connection times out\n     * @throws IOException on error\n     */\n    Document post() throws IOException;\n\n    /**\n     * Execute the request.\n     * @return the executed {@link Response}\n     * @throws java.net.MalformedURLException if the request URL is not a HTTP or HTTPS URL, or is otherwise malformed\n     * @throws HttpStatusException if the response is not OK and HTTP response errors are not ignored\n     * @throws UnsupportedMimeTypeException if the response mime type is not supported and those errors are not ignored\n     * @throws java.net.SocketTimeoutException if the connection times out\n     * @throws IOException on error\n     */\n    Response execute() throws IOException;\n\n    /**\n     * Get the request object associated with this connection\n     * @return request\n     */\n    Request request();\n\n    /**\n     * Set the connection's request\n     * @param request new request object\n     * @return this Connection, for chaining\n     */\n    Connection request(Request request);\n\n    /**\n     * Get the response, once the request has been executed.\n     * @return response\n     * @throws IllegalArgumentException if called before the response has been executed.\n     */\n    Response response();\n\n    /**\n     * Set the connection's response\n     * @param response new response\n     * @return this Connection, for chaining\n     */\n    Connection response(Response response);\n\n    /**\n     Set the response progress handler, which will be called periodically as the response body is downloaded. Since\n     documents are parsed as they are downloaded, this is also a good proxy for the parse progress.\n     <p>The Response object is supplied as the progress context, and may be read from to obtain headers etc.</p>\n     @param handler the progress handler\n     @return this Connection, for chaining\n     @since 1.18.1\n     */\n    default Connection onResponseProgress(Progress<Response> handler) {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * Common methods for Requests and Responses\n     * @param <T> Type of Base, either Request or Response\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    interface Base<T extends Base<T>> {\n        /**\n         * Get the URL of this Request or Response. For redirected responses, this will be the final destination URL.\n         * @return URL\n         * @throws IllegalArgumentException if called on a Request that was created without a URL.\n         */\n        URL url();\n\n        /**\n         * Set the URL\n         * @param url new URL\n         * @return this, for chaining\n         */\n        T url(URL url);\n\n        /**\n         * Get the request method, which defaults to <code>GET</code>\n         * @return method\n         */\n        Method method();\n\n        /**\n         * Set the request method\n         * @param method new method\n         * @return this, for chaining\n         */\n        T method(Method method);\n\n        /**\n         * Get the value of a header. If there is more than one header value with the same name, the headers are returned\n         * comma separated, per <a href=\"https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\">rfc2616-sec4</a>.\n         * <p>\n         * Header names are case-insensitive.\n         * </p>\n         * @param name name of header (case-insensitive)\n         * @return value of header, or null if not set.\n         * @see #hasHeader(String)\n         * @see #cookie(String)\n         */\n        @Nullable String header(String name);\n\n        /**\n         * Get the values of a header.\n         * @param name header name, case-insensitive.\n         * @return a list of values for this header, or an empty list if not set.\n         */\n        List<String> headers(String name);\n\n        /**\n         * Set a header. This method will overwrite any existing header with the same case-insensitive name. If there\n         * is more than one value for this header, this method will update the first matching header.\n         * <p>For compatibility, if the content of the header includes text that cannot be represented by ISO-8859-1,\n         * then it should be encoded first per <a href=\"https://www.ietf.org/rfc/rfc2047.txt\">RFC 2047</a>.</p>\n         * @param name Name of header\n         * @param value Value of header\n         * @return this, for chaining\n         * @see #addHeader(String, String)\n         */\n        T header(String name, String value);\n\n        /**\n         * Add a header. The header will be added regardless of whether a header with the same name already exists.\n         * <p>For compatibility, if the content of the header includes text that cannot be represented by ISO-8859-1,\n         * then it should be encoded first per <a href=\"https://www.ietf.org/rfc/rfc2047.txt\">RFC 2047</a>.</p>\n         * @param name Name of new header\n         * @param value Value of new header\n         * @return this, for chaining\n         */\n        T addHeader(String name, String value);\n\n        /**\n         * Check if a header is present\n         * @param name name of header (case-insensitive)\n         * @return if the header is present in this request/response\n         */\n        boolean hasHeader(String name);\n\n        /**\n         * Check if a header is present, with the given value\n         * @param name header name (case-insensitive)\n         * @param value value (case-insensitive)\n         * @return if the header and value pair are set in this req/res\n         */\n        boolean hasHeaderWithValue(String name, String value);\n\n        /**\n         * Remove headers by name. If there is more than one header with this name, they will all be removed.\n         * @param name name of header to remove (case-insensitive)\n         * @return this, for chaining\n         */\n        T removeHeader(String name);\n\n        /**\n         * Retrieve all of the request/response header names and corresponding values as a map. For headers with multiple\n         * values, only the first header is returned.\n         * <p>Note that this is a view of the headers only, and changes made to this map will not be reflected in the\n         * request/response object.</p>\n         * @return headers\n         * @see #multiHeaders()\n\n         */\n        Map<String, String> headers();\n\n        /**\n         * Retreive all of the headers, keyed by the header name, and with a list of values per header.\n         * @return a list of multiple values per header.\n         */\n        Map<String, List<String>> multiHeaders();\n\n        /**\n         * Get a cookie value by name from this request/response.\n         * @param name name of cookie to retrieve.\n         * @return value of cookie, or null if not set\n         */\n        @Nullable String cookie(String name);\n\n        /**\n         * Set a cookie in this request/response.\n         * @param name name of cookie\n         * @param value value of cookie\n         * @return this, for chaining\n         */\n        T cookie(String name, String value);\n\n        /**\n         * Check if a cookie is present\n         * @param name name of cookie\n         * @return if the cookie is present in this request/response\n         */\n        boolean hasCookie(String name);\n\n        /**\n         * Remove a cookie by name\n         * @param name name of cookie to remove\n         * @return this, for chaining\n         */\n        T removeCookie(String name);\n\n        /**\n         Retrieve the request/response cookies as a map. For response cookies, if duplicate cookie names were sent, the\n         last one set will be the one included. For session management, rather than using these response cookies, prefer\n         to use {@link Jsoup#newSession()} and related methods.\n\n         @return simple cookie map\n         @see #cookieStore()\n         */\n        Map<String, String> cookies();\n    }\n\n    /**\n     * Represents a HTTP request.\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    interface Request extends Base<Request> {\n        /**\n         * Get the proxy used for this request.\n         * @return the proxy; <code>null</code> if not enabled.\n         */\n        @Nullable Proxy proxy();\n\n        /**\n         * Update the proxy for this request.\n         * @param proxy the proxy ot use; <code>null</code> to disable.\n         * @return this Request, for chaining\n         */\n        Request proxy(@Nullable Proxy proxy);\n\n        /**\n         * Set the HTTP proxy to use for this request.\n         * @param host the proxy hostname\n         * @param port the proxy port\n         * @return this Connection, for chaining\n         */\n        Request proxy(String host, int port);\n\n        /**\n         * Get the request timeout, in milliseconds.\n         * @return the timeout in milliseconds.\n         */\n        int timeout();\n\n        /**\n         * Update the request timeout.\n         * @param millis timeout, in milliseconds\n         * @return this Request, for chaining\n         */\n        Request timeout(int millis);\n\n        /**\n         * Get the maximum body size, in bytes.\n         * @return the maximum body size, in bytes.\n         */\n        int maxBodySize();\n\n        /**\n         * Update the maximum body size, in bytes.\n         * @param bytes maximum body size, in bytes.\n         * @return this Request, for chaining\n         */\n        Request maxBodySize(int bytes);\n\n        /**\n         * Get the current followRedirects configuration.\n         * @return true if followRedirects is enabled.\n         */\n        boolean followRedirects();\n\n        /**\n         * Configures the request to (not) follow server redirects. By default this is <b>true</b>.\n         * @param followRedirects true if server redirects should be followed.\n         * @return this Request, for chaining\n         */\n        Request followRedirects(boolean followRedirects);\n\n        /**\n         * Get the current ignoreHttpErrors configuration.\n         * @return true if errors will be ignored; false (default) if HTTP errors will cause an IOException to be\n         * thrown.\n         */\n        boolean ignoreHttpErrors();\n\n        /**\n         * Configures the request to ignore HTTP errors in the response.\n         * @param ignoreHttpErrors set to true to ignore HTTP errors.\n         * @return this Request, for chaining\n         */\n        Request ignoreHttpErrors(boolean ignoreHttpErrors);\n\n        /**\n         * Get the current ignoreContentType configuration.\n         * @return true if invalid content-types will be ignored; false (default) if they will cause an IOException to\n         * be thrown.\n         */\n        boolean ignoreContentType();\n\n        /**\n         * Configures the request to ignore the Content-Type of the response.\n         * @param ignoreContentType set to true to ignore the content type.\n         * @return this Request, for chaining\n         */\n        Request ignoreContentType(boolean ignoreContentType);\n\n        /**\n         * Get the current custom SSL socket factory, if any.\n         * @return custom SSL socket factory if set, null otherwise\n         */\n        @Nullable SSLSocketFactory sslSocketFactory();\n\n        /**\n         Set a custom SSL socket factory for HTTPS connections.\n         <p>Note: if set, the legacy <code>HttpURLConnection</code> will be used instead of the JVM's\n         <code>HttpClient</code>.</p>\n\n         @param sslSocketFactory SSL socket factory\n         @see #sslContext(SSLContext)\n         @deprecated use {@link #sslContext(SSLContext)} instead; will be removed in jsoup 1.24.1.\n         */\n        @Deprecated\n        void sslSocketFactory(SSLSocketFactory sslSocketFactory);\n\n        /**\n         Get the current custom SSL context, if any.\n\n         @return custom SSL context if set, null otherwise\n         @since 1.21.2\n         */\n        @Nullable\n        default SSLContext sslContext() {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         Set a custom SSL context for HTTPS connections.\n         <p>Note: when using the legacy <code>HttpURLConnection</code>, only the <code>SSLSocketFactory</code> from the\n         context will be used.</p>\n\n         @param sslContext SSL context\n         @return this Request, for chaining\n         @since 1.21.2\n         */\n        default Request sslContext(SSLContext sslContext) {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         * Add a data parameter to the request\n         * @param keyval data to add.\n         * @return this Request, for chaining\n         */\n        Request data(KeyVal keyval);\n\n        /**\n         * Get all of the request's data parameters\n         * @return collection of keyvals\n         */\n        Collection<KeyVal> data();\n\n        /**\n         * Set a POST (or PUT) request body. Useful when a server expects a plain request body, not a set of URL\n         * encoded form key/value pairs. E.g.:\n         * <code><pre>Jsoup.connect(url)\n         * .requestBody(json)\n         * .header(\"Content-Type\", \"application/json\")\n         * .post();</pre></code>\n         * <p>If any data key/vals are supplied, they will be sent as URL query params.</p>\n         * @param body to use as the request body. Set to null to clear a previously set body.\n         * @return this Request, for chaining\n         * @see #requestBodyStream(InputStream)\n         */\n        Request requestBody(@Nullable String body);\n\n        /**\n         * Get the current request body.\n         * @return null if not set.\n         */\n        @Nullable String requestBody();\n\n        /**\n         Set the request body. Useful for posting data such as byte arrays or files, and the server expects a single\n         request body (and not a multipart upload). E.g.:\n         <code><pre> Jsoup.connect(url)\n         .requestBody(new ByteArrayInputStream(bytes))\n         .header(\"Content-Type\", \"application/octet-stream\")\n         .post();\n         </pre></code>\n         <p>Or, use a FileInputStream to data from disk.</p>\n         <p>You should close the stream in a finally block.</p>\n\n         @param stream the input stream to send.\n         @return this Request, for chaining\n         @see #requestBody(String)\n         @since 1.20.1\n         */\n        default Request requestBodyStream(InputStream stream) {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         * Specify the parser to use when parsing the document.\n         * @param parser parser to use.\n         * @return this Request, for chaining\n         */\n        Request parser(Parser parser);\n\n        /**\n         * Get the current parser to use when parsing the document.\n         * @return current Parser\n         */\n        Parser parser();\n\n        /**\n         * Sets the post data character set for x-www-form-urlencoded post data\n         * @param charset character set to encode post data\n         * @return this Request, for chaining\n         */\n        Request postDataCharset(String charset);\n\n        /**\n         * Gets the post data character set for x-www-form-urlencoded post data\n         * @return character set to encode post data\n         */\n        String postDataCharset();\n\n        /**\n         Set the authenticator to use for this request.\n         See {@link Connection#auth(RequestAuthenticator) Connection.auth(authenticator)} for examples and\n         implementation notes.\n         * @param authenticator the authenticator\n         * @return this Request, for chaining.\n         * @since 1.17.1\n         */\n        default Request auth(@Nullable RequestAuthenticator authenticator)  {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         Get the RequestAuthenticator, if any, that will be used on this request.\n         * @return the RequestAuthenticator, or {@code null} if not set\n         * @since 1.17.1\n         */\n        @Nullable\n        default RequestAuthenticator auth() {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    /**\n     * Represents a HTTP response.\n     */\n    interface Response extends Base<Response> {\n\n        /**\n         * Get the status code of the response.\n         * @return status code\n         */\n        int statusCode();\n\n        /**\n         * Get the status message of the response.\n         * @return status message\n         */\n        String statusMessage();\n\n        /**\n         * Get the character set name of the response, derived from the content-type header.\n         * @return character set name if set, <b>null</b> if not\n         */\n        @Nullable String charset();\n\n        /**\n         * Set / override the response character set. When the document body is parsed it will be with this charset.\n         * @param charset to decode body as\n         * @return this Response, for chaining\n         */\n        Response charset(String charset);\n\n        /**\n         * Get the response content type (e.g. \"text/html\");\n         * @return the response content type, or <b>null</b> if one was not set\n         */\n        @Nullable String contentType();\n\n        /**\n         Read and parse the body of the response as a Document. If you intend to parse the same response multiple times,\n         you should {@link #readFully()} first, which will buffer the body into memory.\n\n         @return a parsed Document\n         @throws IOException if an IO exception occurs whilst reading the body.\n         @see #readFully()\n         */\n        Document parse() throws IOException;\n\n        /**\n         Read the response body, and returns it as a plain String.\n\n         @return body\n         @throws IOException if an IO exception occurs whilst reading the body.\n         @since 1.21.1\n         */\n        default String readBody() throws IOException {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         Get the body of the response as a plain String.\n\n         <p>Will throw an UncheckedIOException if the body has not been buffered and an error occurs whilst reading the\n         body; use {@link #readFully()} first to buffer the body and catch any exceptions explicitly. Or more simply,\n         {@link #readBody()}.</p>\n\n         @return body\n         @throws UncheckedIOException if an IO exception occurs whilst reading the body.\n         @see #readBody()\n         @see #readFully()\n         */\n        String body();\n\n        /**\n         Get the body of the response as an array of bytes.\n\n         <p>Will throw an UncheckedIOException if the body has not been buffered and an error occurs whilst reading the\n         body; use {@link #readFully()} first to buffer the body and catch any exceptions explicitly.</p>\n\n         @return body bytes\n         @throws UncheckedIOException if an IO exception occurs whilst reading the body.\n         @see #readFully()\n         */\n        byte[] bodyAsBytes();\n\n        /**\n         Read the body of the response into a local buffer, so that {@link #parse()} may be called repeatedly on the same\n         connection response. Otherwise, once the response is read, its InputStream will have been drained and may not be\n         re-read.\n\n         <p>Subsequent calls methods than consume the body, such as {@link #parse()}, {@link #body()},\n         {@link #bodyAsBytes()}, will not need to read the body again, and will not throw exceptions.</p>\n         <p>Calling {@link #readBody()}} has the same effect.</p>\n\n         @return this response, for chaining\n         @throws IOException if an IO exception occurs during buffering.\n         @since 1.21.1\n         */\n        default Response readFully() throws IOException {\n            throw new UnsupportedOperationException();\n        }\n\n        /**\n         * Read the body of the response into a local buffer, so that {@link #parse()} may be called repeatedly on the\n         * same connection response. Otherwise, once the response is read, its InputStream will have been drained and\n         * may not be re-read.\n         * <p>Calling {@link #body() } or {@link #bodyAsBytes()} has the same effect.</p>\n         * @return this response, for chaining\n         * @throws UncheckedIOException if an IO exception occurs during buffering.\n         * @deprecated use {@link #readFully()} instead (for the checked exception). Will be removed in jsoup 1.24.1.\n         */\n        @Deprecated\n        Response bufferUp();\n\n        /**\n         Get the body of the response as a (buffered) InputStream. You should close the input stream when you're done\n         with it.\n         <p>Other body methods (like readFully, body, parse, etc) will generally not work in conjunction with this method,\n         as it consumes the InputStream.</p>\n         <p>Any configured max size or maximum read timeout applied to the connection will not be applied to this stream,\n         unless {@link #readFully()} is called prior.</p>\n         <p>This method is useful for writing large responses to disk, without buffering them completely into memory\n         first.</p>\n         @return the response body input stream\n         */\n        BufferedInputStream bodyStream();\n\n        /**\n         Returns a {@link StreamParser} that will parse the Response progressively.\n         * @return a StreamParser, prepared to parse this response.\n         * @throws IOException if an IO exception occurs preparing the parser.\n         */\n        default StreamParser streamParser() throws IOException {\n            throw new UnsupportedOperationException();\n        }\n    }\n\n    /**\n     * A Key:Value tuple(+), used for form data.\n     */\n    interface KeyVal {\n\n        /**\n         * Update the key of a keyval\n         * @param key new key\n         * @return this KeyVal, for chaining\n         */\n        KeyVal key(String key);\n\n        /**\n         * Get the key of a keyval\n         * @return the key\n         */\n        String key();\n\n        /**\n         * Update the value of a keyval\n         * @param value the new value\n         * @return this KeyVal, for chaining\n         */\n        KeyVal value(String value);\n\n        /**\n         * Get the value of a keyval\n         * @return the value\n         */\n        String value();\n\n        /**\n         * Add or update an input stream to this keyVal\n         * @param inputStream new input stream\n         * @return this KeyVal, for chaining\n         */\n        KeyVal inputStream(InputStream inputStream);\n\n        /**\n         * Get the input stream associated with this keyval, if any\n         * @return input stream if set, or null\n         */\n        @Nullable InputStream inputStream();\n\n        /**\n         * Does this keyval have an input stream?\n         * @return true if this keyval does indeed have an input stream\n         */\n        boolean hasInputStream();\n\n        /**\n         * Set the Content Type header used in the MIME body (aka mimetype) when uploading files.\n         * Only useful if {@link #inputStream(InputStream)} is set.\n         * <p>Will default to {@code application/octet-stream}.</p>\n         * @param contentType the new content type\n         * @return this KeyVal\n         */\n        KeyVal contentType(String contentType);\n\n        /**\n         * Get the current Content Type, or {@code null} if not set.\n         * @return the current Content Type.\n         */\n        @Nullable String contentType();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/HttpStatusException.java",
    "content": "package org.jsoup;\n\nimport java.io.IOException;\n\n/**\n * Signals that a HTTP request resulted in a not OK HTTP response.\n */\npublic class HttpStatusException extends IOException {\n    private final int statusCode;\n    private final String url;\n\n    public HttpStatusException(String message, int statusCode, String url) {\n        super(message + \". Status=\" + statusCode + \", URL=[\" + url + \"]\");\n        this.statusCode = statusCode;\n        this.url = url;\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/Jsoup.java",
    "content": "package org.jsoup;\n\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.helper.HttpConnection;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.safety.Cleaner;\nimport org.jsoup.safety.Safelist;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.nio.file.Path;\n\nimport static org.jsoup.internal.SharedConstants.DummyUri;\n\n/**\n The core public access point to the jsoup functionality.\n\n @author Jonathan Hedley */\n\npublic class Jsoup {\n    private Jsoup() {}\n\n    /**\n     Parse HTML into a Document. The parser will make a sensible, balanced document tree out of any HTML.\n\n     @param html    HTML to parse\n     @param baseUri The URL where the HTML was retrieved from. Used to resolve relative URLs to absolute URLs, that occur\n     before the HTML declares a {@code <base href>} tag.\n     @return sane HTML\n     */\n    public static Document parse(String html, String baseUri) {\n        return Parser.parse(html, baseUri);\n    }\n\n    /**\n     Parse HTML into a Document, using the provided Parser. You can provide an alternate parser, such as a simple XML\n     (non-HTML) parser.\n\n     @param html    HTML to parse\n     @param baseUri The URL where the HTML was retrieved from. Used to resolve relative URLs to absolute URLs, that occur\n     before the HTML declares a {@code <base href>} tag.\n     @param parser alternate {@link Parser#xmlParser() parser} to use.\n     @return sane HTML\n     */\n    public static Document parse(String html, String baseUri, Parser parser) {\n        return parser.parseInput(html, baseUri);\n    }\n\n    /**\n     Parse HTML into a Document, using the provided Parser. You can provide an alternate parser, such as a simple XML\n     (non-HTML) parser.  As no base URI is specified, absolute URL resolution, if required, relies on the HTML including\n     a {@code <base href>} tag.\n\n     @param html    HTML to parse\n     before the HTML declares a {@code <base href>} tag.\n     @param parser alternate {@link Parser#xmlParser() parser} to use.\n     @return sane HTML\n     */\n    public static Document parse(String html, Parser parser) {\n        return parser.parseInput(html, \"\");\n    }\n\n    /**\n     Parse HTML into a Document. As no base URI is specified, absolute URL resolution, if required, relies on the HTML\n     including a {@code <base href>} tag.\n\n     @param html HTML to parse\n     @return sane HTML\n\n     @see #parse(String, String)\n     */\n    public static Document parse(String html) {\n        return Parser.parse(html, \"\");\n    }\n\n    /**\n     * Creates a new {@link Connection} (session), with the defined request URL. Use to fetch and parse a HTML page.\n     * <p>\n     * Use examples:\n     * <ul>\n     *  <li><code>Document doc = Jsoup.connect(\"http://example.com\").userAgent(\"Mozilla\").data(\"name\", \"jsoup\").get();</code></li>\n     *  <li><code>Document doc = Jsoup.connect(\"http://example.com\").cookie(\"auth\", \"token\").post();</code></li>\n     * </ul>\n     * @param url URL to connect to. The protocol must be {@code http} or {@code https}.\n     * @return the connection. You can add data, cookies, and headers; set the user-agent, referrer, method; and then execute.\n     * @see #newSession()\n     * @see Connection#newRequest()\n     */\n    public static Connection connect(String url) {\n        return HttpConnection.connect(url);\n    }\n\n    /**\n     Creates a new {@link Connection} to use as a session. Connection settings (user-agent, timeouts, URL, etc), and\n     cookies will be maintained for the session. Use examples:\n<pre><code>\nConnection session = Jsoup.newSession()\n     .timeout(20 * 1000)\n     .userAgent(\"FooBar 2000\");\n\nDocument doc1 = session.newRequest()\n     .url(\"https://jsoup.org/\").data(\"ref\", \"example\")\n     .get();\nDocument doc2 = session.newRequest()\n     .url(\"https://en.wikipedia.org/wiki/Main_Page\")\n     .get();\nConnection con3 = session.newRequest();\n</code></pre>\n\n     <p>For multi-threaded requests, it is safe to use this session between threads, but take care to call {@link\n    Connection#newRequest()} per request and not share that instance between threads when executing or parsing.</p>\n\n     @return a connection\n     @since 1.14.1\n     */\n    public static Connection newSession() {\n        return new HttpConnection();\n    }\n\n    /**\n     Parse the contents of a file as HTML.\n\n     @param file          file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     */\n    public static Document parse(File file, @Nullable String charsetName, String baseUri) throws IOException {\n        return DataUtil.load(file, charsetName, baseUri);\n    }\n\n    /**\n     Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs.\n\n     @param file        file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     @see #parse(File, String, String) parse(file, charset, baseUri)\n     */\n    public static Document parse(File file, @Nullable String charsetName) throws IOException {\n        return DataUtil.load(file, charsetName, file.getAbsolutePath());\n    }\n\n    /**\n     Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs.\n     The charset used to read the file will be determined by the byte-order-mark (BOM), or a {@code <meta charset>} tag,\n     or if neither is present, will be {@code UTF-8}.\n\n     <p>This is the equivalent of calling {@link #parse(File, String) parse(file, null)}</p>\n\n     @param file the file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @return sane HTML\n     @throws IOException if the file could not be found or read.\n     @see #parse(File, String, String) parse(file, charset, baseUri)\n     @since 1.15.1\n     */\n    public static Document parse(File file) throws IOException {\n        return DataUtil.load(file, null, file.getAbsolutePath());\n    }\n\n    /**\n     Parse the contents of a file as HTML.\n\n     @param file          file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @param parser alternate {@link Parser#xmlParser() parser} to use.\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     @since 1.14.2\n     */\n    public static Document parse(File file, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return DataUtil.load(file, charsetName, baseUri, parser);\n    }\n\n    /**\n     Parse the contents of a file as HTML.\n\n     @param path          file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     @since 1.18.1\n     */\n    public static Document parse(Path path, @Nullable String charsetName, String baseUri) throws IOException {\n        return DataUtil.load(path, charsetName, baseUri);\n    }\n\n    /**\n     Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs.\n\n     @param path        file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     @see #parse(File, String, String) parse(file, charset, baseUri)\n     @since 1.18.1\n     */\n    public static Document parse(Path path, @Nullable String charsetName) throws IOException {\n        return DataUtil.load(path, charsetName, path.toAbsolutePath().toString());\n    }\n\n    /**\n     Parse the contents of a file as HTML. The location of the file is used as the base URI to qualify relative URLs.\n     The charset used to read the file will be determined by the byte-order-mark (BOM), or a {@code <meta charset>} tag,\n     or if neither is present, will be {@code UTF-8}.\n\n     <p>This is the equivalent of calling {@link #parse(File, String) parse(file, null)}</p>\n\n     @param path the file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @return sane HTML\n     @throws IOException if the file could not be found or read.\n     @see #parse(Path, String, String) parse(file, charset, baseUri)\n     @since 1.18.1\n     */\n    public static Document parse(Path path) throws IOException {\n        return DataUtil.load(path, null, path.toAbsolutePath().toString());\n    }\n\n    /**\n     Parse the contents of a file as HTML.\n\n     @param path          file to load HTML from. Supports gzipped files (ending in .z or .gz).\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @param parser alternate {@link Parser#xmlParser() parser} to use.\n     @return sane HTML\n\n     @throws IOException if the file could not be found, or read, or if the charsetName is invalid.\n     @since 1.18.1\n     */\n    public static Document parse(Path path, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return DataUtil.load(path, charsetName, baseUri, parser);\n    }\n\n     /**\n     Read an input stream, and parse it to a Document.\n\n     @param in          input stream to read. The stream will be closed after reading.\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @return sane HTML\n\n     @throws IOException if the stream could not be read, or if the charsetName is invalid.\n     */\n    public static Document parse(InputStream in, @Nullable String charsetName, String baseUri) throws IOException {\n        return DataUtil.load(in, charsetName, baseUri);\n    }\n\n    /**\n     Read an input stream, and parse it to a Document. You can provide an alternate parser, such as a simple XML\n     (non-HTML) parser.\n\n     @param in          input stream to read. Make sure to close it after parsing.\n     @param charsetName (optional) character set of file contents. Set to {@code null} to determine from {@code http-equiv} meta tag, if\n     present, or fall back to {@code UTF-8} (which is often safe to do).\n     @param baseUri     The URL where the HTML was retrieved from, to resolve relative links against.\n     @param parser alternate {@link Parser#xmlParser() parser} to use.\n     @return sane HTML\n\n     @throws IOException if the stream could not be read, or if the charsetName is invalid.\n     */\n    public static Document parse(InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return DataUtil.load(in, charsetName, baseUri, parser);\n    }\n\n    /**\n     Parse a fragment of HTML, with the assumption that it forms the {@code body} of the HTML.\n\n     @param bodyHtml body HTML fragment\n     @param baseUri  URL to resolve relative URLs against.\n     @return sane HTML document\n\n     @see Document#body()\n     */\n    public static Document parseBodyFragment(String bodyHtml, String baseUri) {\n        return Parser.parseBodyFragment(bodyHtml, baseUri);\n    }\n\n    /**\n     Parse a fragment of HTML, with the assumption that it forms the {@code body} of the HTML.\n\n     @param bodyHtml body HTML fragment\n     @return sane HTML document\n\n     @see Document#body()\n     */\n    public static Document parseBodyFragment(String bodyHtml) {\n        return Parser.parseBodyFragment(bodyHtml, \"\");\n    }\n\n    /**\n     Fetch a URL, and parse it as HTML. Provided for compatibility; in most cases use {@link #connect(String)} instead.\n     <p>\n     The encoding character set is determined by the content-type header or http-equiv meta tag, or falls back to {@code UTF-8}.\n\n     @param url           URL to fetch (with a GET). The protocol must be {@code http} or {@code https}.\n     @param timeoutMillis Connection and read timeout, in milliseconds. If exceeded, IOException is thrown.\n     @return The parsed HTML.\n\n     @throws java.net.MalformedURLException if the request URL is not a HTTP or HTTPS URL, or is otherwise malformed\n     @throws HttpStatusException if the response is not OK and HTTP response errors are not ignored\n     @throws UnsupportedMimeTypeException if the response mime type is not supported and those errors are not ignored\n     @throws java.net.SocketTimeoutException if the connection times out\n     @throws IOException if a connection or read error occurs\n\n     @see #connect(String)\n     */\n    public static Document parse(URL url, int timeoutMillis) throws IOException {\n        Connection con = HttpConnection.connect(url);\n        con.timeout(timeoutMillis);\n        return con.get();\n    }\n\n    /**\n     Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through an allow-list of safe\n     tags and attributes.\n\n     @param bodyHtml  input untrusted HTML (body fragment)\n     @param baseUri   URL to resolve relative URLs against\n     @param safelist  list of permitted HTML elements\n     @return safe HTML (body fragment)\n\n     @see Cleaner#clean(Document)\n     */\n    public static String clean(String bodyHtml, String baseUri, Safelist safelist) {\n        if (baseUri.isEmpty() && safelist.preserveRelativeLinks()) {\n            baseUri = DummyUri; // set a placeholder URI to allow relative links to pass abs resolution for protocol tests; won't leak to output\n        }\n\n        Document dirty = parseBodyFragment(bodyHtml, baseUri);\n        Cleaner cleaner = new Cleaner(safelist);\n        Document clean = cleaner.clean(dirty);\n        return clean.body().html();\n    }\n\n    /**\n     Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of permitted\n     tags and attributes.\n\n     <p>Note that as this method does not take a base href URL to resolve attributes with relative URLs against, those\n     URLs will be removed, unless the input HTML contains a {@code <base href> tag}. If you wish to preserve those, use\n     the {@link Jsoup#clean(String html, String baseHref, Safelist)} method instead, and enable\n     {@link Safelist#preserveRelativeLinks(boolean)}.</p>\n\n     <p>Note that the output of this method is still <b>HTML</b> even when using the TextNode only\n     {@link Safelist#none()}, and so any HTML entities in the output will be appropriately escaped.\n     If you want plain text, not HTML, you should use a text method such as {@link Element#text()} instead, after\n     cleaning the document.</p>\n     <p>Example:</p>\n     <pre>{@code\n     String sourceBodyHtml = \"<p>5 is &lt; 6.</p>\";\n     String html = Jsoup.clean(sourceBodyHtml, Safelist.none());\n\n     Cleaner cleaner = new Cleaner(Safelist.none());\n     String text = cleaner.clean(Jsoup.parse(sourceBodyHtml)).text();\n\n     // html is: 5 is &lt; 6.\n     // text is: 5 is < 6.\n     }</pre>\n\n     @param bodyHtml input untrusted HTML (body fragment)\n     @param safelist list of permitted HTML elements\n     @return safe HTML (body fragment)\n     @see Cleaner#clean(Document)\n     */\n    public static String clean(String bodyHtml, Safelist safelist) {\n        return clean(bodyHtml, \"\", safelist);\n    }\n\n    /**\n     * Get safe HTML from untrusted input HTML, by parsing input HTML and filtering it through a safe-list of\n     * permitted tags and attributes.\n     * <p>The HTML is treated as a body fragment; it's expected the cleaned HTML will be used within the body of an\n     * existing document. If you want to clean full documents, use {@link Cleaner#clean(Document)} instead, and add\n     * structural tags (<code>html, head, body</code> etc) to the safelist.\n     *\n     * @param bodyHtml input untrusted HTML (body fragment)\n     * @param baseUri URL to resolve relative URLs against\n     * @param safelist list of permitted HTML elements\n     * @param outputSettings document output settings; use to control pretty-printing and entity escape modes\n     * @return safe HTML (body fragment)\n     * @see Cleaner#clean(Document)\n     */\n    public static String clean(String bodyHtml, String baseUri, Safelist safelist, Document.OutputSettings outputSettings) {\n        Document dirty = parseBodyFragment(bodyHtml, baseUri);\n        Cleaner cleaner = new Cleaner(safelist);\n        Document clean = cleaner.clean(dirty);\n        clean.outputSettings(outputSettings);\n        return clean.body().html();\n    }\n\n    /**\n     Test if the input body HTML has only tags and attributes allowed by the Safelist. Useful for form validation.\n     <p>\n     This method is intended to be used in a user interface as a validator for user input. Note that regardless of the\n     output of this method, the input document <b>must always</b> be normalized using a method such as\n     {@link #clean(String, String, Safelist)}, and the result of that method used to store or serialize the document\n     before later reuse such as presentation to end users. This ensures that enforced attributes are set correctly, and\n     that any differences between how a given browser and how jsoup parses the input HTML are normalized.\n     </p>\n     <p>Example:</p>\n     <pre>{@code\n     Safelist safelist = Safelist.relaxed();\n     boolean isValid = Jsoup.isValid(sourceBodyHtml, safelist);\n     String normalizedHtml = Jsoup.clean(sourceBodyHtml, \"https://example.com/\", safelist);\n     }</pre>\n     <p>Assumes the HTML is a body fragment (i.e. will be used in an existing HTML document body.)\n     @param bodyHtml HTML to test\n     @param safelist safelist to test against\n     @return true if no tags or attributes were removed; false otherwise\n     @see #clean(String, Safelist)\n     */\n    public static boolean isValid(String bodyHtml, Safelist safelist) {\n        return new Cleaner(safelist).isValidBodyHtml(bodyHtml);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/Progress.java",
    "content": "package org.jsoup;\n\n@FunctionalInterface\n\npublic interface Progress<ProgressContext> {\n    /**\n     Called to report progress. Note that this will be executed by the same thread that is doing the work, so either\n     don't take to long, or hand it off to another thread.\n     @param processed the number of bytes processed so far.\n     @param total the total number of expected bytes, or -1 if unknown.\n     @param percent the percentage of completion, 0.0..100.0. If the expected total is unknown, % will remain at zero\n     until complete.\n     @param context the object that progress was made on.\n     @since 1.18.1\n     */\n    void onProgress(int processed, int total, float percent, ProgressContext context);\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/SerializationException.java",
    "content": "package org.jsoup;\n\n/**\n * A SerializationException is raised whenever serialization of a DOM element fails. This exception usually wraps an\n * {@link java.io.IOException} that may be thrown due to an inaccessible output stream.\n */\npublic final class SerializationException extends RuntimeException {\n\t/**\n\t * Creates and initializes a new serialization exception with no error message and cause.\n\t */\n\tpublic SerializationException() {\n\t\tsuper();\n\t}\n\n\t/**\n\t * Creates and initializes a new serialization exception with the given error message and no cause.\n\t * \n\t * @param message\n\t *            the error message of the new serialization exception (may be <code>null</code>).\n\t */\n\tpublic SerializationException(String message) {\n\t\tsuper(message);\n\t}\n\n\t/**\n\t * Creates and initializes a new serialization exception with the specified cause and an error message of\n     * <code>(cause==null ? null : cause.toString())</code> (which typically contains the class and error message of\n     * <code>cause</code>).\n\t * \n\t * @param cause\n\t *            the cause of the new serialization exception (may be <code>null</code>).\n\t */\n\tpublic SerializationException(Throwable cause) {\n\t\tsuper(cause);\n\t}\n\n\t/**\n\t * Creates and initializes a new serialization exception with the given error message and cause.\n\t * \n\t * @param message\n\t *            the error message of the new serialization exception.\n\t * @param cause\n\t *            the cause of the new serialization exception.\n\t */\n\tpublic SerializationException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/UnsupportedMimeTypeException.java",
    "content": "package org.jsoup;\n\nimport java.io.IOException;\n\n/**\n * Signals that a HTTP response returned a mime type that is not supported.\n */\npublic class UnsupportedMimeTypeException extends IOException {\n    private final String mimeType;\n    private final String url;\n\n    public UnsupportedMimeTypeException(String message, String mimeType, String url) {\n        super(message);\n        this.mimeType = mimeType;\n        this.url = url;\n    }\n\n    public String getMimeType() {\n        return mimeType;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    @Override\n    public String toString() {\n        return super.toString() + \". Mimetype=\" + mimeType + \", URL=\"+url;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/examples/HtmlToPlainText.java",
    "content": "package org.jsoup.examples;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.NodeVisitor;\n\nimport java.io.IOException;\nimport java.util.HashSet;\n\n/**\n HTML to plain-text. This example program demonstrates the use of jsoup to convert HTML input to lightly-formatted\n plain-text. That is divergent from the general goal of jsoup's .text() methods, which is to get clean data from a\n scrape.\n <p>\n Note that this is a fairly simplistic formatter -- for real world use you'll want to embrace and extend.\n </p>\n <p>\n To invoke from the command line, assuming you've downloaded the jsoup-examples jar to your current directory:</p>\n <p><code>java -jar jsoup-examples.jar url [selector]</code></p>\n where <i>url</i> is the URL to fetch, and <i>selector</i> is an optional CSS selector.\n*/\npublic class HtmlToPlainText {\n    private static final String userAgent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 (jsoup-example)\";\n    private static final int timeout = 5 * 1000;\n\n    public static void main(String... args) throws IOException {\n        Validate.isTrue(args.length == 1 || args.length == 2, \"usage: java -jar jsoup-examples.jar url [selector]\");\n        final String url = args[0];\n        final String selector = args.length == 2 ? args[1] : null;\n\n        // fetch the specified URL and parse to a HTML DOM:\n        Connection session = Jsoup.newSession() // .newSession creates a session to maintain settings and cookies across multiple requests\n            .userAgent(userAgent)\n            .timeout(timeout);\n        Document doc = session.newRequest(url).get(); // .get executes a GET request, and parses the result\n\n        if (selector != null) {\n            Elements elements = doc.select(selector); // get each element that matches the CSS selector\n            elements = trimParents(elements); // trim out elements that descend from a previously seen element\n            for (Element element : elements) {\n                String plainText = getPlainText(element); // format that element to plain text\n                System.out.println(plainText);\n            }\n        } else { // format the whole doc\n            String plainText = getPlainText(doc);\n            System.out.println(plainText);\n        }\n    }\n\n    /**\n     * Format an Element to plain-text\n     * @param element the root element to format\n     * @return formatted text\n     */\n    static String getPlainText(Element element) {\n        FormattingVisitor formatter = new FormattingVisitor();\n        formatter.traverse(element); // walk the DOM, and call .head() and .tail() for each node\n\n        return formatter.toString();\n    }\n\n    // the formatting rules, implemented in a breadth-first DOM traverse\n    private static class FormattingVisitor implements NodeVisitor {\n        private static final int maxWidth = 80;\n        private int width = 0;\n        private StringBuilder accum = new StringBuilder(); // holds the accumulated text\n\n        // hit when the node is first seen\n        @Override\n        public void head(Node node, int depth) {\n            String name = node.nodeName();\n            if (node instanceof TextNode)\n                append(((TextNode) node).text()); // TextNodes carry all user-readable text in the DOM.\n            else if (name.equals(\"li\"))\n                append(\"\\n * \");\n            else if (name.equals(\"dt\"))\n                append(\"  \");\n            else if (StringUtil.in(name, \"p\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"tr\"))\n                append(\"\\n\");\n        }\n\n        // hit when all of the node's children (if any) have been visited\n        @Override\n        public void tail(Node node, int depth) {\n            String name = node.nodeName();\n            if (StringUtil.in(name, \"br\", \"dd\", \"dt\", \"p\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\"))\n                append(\"\\n\");\n            else if (name.equals(\"a\"))\n                append(String.format(\" <%s>\", node.absUrl(\"href\")));\n        }\n\n        // appends text to the string builder with a simple word wrap method\n        private void append(String text) {\n            if (text.startsWith(\"\\n\"))\n                width = 0; // reset counter if starts with a newline. only from formats above, not in natural text\n            if (text.equals(\" \") &&\n                    (accum.length() == 0 || StringUtil.in(accum.substring(accum.length() - 1), \" \", \"\\n\")))\n                return; // don't accumulate long runs of empty spaces\n\n            if (text.length() + width > maxWidth) { // won't fit, needs to wrap\n                String[] words = text.split(\"\\\\s+\");\n                for (int i = 0; i < words.length; i++) {\n                    String word = words[i];\n                    boolean last = i == words.length - 1;\n                    if (!last) // insert a space if not the last word\n                        word = word + \" \";\n                    if (word.length() + width > maxWidth) { // wrap and reset counter\n                        accum.append(\"\\n\").append(word);\n                        width = word.length();\n                    } else {\n                        accum.append(word);\n                        width += word.length();\n                    }\n                }\n            } else { // fits as is, without need to wrap text\n                accum.append(text);\n                width += text.length();\n            }\n        }\n\n        @Override\n        public String toString() {\n            return accum.toString();\n        }\n    }\n\n    static Elements trimParents(final Elements elements) {\n        // removes elements from the list if their parent / ancestor is already in the list; prevents redundant output for selectors that match nested elements\n        HashSet<Element> seen = new HashSet<>(elements.size());\n        Elements trimmed = new Elements();\n\n        EACH: for (Element el: elements) {\n            Element current = el;\n            while (current.parent() != null) {\n                if (seen.contains(current.parent())) {\n                    continue EACH;\n                }\n                current = current.parent();\n            }\n            seen.add(el);\n            trimmed.add(el);\n        }\n\n        return trimmed;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/examples/ListLinks.java",
    "content": "package org.jsoup.examples;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\nimport java.io.IOException;\n\n/**\n Example program to list links from a URL.\n <p>To invoke from the command line, assuming you've downloaded the jsoup-examples\n jar to your current directory:</p>\n <p><code>java -cp jsoup-examples.jar org.jsoup.examples.ListLinks url</code></p>\n where <i>url</i> is the URL to fetch.\n */\npublic class ListLinks {\n    public static void main(String[] args) throws IOException {\n        Validate.isTrue(args.length == 1, \"usage: supply url to fetch\");\n        String url = args[0];\n        print(\"Fetching %s...\", url);\n\n        Document doc = Jsoup.connect(url).get();\n        Elements links = doc.select(\"a[href]\");\n        Elements media = doc.select(\"[src]\");\n        Elements imports = doc.select(\"link[href]\");\n\n        print(\"\\nMedia: (%d)\", media.size());\n        for (Element src : media) {\n            if (src.nameIs(\"img\"))\n                print(\" * %s: <%s> %sx%s (%s)\",\n                        src.tagName(), src.attr(\"abs:src\"), src.attr(\"width\"), src.attr(\"height\"),\n                        trim(src.attr(\"alt\"), 20));\n            else\n                print(\" * %s: <%s>\", src.tagName(), src.attr(\"abs:src\"));\n        }\n\n        print(\"\\nImports: (%d)\", imports.size());\n        for (Element link : imports) {\n            print(\" * %s <%s> (%s)\", link.tagName(),link.attr(\"abs:href\"), link.attr(\"rel\"));\n        }\n\n        print(\"\\nLinks: (%d)\", links.size());\n        for (Element link : links) {\n            print(\" * a: <%s>  (%s)\", link.attr(\"abs:href\"), trim(link.text(), 35));\n        }\n    }\n\n    private static void print(String msg, Object... args) {\n        System.out.println(String.format(msg, args));\n    }\n\n    private static String trim(String s, int width) {\n        if (s.length() > width)\n            return s.substring(0, width-1) + \".\";\n        else\n            return s;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/examples/Wikipedia.java",
    "content": "package org.jsoup.examples;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\n\nimport java.io.IOException;\n\n/**\n * A simple example, used on the jsoup website.\n <p>To invoke from the command line, assuming you've downloaded the jsoup-examples\n jar to your current directory:</p>\n <p><code>java -cp jsoup-examples.jar org.jsoup.examples.Wikipedia url</code></p>\n */\npublic class Wikipedia {\n    public static void main(String[] args) throws IOException {\n        Document doc = Jsoup.connect(\"https://en.wikipedia.org/\").get();\n        log(doc.title());\n\n        Elements newsHeadlines = doc.select(\"#mp-itn b a\");\n        for (Element headline : newsHeadlines) {\n            log(\"%s\\n\\t%s\", headline.attr(\"title\"), headline.absUrl(\"href\"));\n        }\n    }\n\n    private static void log(String msg, String... vals) {\n        System.out.println(String.format(msg, (Object[]) vals));\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/examples/package-info.java",
    "content": "/**\n Contains example programs and use of jsoup. See the <a href=\"https://jsoup.org/cookbook/\">jsoup cookbook</a>.\n <p>The code in this package is not available in the main `jsoup.jar` file; only in the `jsoup-examples.jar`, and has no\n API contract. To use these examples your own project, you should copy the source code and modify as desired.</p>\n */\npackage org.jsoup.examples;\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/AuthenticationHandler.java",
    "content": "package org.jsoup.helper;\n\nimport org.jspecify.annotations.Nullable;\n\nimport java.lang.reflect.Constructor;\nimport java.net.Authenticator;\nimport java.net.PasswordAuthentication;\n\n/**\n Handles per request Authenticator-based authentication. Loads the class `org.jsoup.helper.RequestAuthHandler` if\n per-request Authenticators are supported (Java 9+), or installs a system-wide Authenticator that delegates to a request\n ThreadLocal.\n */\nclass AuthenticationHandler extends Authenticator {\n    static final int MaxAttempts = 3; // max authentication attempts per request. allows for multiple auths (e.g. proxy and server) in one request, but saves otherwise 20 requests if credentials are incorrect.\n    static AuthShim handler;\n\n    static {\n        try {\n            //noinspection unchecked\n            Class<AuthShim> perRequestClass = (Class<AuthShim>) Class.forName(\"org.jsoup.helper.RequestAuthHandler\");\n            Constructor<AuthShim> constructor = perRequestClass.getConstructor();\n            handler = constructor.newInstance();\n        } catch (ClassNotFoundException e) {\n            handler = new GlobalHandler();\n        } catch (Exception e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    @Nullable RequestAuthenticator auth;\n    int attemptCount = 0;\n\n    AuthenticationHandler() {}\n\n    AuthenticationHandler(RequestAuthenticator auth) {\n        this.auth = auth;\n    }\n\n    /**\n     Authentication callback, called by HttpURLConnection - either as system-wide default (Java 8) or per HttpURLConnection (Java 9+)\n     * @return credentials, or null if not attempting to auth.\n     */\n    @Nullable @Override public final PasswordAuthentication getPasswordAuthentication() {\n        AuthenticationHandler delegate = handler.get(this);\n        if (delegate == null) return null; // this request has no auth handler\n        delegate.attemptCount++;\n        // if the password returned fails, Java will repeatedly retry the request with a new password auth hit (because\n        // it may be an interactive prompt, and the user could eventually get it right). But in Jsoup's context, the\n        // auth will either be correct or not, so just abandon\n        if (delegate.attemptCount > MaxAttempts)\n            return null; // When using HttpClient, this will manifest as \"No credentials provided\" IOException; not ideal; would be clearer if we could then detach the authenticator which would bubble the 401, but there's no path for that\n        if (delegate.auth == null)\n            return null; // detached - would have been the Global Authenticator (not a delegate)\n\n        RequestAuthenticator.Context ctx = new RequestAuthenticator.Context(\n            this.getRequestingURL(), this.getRequestorType(), this.getRequestingPrompt());\n        return delegate.auth.authenticate(ctx);\n    }\n\n    interface AuthShim {\n        void enable(RequestAuthenticator auth, Object connOrHttp);\n\n        void remove();\n\n        @Nullable AuthenticationHandler get(AuthenticationHandler helper);\n    }\n\n    /**\n     On Java 8 we install a system-wide Authenticator, which pulls the delegating Auth from a ThreadLocal pool.\n     */\n    static class GlobalHandler implements AuthShim {\n        static ThreadLocal<AuthenticationHandler> authenticators = new ThreadLocal<>();\n        static {\n            Authenticator.setDefault(new AuthenticationHandler());\n        }\n\n        @Override public void enable(RequestAuthenticator auth, Object ignored) {\n            authenticators.set(new AuthenticationHandler(auth));\n        }\n\n        @Override public void remove() {\n            authenticators.remove();\n        }\n\n        @Override public AuthenticationHandler get(AuthenticationHandler helper) {\n            return authenticators.get();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/CookieUtil.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.CharacterReader;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.net.CookieManager;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.BiConsumer;\n\n/**\n Helper functions to support the Cookie Manager / Cookie Storage in HttpConnection.\n\n @since 1.14.1 */\nclass CookieUtil {\n    // cookie manager get() wants request headers but doesn't use them, so we just pass a dummy object here\n    private static final Map<String, List<String>> EmptyRequestHeaders = Collections.unmodifiableMap(new HashMap<>());\n    private static final String Sep = \"; \";\n    private static final String CookieName = \"Cookie\";\n    private static final String Cookie2Name = \"Cookie2\";\n\n    /**\n     Pre-request, get any applicable headers out of the Request cookies and the Cookie Store, and add them to the request\n     headers. If the Cookie Store duplicates any Request cookies (same name and value), they will be discarded.\n     */\n    static void applyCookiesToRequest(HttpConnection.Request req, BiConsumer<String, String> setter) throws IOException {\n        // Request key/val cookies. LinkedHashSet used to preserve order, as cookie store will return most specific path first\n        Set<String> cookieSet = requestCookieSet(req);\n        Set<String> cookies2 = null;\n\n        // stored:\n        Map<String, List<String>> storedCookies = req.cookieManager().get(asUri(req.url), EmptyRequestHeaders);\n        for (Map.Entry<String, List<String>> entry : storedCookies.entrySet()) {\n            // might be Cookie: name=value; name=value\\nCookie2: name=value; name=value\n            List<String> cookies = entry.getValue(); // these will be name=val\n            if (cookies == null || cookies.size() == 0) // the cookie store often returns just an empty \"Cookie\" key, no val\n                continue;\n\n            String key = entry.getKey(); // Cookie or Cookie2\n            Set<String> set;\n            if (CookieName.equals(key))\n                set = cookieSet;\n            else if (Cookie2Name.equals(key)) {\n                set = new HashSet<>();\n                cookies2 = set;\n            } else {\n                continue; // unexpected header key\n            }\n            set.addAll(cookies);\n        }\n\n        if (cookieSet.size() > 0)\n            setter.accept(CookieName, StringUtil.join(cookieSet, Sep));\n        if (cookies2 != null && cookies2.size() > 0)\n            setter.accept(Cookie2Name, StringUtil.join(cookies2, Sep));\n    }\n\n    private static LinkedHashSet<String> requestCookieSet(Connection.Request req) {\n        LinkedHashSet<String> set = new LinkedHashSet<>();\n        // req cookies are the wildcard key/val cookies (no domain, path, etc)\n        for (Map.Entry<String, String> cookie : req.cookies().entrySet()) {\n            set.add(cookie.getKey() + \"=\" + cookie.getValue());\n        }\n        return set;\n    }\n\n    static URI asUri(URL url) throws IOException {\n        try {\n            return url.toURI();\n        } catch (URISyntaxException e) {  // this would be a WTF because we construct the URL\n            MalformedURLException ue = new MalformedURLException(e.getMessage());\n            ue.initCause(e);\n            throw ue;\n        }\n    }\n\n    /** Store the Result cookies into the cookie manager, and place relevant cookies into the Response object. */\n    static void storeCookies(HttpConnection.Request req, HttpConnection.Response res, URL url, Map<String, List<String>> resHeaders) throws IOException {\n        CookieManager manager = req.cookieManager();\n        URI uri = CookieUtil.asUri(url);\n        manager.put(uri, resHeaders); // stores cookies for session\n\n        // set up the simple cookies() map\n        // the response may include cookies that are not relevant to this request, but users may require them if they are not using the cookie manager (setting request cookies only from the simple cookies() response):\n        for (Map.Entry<String, List<String>> entry : resHeaders.entrySet()) {\n            String name = entry.getKey();\n            List<String> values = entry.getValue();\n            if (name.equalsIgnoreCase(\"Set-Cookie\")) {\n                for (String value : values) {\n                    parseCookie(value, res);\n                }\n            }\n        }\n    }\n\n    static void parseCookie(@Nullable String value, HttpConnection.Response res) {\n        if (value == null) return;\n        CharacterReader reader = new CharacterReader(value);\n        String cookieName = reader.consumeTo('=').trim();\n        reader.advance();\n        String cookieVal = reader.consumeTo(';').trim();\n        // ignores path, date, domain, validateTLSCertificates et al. full details will be available in cookiestore if required\n        // name not blank, value not null\n        if (!cookieName.isEmpty())\n            res.cookie(cookieName, cookieVal); // if duplicate names, last set will win\n        reader.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/DataUtil.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jsoup.internal.ControllableInputStream;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.internal.SimpleStreamReader;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.XmlDeclaration;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.Selector;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.io.UncheckedIOException;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Channels;\nimport java.nio.channels.SeekableByteChannel;\nimport java.nio.charset.Charset;\nimport java.nio.charset.IllegalCharsetNameException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Locale;\nimport java.util.Random;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.zip.GZIPInputStream;\n\nimport static org.jsoup.internal.SharedConstants.DefaultBufferSize;\n\n/**\n * Internal static utilities for handling data.\n *\n */\n@SuppressWarnings(\"CharsetObjectCanBeUsed\")\npublic final class DataUtil {\n    private static final Pattern charsetPattern = Pattern.compile(\"(?i)\\\\bcharset=\\\\s*(?:[\\\"'])?([^\\\\s,;\\\"']*)\");\n    public static final Charset UTF_8 = Charset.forName(\"UTF-8\"); // Don't use StandardCharsets, as those only appear in Android API 19, and we target 10.\n    static final String defaultCharsetName = UTF_8.name(); // used if not found in header or meta charset\n    private static final int firstReadBufferSize = 1024 * 5;\n    private static final char[] mimeBoundaryChars =\n            \"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n    static final int boundaryLength = 32;\n\n    private DataUtil() {}\n\n    /**\n     * Loads and parses a file to a Document, with the HtmlParser. Files that are compressed with gzip (and end in {@code .gz} or {@code .z})\n     * are supported in addition to uncompressed files.\n     *\n     * @param file file to load\n     * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in\n     *     the file will always override this setting.\n     * @param baseUri base URI of document, to resolve relative links against\n     * @return Document\n     * @throws IOException on IO error\n     */\n    public static Document load(File file, @Nullable String charsetName, String baseUri) throws IOException {\n        return load(file.toPath(), charsetName, baseUri);\n    }\n\n    /**\n     * Loads and parses a file to a Document. Files that are compressed with gzip (and end in {@code .gz} or {@code .z})\n     * are supported in addition to uncompressed files.\n     *\n     * @param file file to load\n     * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in\n     *     the file will always override this setting.\n     * @param baseUri base URI of document, to resolve relative links against\n     * @param parser alternate {@link Parser#xmlParser() parser} to use.\n\n     * @return Document\n     * @throws IOException on IO error\n     * @since 1.14.2\n     */\n    public static Document load(File file, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return load(file.toPath(), charsetName, baseUri, parser);\n    }\n\n    /**\n     * Loads and parses a file to a Document, with the HtmlParser. Files that are compressed with gzip (and end in {@code .gz} or {@code .z})\n     * are supported in addition to uncompressed files.\n     *\n     * @param path file to load\n     * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in\n     *     the file will always override this setting.\n     * @param baseUri base URI of document, to resolve relative links against\n     * @return Document\n     * @throws IOException on IO error\n     */\n    public static Document load(Path path, @Nullable String charsetName, String baseUri) throws IOException {\n        return load(path, charsetName, baseUri, Parser.htmlParser());\n    }\n\n    /**\n     * Loads and parses a file to a Document. Files that are compressed with gzip (and end in {@code .gz} or {@code .z})\n     * are supported in addition to uncompressed files.\n     *\n     * @param path file to load\n     * @param charsetName (optional) character set of input; specify {@code null} to attempt to autodetect. A BOM in\n     * the file will always override this setting.\n     * @param baseUri base URI of document, to resolve relative links against\n     * @param parser alternate {@link Parser#xmlParser() parser} to use.\n\n     * @return Document\n     * @throws IOException on IO error\n     * @since 1.17.2\n     */\n    public static Document load(Path path, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return parseInputStream(openStream(path), charsetName, baseUri, parser);\n    }\n\n    /**\n     * Returns a {@link StreamParser} that will parse the supplied file progressively.\n     * Files that are compressed with gzip (and end in {@code .gz} or {@code .z})\n     * are supported in addition to uncompressed files.\n     *\n     * @param path file to load\n     * @param charset (optional) character set of input; specify {@code null} to attempt to autodetect from metadata.\n     * A BOM in the file will always override this setting.\n     * @param baseUri base URI of document, to resolve relative links against\n     * @param parser underlying HTML or XML parser to use.\n\n     * @return Document\n     * @throws IOException on IO error\n     * @since 1.18.2\n     * @see Connection.Response#streamParser()\n     */\n    public static StreamParser streamParser(Path path, @Nullable Charset charset, String baseUri, Parser parser) throws IOException {\n        StreamParser streamer = new StreamParser(parser);\n        String charsetName = charset != null? charset.name() : null;\n        try {\n            DataUtil.CharsetDoc charsetDoc = DataUtil.detectCharset(openStream(path), charsetName, baseUri, parser);\n            Reader reader = new SimpleStreamReader(charsetDoc.input, charsetDoc.charset);\n            streamer.parse(reader, baseUri); // initializes the parse and the document, but does not step() it\n        } catch (IOException e) {\n            streamer.close();\n            throw e;\n        }\n        return streamer;\n    }\n\n    /** Open an input stream from a file; if it's a gzip file, returns a GZIPInputStream to unzip it. */\n    private static ControllableInputStream openStream(Path path) throws IOException {\n        final SeekableByteChannel byteChannel = Files.newByteChannel(path);\n        InputStream stream = Channels.newInputStream(byteChannel);\n        String name = Normalizer.lowerCase(path.getFileName().toString());\n        if (name.endsWith(\".gz\") || name.endsWith(\".z\")) {\n            try {\n                final boolean zipped = (stream.read() == 0x1f && stream.read() == 0x8b); // gzip magic bytes\n                byteChannel.position(0); // reset to start of file\n                if (zipped) stream = new GZIPInputStream(stream);\n            } catch (IOException e) {\n                stream.close(); // error during our first read; close the stream and cascade close byteChannel\n                throw e;\n            }\n        }\n        return ControllableInputStream.wrap(stream, 0);\n    }\n\n    /**\n     * Parses a Document from an input steam.\n     * @param in input stream to parse. The stream will be closed after reading.\n     * @param charsetName character set of input (optional)\n     * @param baseUri base URI of document, to resolve relative links against\n     * @return Document\n     * @throws IOException on IO error\n     */\n    public static Document load(InputStream in, @Nullable String charsetName, String baseUri) throws IOException {\n        return parseInputStream(ControllableInputStream.wrap(in, 0), charsetName, baseUri, Parser.htmlParser());\n    }\n\n    /**\n     * Parses a Document from an input steam, using the provided Parser.\n     * @param in input stream to parse. The stream will be closed after reading.\n     * @param charsetName character set of input (optional)\n     * @param baseUri base URI of document, to resolve relative links against\n     * @param parser alternate {@link Parser#xmlParser() parser} to use.\n     * @return Document\n     * @throws IOException on IO error\n     */\n    public static Document load(InputStream in, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        return parseInputStream(ControllableInputStream.wrap(in, 0), charsetName, baseUri, parser);\n    }\n\n    /**\n     * Writes the input stream to the output stream. Doesn't close them.\n     * @param in input stream to read from\n     * @param out output stream to write to\n     * @throws IOException on IO error\n     */\n    static void crossStreams(final InputStream in, final OutputStream out) throws IOException {\n        final byte[] buffer = new byte[DefaultBufferSize];\n        int len;\n        while ((len = in.read(buffer)) != -1) {\n            out.write(buffer, 0, len);\n        }\n    }\n\n    /** A struct to return a detected charset, and a document (if fully read). */\n    static class CharsetDoc {\n        Charset charset;\n        InputStream input;\n        @Nullable Document doc;\n\n        CharsetDoc(Charset charset, @Nullable Document doc, InputStream input) {\n            this.charset = charset;\n            this.input = input;\n            this.doc = doc;\n        }\n    }\n\n    static Document parseInputStream(@Nullable ControllableInputStream input, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        if (input == null) return new Document(baseUri); // empty body\n\n        final Document doc;\n        CharsetDoc charsetDoc = null;\n        try {\n            charsetDoc = detectCharset(input, charsetName, baseUri, parser);\n            doc = parseInputStream(charsetDoc, baseUri, parser);\n        } finally {\n            if (charsetDoc != null)\n                charsetDoc.input.close();\n        }\n        return doc;\n    }\n\n    private static final Evaluator metaCharset = Selector.evaluatorOf(\"meta[http-equiv=content-type], meta[charset]\");\n\n    static CharsetDoc detectCharset(ControllableInputStream input, @Nullable String charsetName, String baseUri, Parser parser) throws IOException {\n        Document doc = null;\n        // read the start of the stream and look for a BOM or meta charset:\n        // look for BOM - overrides any other header or input\n        String bomCharset = detectCharsetFromBom(input); // resets / consumes appropriately\n        if (bomCharset != null)\n            charsetName = bomCharset;\n\n        if (charsetName == null) { // read ahead and determine from meta. safe first parse as UTF-8\n            int origMax = input.max();\n            input.max(firstReadBufferSize);\n            input.resetFullyRead(); // clear any pre-read (e.g., BOM) state before capped sniff\n            input.mark(firstReadBufferSize);\n            input.allowClose(false); // ignores closes during parse, in case we need to rewind\n            try (Reader reader = new SimpleStreamReader(input, UTF_8)) { // input is currently capped to firstReadBufferSize\n                doc = parser.parseInput(reader, baseUri);\n                input.reset();\n                input.max(origMax); // reset for a full read if required\n            } catch (UncheckedIOException e) {\n                throw e.getCause();\n            } finally {\n                input.allowClose(true);\n            }\n\n            // look for <meta http-equiv=\"Content-Type\" content=\"text/html;charset=gb2312\"> or HTML5 <meta charset=\"gb2312\">\n            Elements metaElements = doc.select(metaCharset);\n            String foundCharset = null; // if not found, will keep utf-8 as best attempt\n            for (Element meta : metaElements) {\n                if (meta.hasAttr(\"http-equiv\"))\n                    foundCharset = getCharsetFromContentType(meta.attr(\"content\"));\n                if (foundCharset == null && meta.hasAttr(\"charset\"))\n                    foundCharset = meta.attr(\"charset\");\n                if (foundCharset != null)\n                    break;\n            }\n\n            // look for <?xml encoding='ISO-8859-1'?>\n            if (foundCharset == null && doc.childNodeSize() > 0) {\n                Node first = doc.childNode(0);\n                XmlDeclaration decl = null;\n                if (first instanceof XmlDeclaration)\n                    decl = (XmlDeclaration) first;\n                else if (first instanceof Comment) {\n                    Comment comment = (Comment) first;\n                    if (comment.isXmlDeclaration())\n                        decl = comment.asXmlDeclaration();\n                }\n                if (decl != null && decl.name().equalsIgnoreCase(\"xml\")) {\n                    foundCharset = decl.attr(\"encoding\");\n                }\n            }\n            foundCharset = validateCharset(foundCharset);\n            if (foundCharset != null && !foundCharset.equalsIgnoreCase(defaultCharsetName)) { // need to re-decode. (case-insensitive check here to match how validate works)\n                foundCharset = foundCharset.trim().replaceAll(\"[\\\"']\", \"\");\n                charsetName = foundCharset;\n                doc = null;\n            } else if (input.baseReadFully()) { // if we have read fully, and the charset was correct, keep that current parse\n                input.close(); // the parser tried to close it\n            } else {\n                doc = null;\n            }\n        } else { // specified by content type header (or by user on file load)\n            Validate.notEmpty(charsetName, \"Must set charset arg to character set of file to parse. Set to null to attempt to detect from HTML\");\n        }\n\n        // finally: prepare the return struct\n        if (charsetName == null)\n            charsetName = defaultCharsetName;\n        Charset charset = charsetName.equals(defaultCharsetName) ? UTF_8 : Charset.forName(charsetName);\n        return new CharsetDoc(charset, doc, input);\n    }\n\n    static Document parseInputStream(CharsetDoc charsetDoc, String baseUri, Parser parser) throws IOException {\n        // if doc != null it was fully parsed during charset detection; so just return that\n        if (charsetDoc.doc != null)\n            return charsetDoc.doc;\n\n        final InputStream input = charsetDoc.input;\n        Validate.notNull(input);\n        final Document doc;\n        final Charset charset = charsetDoc.charset;\n        try (Reader reader = new SimpleStreamReader(input, charset)) {\n            try {\n                doc = parser.parseInput(reader, baseUri);\n            } catch (UncheckedIOException e) {\n                // io exception when parsing (not seen before because reading the stream as we go)\n                throw e.getCause();\n            }\n            doc.outputSettings().charset(charset);\n            if (!charset.canEncode()) {\n                // some charsets can read but not encode; switch to an encodable charset and update the meta el\n                doc.charset(UTF_8);\n            }\n        }\n        return doc;\n    }\n\n    /**\n     * Read the input stream into a byte buffer. To deal with slow input streams, you may interrupt the thread this\n     * method is executing on. The data read until being interrupted will be available.\n     * @param inStream the input stream to read from\n     * @param maxSize the maximum size in bytes to read from the stream. Set to 0 to be unlimited.\n     * @return the filled byte buffer\n     * @throws IOException if an exception occurs whilst reading from the input stream.\n     */\n    public static ByteBuffer readToByteBuffer(InputStream inStream, int maxSize) throws IOException {\n        return ControllableInputStream.readToByteBuffer(inStream, maxSize);\n    }\n\n    static ByteBuffer emptyByteBuffer() {\n        return ByteBuffer.allocate(0);\n    }\n\n    /**\n     * Parse out a charset from a content type header. If the charset is not supported, returns null (so the default\n     * will kick in.)\n     * @param contentType e.g. \"text/html; charset=EUC-JP\"\n     * @return \"EUC-JP\", or null if not found. Charset is trimmed and uppercased.\n     */\n    static @Nullable String getCharsetFromContentType(@Nullable String contentType) {\n        if (contentType == null) return null;\n        Matcher m = charsetPattern.matcher(contentType);\n        if (m.find()) {\n            String charset = m.group(1).trim();\n            charset = charset.replace(\"charset=\", \"\");\n            return validateCharset(charset);\n        }\n        return null;\n    }\n\n    private @Nullable static String validateCharset(@Nullable String cs) {\n        if (cs == null || cs.length() == 0) return null;\n        cs = cs.trim().replaceAll(\"[\\\"']\", \"\");\n        try {\n            if (Charset.isSupported(cs)) return cs;\n            cs = cs.toUpperCase(Locale.ENGLISH);\n            if (Charset.isSupported(cs)) return cs;\n        } catch (IllegalCharsetNameException e) {\n            // if all this charset matching fails.... we just take the default\n        }\n        return null;\n    }\n\n    /**\n     * Creates a random string, suitable for use as a mime boundary\n     */\n    static String mimeBoundary() {\n        final StringBuilder mime = StringUtil.borrowBuilder();\n        final Random rand = new Random();\n        for (int i = 0; i < boundaryLength; i++) {\n            mime.append(mimeBoundaryChars[rand.nextInt(mimeBoundaryChars.length)]);\n        }\n        return StringUtil.releaseBuilder(mime);\n    }\n\n    private static @Nullable String detectCharsetFromBom(ControllableInputStream input) throws IOException {\n        byte[] bom = new byte[4];\n        input.mark(bom.length);\n        //noinspection ResultOfMethodCallIgnored\n        input.read(bom, 0, 4);\n        input.reset();\n\n        // 16 and 32 decoders consume the BOM to determine be/le; utf-8 should be consumed here\n        if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF || // BE\n            bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { // LE\n            return \"UTF-32\"; // and I hope it's on your system\n        } else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF || // BE\n            bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE) {\n            return \"UTF-16\"; // in all Javas\n        } else if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF) {\n            input.read(bom, 0, 3); // consume the UTF-8 BOM\n            return \"UTF-8\"; // in all Javas\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/HttpConnection.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jsoup.HttpStatusException;\nimport org.jsoup.Progress;\nimport org.jsoup.UnsupportedMimeTypeException;\nimport org.jsoup.internal.ControllableInputStream;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.jspecify.annotations.Nullable;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport java.io.BufferedInputStream;\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.UncheckedIOException;\nimport java.net.CookieManager;\nimport java.net.CookieStore;\nimport java.net.InetSocketAddress;\nimport java.net.MalformedURLException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.nio.Buffer;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.IllegalCharsetNameException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.regex.Pattern;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.Inflater;\nimport java.util.zip.InflaterInputStream;\n\nimport static org.jsoup.Connection.Method.HEAD;\nimport static org.jsoup.helper.DataUtil.UTF_8;\nimport static org.jsoup.internal.Normalizer.lowerCase;\nimport static org.jsoup.internal.SharedConstants.DefaultBufferSize;\n\n/**\n * Implementation of {@link Connection}.\n * @see org.jsoup.Jsoup#connect(String)\n */\n@SuppressWarnings(\"CharsetObjectCanBeUsed\")\npublic class HttpConnection implements Connection {\n    public static final String CONTENT_ENCODING = \"Content-Encoding\";\n    /**\n     * Many users would get caught by not setting a user-agent and therefore getting different responses on their desktop\n     * vs in jsoup, which would otherwise default to {@code Java}. So by default, use a desktop UA.\n     */\n    public static final String DEFAULT_UA =\n        \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36\";\n    private static final String USER_AGENT = \"User-Agent\";\n    public static final String CONTENT_TYPE = \"Content-Type\";\n    public static final String MULTIPART_FORM_DATA = \"multipart/form-data\";\n    public static final String FORM_URL_ENCODED = \"application/x-www-form-urlencoded\";\n    private static final int HTTP_TEMP_REDIR = 307; // http/1.1 temporary redirect, not in Java's set.\n    static final String DefaultUploadType = \"application/octet-stream\";\n    private static final Charset ISO_8859_1 = Charset.forName(\"ISO-8859-1\");\n\n    private HttpConnection.Request req;\n    private Connection.@Nullable Response res;\n    @Nullable Object client; // The HttpClient for this Connection, if via the HttpClientExecutor\n    @Nullable RequestAuthenticator lastAuth; // The previous Authenticator used by this Connection, if via the HttpClientExecutor\n\n    /**\n     Create a new Connection, with the request URL specified.\n     @param url the URL to fetch from\n     @return a new Connection object\n     */\n    public static Connection connect(String url) {\n        Connection con = new HttpConnection();\n        con.url(url);\n        return con;\n    }\n\n    /**\n     Create a new Connection, with the request URL specified.\n     @param url the URL to fetch from\n     @return a new Connection object\n     */\n    public static Connection connect(URL url) {\n        Connection con = new HttpConnection();\n        con.url(url);\n        return con;\n    }\n\n    /**\n     Create a new, empty HttpConnection.\n     */\n    public HttpConnection() {\n        req = new Request();\n        req.connection = this;\n    }\n\n    /**\n     Create a new Request by deep-copying an existing Request. Note that the data and body of the original are not\n     copied. All other settings (proxy, parser, cookies, etc) are copied.\n     @param copy the request to copy\n     */\n    HttpConnection(Request copy) {\n        req = new Request(copy);\n    }\n\n    static String encodeMimeName(String val) {\n        return val.replace(\"\\\"\", \"%22\");\n    }\n\n    @Override\n    public Connection newRequest() {\n        // copy the prototype request for the different settings, cookie manager, etc\n        return new HttpConnection(req);\n    }\n\n    /** Create a new Connection that just wraps the provided Request and Response */\n    private HttpConnection(Request req, Response res) {\n        this.req = req;\n        this.res = res;\n    }\n\n    @Override\n    public Connection url(URL url) {\n        req.url(url);\n        return this;\n    }\n\n    @Override\n    public Connection url(String url) {\n        Validate.notEmptyParam(url, \"url\");\n        try {\n            req.url(new URL(url));\n        } catch (MalformedURLException e) {\n            throw new IllegalArgumentException(String.format(\"The supplied URL, '%s', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'. See https://jsoup.org/cookbook/extracting-data/working-with-urls\", url), e);\n        }\n        return this;\n    }\n\n    @Override\n    public Connection proxy(@Nullable Proxy proxy) {\n        req.proxy(proxy);\n        return this;\n    }\n\n    @Override\n    public Connection proxy(String host, int port) {\n        req.proxy(host, port);\n        return this;\n    }\n\n    @Override\n    public Connection userAgent(String userAgent) {\n        Validate.notNullParam(userAgent, \"userAgent\");\n        req.header(USER_AGENT, userAgent);\n        return this;\n    }\n\n    @Override\n    public Connection timeout(int millis) {\n        req.timeout(millis);\n        return this;\n    }\n\n    @Override\n    public Connection maxBodySize(int bytes) {\n        req.maxBodySize(bytes);\n        return this;\n    }\n\n    @Override\n    public Connection followRedirects(boolean followRedirects) {\n        req.followRedirects(followRedirects);\n        return this;\n    }\n\n    @Override\n    public Connection referrer(String referrer) {\n        Validate.notNullParam(referrer, \"referrer\");\n        req.header(\"Referer\", referrer);\n        return this;\n    }\n\n    @Override\n    public Connection method(Method method) {\n        req.method(method);\n        return this;\n    }\n\n    @Override\n    public Connection ignoreHttpErrors(boolean ignoreHttpErrors) {\n\t\treq.ignoreHttpErrors(ignoreHttpErrors);\n\t\treturn this;\n\t}\n\n    @Override\n    public Connection ignoreContentType(boolean ignoreContentType) {\n        req.ignoreContentType(ignoreContentType);\n        return this;\n    }\n\n    @Override\n    public Connection data(String key, String value) {\n        req.data(KeyVal.create(key, value));\n        return this;\n    }\n\n    @Override\n    public Connection sslSocketFactory(SSLSocketFactory sslSocketFactory) {\n\t    req.sslSocketFactory(sslSocketFactory);\n\t    return this;\n    }\n\n    @Override\n    public Connection sslContext(SSLContext sslContext) {\n        req.sslContext(sslContext);\n        return this;\n    }\n\n    @Override\n    public Connection data(String key, String filename, InputStream inputStream) {\n        req.data(KeyVal.create(key, filename, inputStream));\n        return this;\n    }\n\n    @Override\n    public Connection data(String key, String filename, InputStream inputStream, String contentType) {\n        req.data(KeyVal.create(key, filename, inputStream).contentType(contentType));\n        return this;\n    }\n\n    @Override\n    public Connection data(Map<String, String> data) {\n        Validate.notNullParam(data, \"data\");\n        for (Map.Entry<String, String> entry : data.entrySet()) {\n            req.data(KeyVal.create(entry.getKey(), entry.getValue()));\n        }\n        return this;\n    }\n\n    @Override\n    public Connection data(String... keyvals) {\n        Validate.notNullParam(keyvals, \"keyvals\");\n        Validate.isTrue(keyvals.length %2 == 0, \"Must supply an even number of key value pairs\");\n        for (int i = 0; i < keyvals.length; i += 2) {\n            String key = keyvals[i];\n            String value = keyvals[i+1];\n            Validate.notEmpty(key, \"Data key must not be empty\");\n            Validate.notNull(value, \"Data value must not be null\");\n            req.data(KeyVal.create(key, value));\n        }\n        return this;\n    }\n\n    @Override\n    public Connection data(Collection<Connection.KeyVal> data) {\n        Validate.notNullParam(data, \"data\");\n        for (Connection.KeyVal entry: data) {\n            req.data(entry);\n        }\n        return this;\n    }\n\n    @Override\n    public Connection.@Nullable KeyVal data(String key) {\n        Validate.notEmptyParam(key, \"key\");\n        for (Connection.KeyVal keyVal : request().data()) {\n            if (keyVal.key().equals(key))\n                return keyVal;\n        }\n        return null;\n    }\n\n    @Override\n    public Connection requestBody(String body) {\n        req.requestBody(body);\n        return this;\n    }\n\n    @Override\n    public Connection requestBodyStream(InputStream stream) {\n        req.requestBodyStream(stream);\n        return this;\n    }\n\n    @Override\n    public Connection header(String name, String value) {\n        req.header(name, value);\n        return this;\n    }\n\n    @Override\n    public Connection headers(Map<String,String> headers) {\n        Validate.notNullParam(headers, \"headers\");\n        for (Map.Entry<String,String> entry : headers.entrySet()) {\n            req.header(entry.getKey(),entry.getValue());\n        }\n        return this;\n    }\n\n    @Override\n    public Connection cookie(String name, String value) {\n        req.cookie(name, value);\n        return this;\n    }\n\n    @Override\n    public Connection cookies(Map<String, String> cookies) {\n        Validate.notNullParam(cookies, \"cookies\");\n        for (Map.Entry<String, String> entry : cookies.entrySet()) {\n            req.cookie(entry.getKey(), entry.getValue());\n        }\n        return this;\n    }\n\n    @Override\n    public Connection cookieStore(CookieStore cookieStore) {\n        // create a new cookie manager using the new store\n        req.cookieManager = new CookieManager(cookieStore, null);\n        return this;\n    }\n\n    @Override\n    public CookieStore cookieStore() {\n        return req.cookieManager.getCookieStore();\n    }\n\n    @Override\n    public Connection parser(Parser parser) {\n        req.parser(parser);\n        return this;\n    }\n\n    @Override\n    public Document get() throws IOException {\n        req.method(Method.GET);\n        execute();\n        Validate.notNull(res);\n        return res.parse();\n    }\n\n    @Override\n    public Document post() throws IOException {\n        req.method(Method.POST);\n        execute();\n        Validate.notNull(res);\n        return res.parse();\n    }\n\n    @Override\n    public Connection.Response execute() throws IOException {\n        res = Response.execute(req);\n        return res;\n    }\n\n    @Override\n    public Connection.Request request() {\n        return req;\n    }\n\n    @Override\n    public Connection request(Connection.Request request) {\n        req = (HttpConnection.Request) request; // will throw a class-cast exception if the user has extended some but not all of Connection; that's desired\n        return this;\n    }\n\n    @Override\n    public Connection.Response response() {\n        if (res == null) {\n            throw new IllegalArgumentException(\"You must execute the request before getting a response.\");\n        }\n        return res;\n    }\n\n    @Override\n    public Connection response(Connection.Response response) {\n        res = response;\n        return this;\n    }\n\n    @Override\n    public Connection postDataCharset(String charset) {\n        req.postDataCharset(charset);\n        return this;\n    }\n\n    @Override public Connection auth(@Nullable RequestAuthenticator authenticator) {\n        req.auth(authenticator);\n        return this;\n    }\n\n    @Override public Connection onResponseProgress(Progress<Connection.Response> handler) {\n        req.responseProgress = handler;\n        return this;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static abstract class Base<T extends Connection.Base<T>> implements Connection.Base<T> {\n        private static final URL UnsetUrl; // only used if you created a new Request()\n        static {\n            try {\n                UnsetUrl = new URL(\"http://undefined/\");\n            } catch (MalformedURLException e) {\n                throw new IllegalStateException(e);\n            }\n        }\n\n        URL url = UnsetUrl;\n        Method method = Method.GET;\n        Map<String, List<String>> headers;\n        Map<String, String> cookies;\n\n        private Base() {\n            headers = new LinkedHashMap<>();\n            cookies = new LinkedHashMap<>();\n        }\n\n        private Base(Base<T> copy) {\n            url = copy.url; // unmodifiable object\n            method = copy.method;\n            headers = new LinkedHashMap<>();\n            for (Map.Entry<String, List<String>> entry : copy.headers.entrySet()) {\n                headers.put(entry.getKey(), new ArrayList<>(entry.getValue()));\n            }\n            cookies = new LinkedHashMap<>(); cookies.putAll(copy.cookies); // just holds strings\n        }\n\n        @Override\n        public URL url() {\n            if (url == UnsetUrl)\n                throw new IllegalArgumentException(\"URL not set. Make sure to call #url(...) before executing the request.\");\n            return url;\n        }\n\n        @Override\n        public T url(URL url) {\n            Validate.notNullParam(url, \"url\");\n            this.url = new UrlBuilder(url).build();\n            return (T) this;\n        }\n\n        @Override\n        public Method method() {\n            return method;\n        }\n\n        @Override\n        public T method(Method method) {\n            Validate.notNullParam(method, \"method\");\n            this.method = method;\n            return (T) this;\n        }\n\n        @Override @Nullable\n        public String header(String name) {\n            Validate.notNullParam(name, \"name\");\n            List<String> vals = getHeadersCaseInsensitive(name);\n            if (!vals.isEmpty()) {\n                // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\n                return StringUtil.join(vals, \", \");\n            }\n\n            return null;\n        }\n\n        @Override\n        public T addHeader(String name, @Nullable String value) {\n            Validate.notEmptyParam(name, \"name\");\n            value = value == null ? \"\" : value;\n\n            List<String> values = headers(name);\n            if (values.isEmpty()) {\n                values = new ArrayList<>();\n                headers.put(name, values);\n            }\n            values.add(value);\n\n            return (T) this;\n        }\n\n        @Override\n        public List<String> headers(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            return getHeadersCaseInsensitive(name);\n        }\n\n        @Override\n        public T header(String name, String value) {\n            Validate.notEmptyParam(name, \"name\");\n            removeHeader(name); // ensures we don't get an \"accept-encoding\" and an \"Accept-Encoding\"\n            addHeader(name, value);\n            return (T) this;\n        }\n\n        @Override\n        public boolean hasHeader(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            return !getHeadersCaseInsensitive(name).isEmpty();\n        }\n\n        /**\n         * Test if the request has a header with this value (case-insensitive).\n         */\n        @Override\n        public boolean hasHeaderWithValue(String name, String value) {\n            Validate.notEmpty(name);\n            Validate.notEmpty(value);\n            List<String> values = headers(name);\n            for (String candidate : values) {\n                if (value.equalsIgnoreCase(candidate))\n                    return true;\n            }\n            return false;\n        }\n\n        @Override\n        public T removeHeader(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            Map.Entry<String, List<String>> entry = scanHeaders(name); // remove is case-insensitive too\n            if (entry != null)\n                headers.remove(entry.getKey()); // ensures correct case\n            return (T) this;\n        }\n\n        @Override\n        public Map<String, String> headers() {\n            LinkedHashMap<String, String> map = new LinkedHashMap<>(headers.size());\n            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {\n                String header = entry.getKey();\n                List<String> values = entry.getValue();\n                if (!values.isEmpty())\n                    map.put(header, values.get(0));\n            }\n            return map;\n        }\n\n        @Override\n        public Map<String, List<String>> multiHeaders() {\n            return headers;\n        }\n\n        private List<String> getHeadersCaseInsensitive(String name) {\n            Validate.notNull(name);\n\n            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {\n                if (name.equalsIgnoreCase(entry.getKey()))\n                    return entry.getValue();\n            }\n\n            return Collections.emptyList();\n        }\n\n        private Map.@Nullable Entry<String, List<String>> scanHeaders(String name) {\n            String lc = lowerCase(name);\n            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {\n                if (lowerCase(entry.getKey()).equals(lc))\n                    return entry;\n            }\n            return null;\n        }\n\n        @Override\n        public String cookie(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            return cookies.get(name);\n        }\n\n        @Override\n        public T cookie(String name, String value) {\n            Validate.notEmptyParam(name, \"name\");\n            Validate.notNullParam(value, \"value\");\n            cookies.put(name, value);\n            return (T) this;\n        }\n\n        @Override\n        public boolean hasCookie(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            return cookies.containsKey(name);\n        }\n\n        @Override\n        public T removeCookie(String name) {\n            Validate.notEmptyParam(name, \"name\");\n            cookies.remove(name);\n            return (T) this;\n        }\n\n        @Override\n        public Map<String, String> cookies() {\n            return cookies;\n        }\n    }\n\n    public static class Request extends HttpConnection.Base<Connection.Request> implements Connection.Request {\n        static {\n            System.setProperty(\"sun.net.http.allowRestrictedHeaders\", \"true\");\n            // make sure that we can send Sec-Fetch-Site headers etc.\n        }\n\n        HttpConnection connection;\n        private @Nullable Proxy proxy;\n        private int timeoutMilliseconds;\n        private int maxBodySizeBytes;\n        private boolean followRedirects;\n        private final Collection<Connection.KeyVal> data;\n        private @Nullable Object body = null; // String or InputStream\n        @Nullable String mimeBoundary;\n        private boolean ignoreHttpErrors = false;\n        private boolean ignoreContentType = false;\n        private Parser parser;\n        private boolean parserDefined = false; // called parser(...) vs initialized in ctor\n        private String postDataCharset = DataUtil.defaultCharsetName;\n        private @Nullable SSLSocketFactory sslSocketFactory;\n        @Nullable SSLContext sslContext;\n        private CookieManager cookieManager;\n        @Nullable RequestAuthenticator authenticator;\n        private @Nullable Progress<Connection.Response> responseProgress;\n\n        private final ReentrantLock executing = new ReentrantLock(); // detects and warns if same request used concurrently\n\n        Request() {\n            super();\n            timeoutMilliseconds = 30000; // 30 seconds\n            maxBodySizeBytes = 1024 * 1024 * 2; // 2MB\n            followRedirects = true;\n            data = new ArrayList<>();\n            method = Method.GET;\n            addHeader(\"Accept-Encoding\", \"gzip\");\n            addHeader(USER_AGENT, DEFAULT_UA);\n            parser = Parser.htmlParser();\n            cookieManager = new CookieManager(); // creates a default InMemoryCookieStore\n        }\n\n        Request(Request copy) {\n            super(copy);\n            connection = copy.connection;\n            proxy = copy.proxy;\n            postDataCharset = copy.postDataCharset;\n            timeoutMilliseconds = copy.timeoutMilliseconds;\n            maxBodySizeBytes = copy.maxBodySizeBytes;\n            followRedirects = copy.followRedirects;\n            data = new ArrayList<>(); // data not copied\n            //body not copied\n            ignoreHttpErrors = copy.ignoreHttpErrors;\n            ignoreContentType = copy.ignoreContentType;\n            parser = copy.parser.newInstance(); // parsers and their tree-builders maintain state, so need a fresh copy\n            parserDefined = copy.parserDefined;\n            sslSocketFactory = copy.sslSocketFactory; // these are all synchronized so safe to share\n            sslContext = copy.sslContext;\n            cookieManager = copy.cookieManager;\n            authenticator = copy.authenticator;\n            responseProgress = copy.responseProgress;\n        }\n\n        @Override @Nullable\n        public Proxy proxy() {\n            return proxy;\n        }\n\n        @Override\n        public Request proxy(@Nullable Proxy proxy) {\n            this.proxy = proxy;\n            return this;\n        }\n\n        @Override\n        public Request proxy(String host, int port) {\n            this.proxy = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port));\n            return this;\n        }\n\n        @Override\n        public int timeout() {\n            return timeoutMilliseconds;\n        }\n\n        @Override\n        public Request timeout(int millis) {\n            Validate.isTrue(millis >= 0, \"Timeout milliseconds must be 0 (infinite) or greater\");\n            timeoutMilliseconds = millis;\n            return this;\n        }\n\n        @Override\n        public int maxBodySize() {\n            return maxBodySizeBytes;\n        }\n\n        @Override\n        public Connection.Request maxBodySize(int bytes) {\n            Validate.isTrue(bytes >= 0, \"maxSize must be 0 (unlimited) or larger\");\n            maxBodySizeBytes = bytes;\n            return this;\n        }\n\n        @Override\n        public boolean followRedirects() {\n            return followRedirects;\n        }\n\n        @Override\n        public Connection.Request followRedirects(boolean followRedirects) {\n            this.followRedirects = followRedirects;\n            return this;\n        }\n\n        @Override\n        public boolean ignoreHttpErrors() {\n            return ignoreHttpErrors;\n        }\n\n        @Override @Nullable\n        public SSLSocketFactory sslSocketFactory() {\n            return sslSocketFactory;\n        }\n\n        @Override\n        public void sslSocketFactory(SSLSocketFactory sslSocketFactory) {\n            this.sslSocketFactory = sslSocketFactory;\n        }\n\n        @Override @Nullable\n        public SSLContext sslContext() {\n            return sslContext;\n        }\n\n        @Override\n        public Connection.Request sslContext(SSLContext sslContext) {\n            this.sslContext = sslContext;\n            return this;\n        }\n\n        @Override\n        public Connection.Request ignoreHttpErrors(boolean ignoreHttpErrors) {\n            this.ignoreHttpErrors = ignoreHttpErrors;\n            return this;\n        }\n\n        @Override\n        public boolean ignoreContentType() {\n            return ignoreContentType;\n        }\n\n        @Override\n        public Connection.Request ignoreContentType(boolean ignoreContentType) {\n            this.ignoreContentType = ignoreContentType;\n            return this;\n        }\n\n        @Override\n        public Request data(Connection.KeyVal keyval) {\n            Validate.notNullParam(keyval, \"keyval\");\n            data.add(keyval);\n            return this;\n        }\n\n        @Override\n        public Collection<Connection.KeyVal> data() {\n            return data;\n        }\n\n        @Override\n        public Connection.Request requestBody(@Nullable String body) {\n            this.body = body;\n            return this;\n        }\n\n        @Override @Nullable\n        public String requestBody() {\n            return body instanceof String ? (String) body : null;\n        }\n\n        @Override\n        public Connection.Request requestBodyStream(InputStream stream) {\n            body = stream;\n            return this;\n        }\n\n        @Override\n        public Request parser(Parser parser) {\n            this.parser = parser;\n            parserDefined = true;\n            return this;\n        }\n\n        @Override\n        public Parser parser() {\n            return parser;\n        }\n\n        @Override\n        public Connection.Request postDataCharset(String charset) {\n            Validate.notNullParam(charset, \"charset\");\n            if (!Charset.isSupported(charset)) throw new IllegalCharsetNameException(charset);\n            this.postDataCharset = charset;\n            return this;\n        }\n\n        @Override\n        public String postDataCharset() {\n            return postDataCharset;\n        }\n\n        CookieManager cookieManager() {\n            return cookieManager;\n        }\n\n        @Override public Connection.Request auth(@Nullable RequestAuthenticator authenticator) {\n            this.authenticator = authenticator;\n            return this;\n        }\n\n        @Override @Nullable public RequestAuthenticator auth() {\n            return authenticator;\n        }\n    }\n\n    public static class Response extends HttpConnection.Base<Connection.Response> implements Connection.Response {\n        private static final int MAX_REDIRECTS = 20;\n        private static final String LOCATION = \"Location\";\n        int statusCode;\n        String statusMessage = \"\";\n        private @Nullable ByteBuffer byteData;\n        private @Nullable ControllableInputStream bodyStream;\n        @Nullable RequestExecutor executor;\n        private @Nullable String charset;\n        @Nullable String contentType;\n        int contentLength;\n        private boolean executed = false;\n        private boolean inputStreamRead = false;\n        private int numRedirects = 0;\n        private final HttpConnection.Request req;\n\n        /*\n         * Matches XML content types (like text/xml, image/svg+xml, application/xhtml+xml;charset=UTF8, etc)\n         */\n        private static final Pattern xmlContentTypeRxp = Pattern.compile(\"(\\\\w+)/\\\\w*\\\\+?xml.*\");\n\n        /**\n         <b>Internal only! </b>Creates a dummy HttpConnection.Response, useful for testing. All actual responses\n         are created from the HttpURLConnection and fields defined.\n         */\n        Response() {\n            super();\n            statusCode = 400;\n            statusMessage = \"Request not made\";\n            req = new Request();\n            contentType = null;\n        }\n\n        static Response execute(HttpConnection.Request req) throws IOException {\n            return execute(req, null);\n        }\n\n        static Response execute(HttpConnection.Request req, @Nullable Response prevRes) throws IOException {\n            Validate.isTrue(req.executing.tryLock(), \"Multiple threads were detected trying to execute the same request concurrently. Make sure to use Connection#newRequest() and do not share an executing request between threads.\");\n            Validate.notNullParam(req, \"req\");\n            URL url = req.url();\n            Validate.notNull(url, \"URL must be specified to connect\");\n            String protocol = url.getProtocol();\n            if (!protocol.equals(\"http\") && !protocol.equals(\"https\"))\n                throw new MalformedURLException(\"Only http & https protocols supported\");\n            final boolean supportsBody = req.method().hasBody();\n            final boolean hasBody = req.body != null;\n            if (!supportsBody)\n                Validate.isFalse(hasBody, \"Cannot set a request body for HTTP method \" + req.method());\n\n            // set up the request for execution\n            if (!req.data().isEmpty() && (!supportsBody || hasBody))\n                serialiseRequestUrl(req);\n            else if (supportsBody)\n                setOutputContentType(req);\n\n            long startTime = System.nanoTime();\n            RequestExecutor executor = RequestDispatch.get(req, prevRes);\n            Response res = null;\n            try {\n                res = executor.execute();\n\n                // redirect if there's a location header (from 3xx, or 201 etc)\n                if (res.hasHeader(LOCATION) && req.followRedirects()) {\n                    if (res.statusCode != HTTP_TEMP_REDIR) {\n                        req.method(Method.GET); // always redirect with a get. any data param from original req are dropped.\n                        req.data().clear();\n                        req.requestBody(null);\n                        req.removeHeader(CONTENT_TYPE);\n                    }\n\n                    String location = res.header(LOCATION);\n                    Validate.notNull(location);\n                    if (location.startsWith(\"http:/\") && location.charAt(6) != '/') // fix broken Location: http:/temp/AAG_New/en/index.php\n                        location = location.substring(6);\n                    URL redir = StringUtil.resolve(req.url(), location);\n                    req.url(redir);\n\n                    return execute(req, res);\n                }\n                if ((res.statusCode < 200 || res.statusCode >= 400) && !req.ignoreHttpErrors())\n                        throw new HttpStatusException(\"HTTP error fetching URL\", res.statusCode, req.url().toString());\n\n                // check that we can handle the returned content type; if not, abort before fetching it\n                String contentType = res.contentType();\n                if (contentType != null\n                        && !req.ignoreContentType()\n                        && !contentType.startsWith(\"text/\")\n                        && !xmlContentTypeRxp.matcher(contentType).matches()\n                        )\n                    throw new UnsupportedMimeTypeException(\"Unhandled content type. Must be text/*, */xml, or */*+xml\",\n                            contentType, req.url().toString());\n\n                // switch to the XML parser if content type is xml and not parser not explicitly set\n                if (contentType != null && xmlContentTypeRxp.matcher(contentType).matches()) {\n                    if (!req.parserDefined) req.parser(Parser.xmlParser());\n                }\n\n                res.charset = DataUtil.getCharsetFromContentType(res.contentType); // may be null, readInputStream deals with it\n                if (res.contentLength != 0 && req.method() != HEAD) { // -1 means unknown, chunked. sun throws an IO exception on 500 response with no content when trying to read body\n                    InputStream stream = executor.responseBody();\n                    if (res.hasHeaderWithValue(CONTENT_ENCODING, \"gzip\"))\n                        stream = new GZIPInputStream(stream);\n                    else if (res.hasHeaderWithValue(CONTENT_ENCODING, \"deflate\"))\n                        stream = new InflaterInputStream(stream, new Inflater(true));\n                    \n                    res.bodyStream = ControllableInputStream.wrap(\n                        stream, DefaultBufferSize, req.maxBodySize())\n                        .timeout(startTime, req.timeout());\n\n                    if (req.responseProgress != null) // set response progress listener\n                        res.bodyStream.onProgress(res.contentLength, req.responseProgress, res);\n                } else {\n                    res.byteData = DataUtil.emptyByteBuffer();\n                }\n            } catch (IOException e) {\n                if (res != null) res.safeClose(); // will be non-null if got to conn\n                throw e;\n            } finally {\n                req.executing.unlock();\n\n                // detach any thread local auth delegate\n                if (req.authenticator != null)\n                    AuthenticationHandler.handler.remove();\n            }\n\n            res.executed = true;\n            return res;\n        }\n\n        @Override\n        public int statusCode() {\n            return statusCode;\n        }\n\n        @Override\n        public String statusMessage() {\n            return statusMessage;\n        }\n\n        @Override @Nullable\n        public String charset() {\n            return charset;\n        }\n\n        @Override\n        public Response charset(String charset) {\n            this.charset = charset;\n            return this;\n        }\n\n        @Override @Nullable\n        public String contentType() {\n            return contentType;\n        }\n\n        /** Called from parse() or streamParser(), validates and prepares the input stream, and aligns common settings. */\n        private ControllableInputStream prepareParse() {\n            Validate.isTrue(executed, \"Request must be executed (with .execute(), .get(), or .post() before parsing response\");\n            ControllableInputStream stream = bodyStream;\n            if (byteData != null) { // bytes have been read in to the buffer, parse that\n                ByteArrayInputStream bytes = new ByteArrayInputStream(byteData.array(), 0, byteData.limit());\n                stream = ControllableInputStream.wrap(bytes, 0); // no max\n                inputStreamRead = false; // ok to reparse if in bytes\n            }\n            Validate.isFalse(inputStreamRead, \"Input stream already read and parsed, cannot re-read.\");\n            Validate.notNull(stream);\n            inputStreamRead = true;\n            return stream;\n        }\n\n        @Override public Document parse() throws IOException {\n            ControllableInputStream stream = prepareParse();\n            Document doc = DataUtil.parseInputStream(stream, charset, url.toExternalForm(), req.parser());\n            doc.connection(new HttpConnection(req, this)); // because we're static, don't have the connection obj. // todo - maybe hold in the req?\n            charset = doc.outputSettings().charset().name(); // update charset from meta-equiv, possibly\n            safeClose();\n            return doc;\n        }\n\n        @Override public StreamParser streamParser() throws IOException {\n            ControllableInputStream stream = prepareParse();\n            String baseUri = url.toExternalForm();\n            DataUtil.CharsetDoc charsetDoc = DataUtil.detectCharset(stream, charset, baseUri, req.parser());\n            // note that there may be a document in CharsetDoc as a result of scanning meta-data -- but as requires a stream parse, it is not used here. todo - revisit.\n\n            // set up the stream parser and rig this connection up to the parsed doc:\n            StreamParser streamer = new StreamParser(req.parser());\n            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charsetDoc.charset));\n            streamer.parse(reader, baseUri); // initializes the parse and the document, but does not step() it\n            streamer.document().connection(new HttpConnection(req, this));\n            charset = charsetDoc.charset.name();\n\n            // we don't safeClose() as in parse(); caller must close streamParser to close InputStream stream\n            return streamer;\n        }\n\n        /**\n         Reads the bodyStream into byteData. A no-op if already executed.\n         */\n        @Override\n        public Connection.Response readFully() throws IOException {\n            Validate.isTrue(executed, \"Request must be executed (with .execute(), .get(), or .post() before getting response body\");\n            if (bodyStream != null && byteData == null) {\n                Validate.isFalse(inputStreamRead, \"Request has already been read (with .parse())\");\n                try {\n                    byteData = DataUtil.readToByteBuffer(bodyStream, req.maxBodySize());\n                } finally {\n                    inputStreamRead = true;\n                    safeClose();\n                }\n            }\n            return this;\n        }\n\n        /**\n         Reads the body, but throws an UncheckedIOException if an IOException occurs.\n         @throws UncheckedIOException if an IOException occurs\n         */\n        private void readByteDataUnchecked() {\n            try {\n                readFully();\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            }\n        }\n\n        @Override\n        public String readBody() throws IOException {\n            readFully();\n            return body();\n        }\n\n        @Override\n        public String body() {\n            readByteDataUnchecked();\n            Validate.notNull(byteData);\n            // charset gets set from header on execute, and from meta-equiv on parse. parse may not have happened yet\n            String body = (charset == null ? UTF_8 : Charset.forName(charset))\n                .decode(byteData).toString();\n            ((Buffer)byteData).rewind(); // cast to avoid covariant return type change in jdk9\n            return body;\n        }\n\n        @Override\n        public byte[] bodyAsBytes() {\n            readByteDataUnchecked();\n            Validate.notNull(byteData);\n            Validate.isTrue(byteData.hasArray()); // we made it, so it should\n\n            byte[] array = byteData.array();\n            int offset = byteData.arrayOffset();\n            int length = byteData.limit();\n\n            if (offset == 0 && length == array.length) { // exact, just return it\n                return array;\n            } else { // trim to size\n                byte[] exactArray = new byte[length];\n                System.arraycopy(array, offset, exactArray, 0, length);\n                return exactArray;\n            }\n        }\n\n        @Override\n        public Connection.Response bufferUp() {\n            readByteDataUnchecked();\n            return this;\n        }\n\n        @Override\n        public BufferedInputStream bodyStream() {\n            Validate.isTrue(executed, \"Request must be executed (with .execute(), .get(), or .post() before getting response body\");\n\n            // if we have read to bytes (via readFully), return those as a stream.\n            if (byteData != null) {\n                return new BufferedInputStream(\n                    new ByteArrayInputStream(byteData.array(), 0, byteData.limit()),\n                    DefaultBufferSize);\n            }\n\n            Validate.isFalse(inputStreamRead, \"Request has already been read\");\n            Validate.notNull(bodyStream);\n            inputStreamRead = true;\n            return bodyStream.inputStream();\n        }\n\n        /**\n         * Call on completion of stream read, to close the body (or error) stream. The connection.disconnect allows\n         * keep-alives to work (as the underlying connection is actually held open, despite the name).\n         */\n        private void safeClose() {\n            if (bodyStream != null) {\n                try {\n                    bodyStream.close();\n                } catch (IOException e) {\n                    // no-op\n                } finally {\n                    bodyStream = null;\n                }\n            }\n\n            if (executor != null) executor.safeClose(); // disconnect\n        }\n\n        Response(HttpConnection.Request request) {\n            this.req = request;\n        }\n\n        // set up url, method, header, cookies\n        void prepareResponse(Map<String, List<String>> resHeaders, HttpConnection.@Nullable Response previousResponse) throws IOException {\n            processResponseHeaders(resHeaders); // includes cookie key/val read during header scan\n            CookieUtil.storeCookies(req, this, url, resHeaders); // add set cookies to cookie store\n\n            if (previousResponse != null) { // was redirected\n                // map previous response cookies into this response cookies() object\n                for (Map.Entry<String, String> prevCookie : previousResponse.cookies().entrySet()) {\n                    if (!hasCookie(prevCookie.getKey()))\n                        cookie(prevCookie.getKey(), prevCookie.getValue());\n                }\n                previousResponse.safeClose();\n\n                // enforce too many redirects:\n                numRedirects = previousResponse.numRedirects + 1;\n                if (numRedirects >= MAX_REDIRECTS)\n                    throw new IOException(String.format(\"Too many redirects occurred trying to load URL %s\", previousResponse.url()));\n            }\n        }\n\n        void processResponseHeaders(Map<String, List<String>> resHeaders) {\n            for (Map.Entry<String, List<String>> entry : resHeaders.entrySet()) {\n                String name = entry.getKey();\n                if (name == null)\n                    continue; // http/1.1 line\n\n                List<String> values = entry.getValue();\n                for (String value : values) {\n                    addHeader(name, fixHeaderEncoding(value));\n                }\n            }\n        }\n\n        /**\n         Servers may encode response headers in UTF-8 instead of RFC defined 8859. The JVM decodes the headers (before we see them) as 8859, which can lead to mojibake data.\n         <p>This method attempts to detect that and re-decode the string as UTF-8.</p>\n         <p>However on Android, the headers will be decoded as UTF8, so we can detect and pass those directly.</p>\n         * @param val a header value string that may have been incorrectly decoded as 8859.\n         * @return a potentially re-decoded string.\n         */\n        @Nullable\n        static String fixHeaderEncoding(@Nullable String val) {\n            if (val == null) return val;\n            // If we can't encode the string as 8859, then it couldn't have been decoded as 8859\n            if (!StandardCharsets.ISO_8859_1.newEncoder().canEncode(val))\n                return val;\n            byte[] bytes = val.getBytes(ISO_8859_1);\n            if (looksLikeUtf8(bytes))\n                return new String(bytes, UTF_8);\n            else\n                return val;\n        }\n\n        private static boolean looksLikeUtf8(byte[] input) {\n            int i = 0;\n            // BOM:\n            if (input.length >= 3\n                && (input[0] & 0xFF) == 0xEF\n                && (input[1] & 0xFF) == 0xBB\n                && (input[2] & 0xFF) == 0xBF) {\n                i = 3;\n            }\n\n            int end;\n            boolean foundNonAscii = false;\n            for (int j = input.length; i < j; ++i) {\n                int o = input[i];\n                if ((o & 0x80) == 0) {\n                    continue; // ASCII\n                }\n                foundNonAscii = true;\n\n                // UTF-8 leading:\n                if ((o & 0xE0) == 0xC0) {\n                    end = i + 1;\n                } else if ((o & 0xF0) == 0xE0) {\n                    end = i + 2;\n                } else if ((o & 0xF8) == 0xF0) {\n                    end = i + 3;\n                } else {\n                    return false;\n                }\n\n                if (end >= input.length)\n                    return false;\n\n                while (i < end) {\n                    i++;\n                    o = input[i];\n                    if ((o & 0xC0) != 0x80) {\n                        return false;\n                    }\n                }\n            }\n            return foundNonAscii;\n        }\n\n        private static void setOutputContentType(final HttpConnection.Request req) {\n            final String contentType = req.header(CONTENT_TYPE);\n            String bound = null;\n            if (contentType != null) {\n                // no-op; don't add content type as already set (e.g. for requestBody())\n                // todo - if content type already set, we could add charset\n\n                // if user has set content type to multipart/form-data, auto add boundary.\n                if(contentType.contains(MULTIPART_FORM_DATA) && !contentType.contains(\"boundary\")) {\n                    bound = DataUtil.mimeBoundary();\n                    req.header(CONTENT_TYPE, MULTIPART_FORM_DATA + \"; boundary=\" + bound);\n                }\n\n            }\n            else if (needsMultipart(req)) {\n                bound = DataUtil.mimeBoundary();\n                req.header(CONTENT_TYPE, MULTIPART_FORM_DATA + \"; boundary=\" + bound);\n            } else {\n                req.header(CONTENT_TYPE, FORM_URL_ENCODED + \"; charset=\" + req.postDataCharset());\n            }\n            req.mimeBoundary = bound;\n        }\n\n        static void writePost(final HttpConnection.Request req, final OutputStream outputStream) throws IOException {\n            try (OutputStreamWriter osw = new OutputStreamWriter(outputStream, req.postDataCharset());\n                 BufferedWriter w = new BufferedWriter(osw)) {\n                implWritePost(req, w, outputStream);\n            }\n        }\n\n        private static void implWritePost(final HttpConnection.Request req, final BufferedWriter w, final OutputStream outputStream) throws IOException {\n            final Collection<Connection.KeyVal> data = req.data();\n            final String boundary = req.mimeBoundary;\n\n            if (boundary != null) { // a multipart post\n                for (Connection.KeyVal keyVal : data) {\n                    w.write(\"--\");\n                    w.write(boundary);\n                    w.write(\"\\r\\n\");\n                    w.write(\"Content-Disposition: form-data; name=\\\"\");\n                    w.write(encodeMimeName(keyVal.key())); // encodes \" to %22\n                    w.write(\"\\\"\");\n                    final InputStream input = keyVal.inputStream();\n                    if (input != null) {\n                        w.write(\"; filename=\\\"\");\n                        w.write(encodeMimeName(keyVal.value()));\n                        w.write(\"\\\"\\r\\nContent-Type: \");\n                        String contentType = keyVal.contentType();\n                        w.write(contentType != null ? contentType : DefaultUploadType);\n                        w.write(\"\\r\\n\\r\\n\");\n                        w.flush();\n                        DataUtil.crossStreams(input, outputStream);\n                        outputStream.flush();\n                    } else {\n                        w.write(\"\\r\\n\\r\\n\");\n                        w.write(keyVal.value());\n                    }\n                    w.write(\"\\r\\n\");\n                }\n                w.write(\"--\");\n                w.write(boundary);\n                w.write(\"--\");\n            } else if (req.body != null) { // a single body (bytes or plain text);  data will be in query string\n                if (req.body instanceof String) {\n                    w.write((String) req.body);\n                } else if (req.body instanceof InputStream) {\n                    DataUtil.crossStreams((InputStream) req.body, outputStream);\n                    outputStream.flush();\n                } else {\n                    throw new IllegalStateException();\n                }\n            } else { // regular form data (application/x-www-form-urlencoded)\n                boolean first = true;\n                for (Connection.KeyVal keyVal : data) {\n                    if (!first) w.append('&');\n                    else first = false;\n\n                    w.write(URLEncoder.encode(keyVal.key(), req.postDataCharset()));\n                    w.write('=');\n                    w.write(URLEncoder.encode(keyVal.value(), req.postDataCharset()));\n                }\n            }\n        }\n\n        // for get url reqs, serialise the data map into the url\n        private static void serialiseRequestUrl(Connection.Request req) throws IOException {\n            UrlBuilder in = new UrlBuilder(req.url());\n\n            for (Connection.KeyVal keyVal : req.data()) {\n                Validate.isFalse(keyVal.hasInputStream(), \"InputStream data not supported in URL query string.\");\n                in.appendKeyVal(keyVal);\n            }\n            req.url(in.build());\n            req.data().clear(); // moved into url as get params\n        }\n    }\n\n    private static boolean needsMultipart(Connection.Request req) {\n        // multipart mode, for files. add the header if we see something with an inputstream, and return a non-null boundary\n        for (Connection.KeyVal keyVal : req.data()) {\n            if (keyVal.hasInputStream())\n                return true;\n        }\n        return false;\n    }\n\n    public static class KeyVal implements Connection.KeyVal {\n        private String key;\n        private String value;\n        private @Nullable InputStream stream;\n        private @Nullable String contentType;\n\n        public static KeyVal create(String key, String value) {\n            return new KeyVal(key, value);\n        }\n\n        public static KeyVal create(String key, String filename, InputStream stream) {\n            return new KeyVal(key, filename)\n                .inputStream(stream);\n        }\n\n        private KeyVal(String key, String value) {\n            Validate.notEmptyParam(key, \"key\");\n            Validate.notNullParam(value, \"value\");\n            this.key = key;\n            this.value = value;\n        }\n\n        @Override\n        public KeyVal key(String key) {\n            Validate.notEmptyParam(key, \"key\");\n            this.key = key;\n            return this;\n        }\n\n        @Override\n        public String key() {\n            return key;\n        }\n\n        @Override\n        public KeyVal value(String value) {\n            Validate.notNullParam(value, \"value\");\n            this.value = value;\n            return this;\n        }\n\n        @Override\n        public String value() {\n            return value;\n        }\n\n        @Override\n        public KeyVal inputStream(InputStream inputStream) {\n            Validate.notNullParam(value, \"inputStream\");\n            this.stream = inputStream;\n            return this;\n        }\n\n        @Override @Nullable\n        public InputStream inputStream() {\n            return stream;\n        }\n\n        @Override\n        public boolean hasInputStream() {\n            return stream != null;\n        }\n\n        @Override\n        public Connection.KeyVal contentType(String contentType) {\n            Validate.notEmpty(contentType);\n            this.contentType = contentType;\n            return this;\n        }\n\n        @Override @Nullable\n        public String contentType() {\n            return contentType;\n        }\n\n        @Override\n        public String toString() {\n            return key + \"=\" + value;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/Re2jRegex.java",
    "content": "package org.jsoup.helper;\n\n/**\n re2j-backed Regex implementation; must only be touched when re2j is on the classpath.\n */\nfinal class Re2jRegex extends Regex {\n    private static final java.util.regex.Pattern unused = java.util.regex.Pattern.compile(\"\");\n\n    private final com.google.re2j.Pattern re2jPattern;\n\n    private Re2jRegex(com.google.re2j.Pattern re2jPattern) {\n        super(unused);\n        this.re2jPattern = re2jPattern;\n    }\n\n    public static Regex compile(String regex) {\n        try {\n            return new Re2jRegex(com.google.re2j.Pattern.compile(regex));\n        } catch (RuntimeException e) {\n            throw new ValidationException(\"Pattern syntax error: \" + e.getMessage());\n        } catch (OutOfMemoryError | StackOverflowError e) { // defensive check on regex to normalize exception\n            throw new ValidationException(\"Pattern complexity error: \" + e.getMessage());\n        }\n    }\n\n    @Override\n    public Matcher matcher(CharSequence input) {\n        return new Re2jMatcher(re2jPattern.matcher(input));\n    }\n\n    @Override\n    public String toString() {\n        return re2jPattern.toString();\n    }\n\n    private static final class Re2jMatcher implements Matcher {\n        private final com.google.re2j.Matcher delegate;\n\n        Re2jMatcher(com.google.re2j.Matcher delegate) {\n            this.delegate = delegate;\n        }\n\n        @Override\n        public boolean find() {\n            return delegate.find();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/Regex.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.internal.SharedConstants;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\n\n/**\n A regular expression abstraction. Allows jsoup to optionally use the re2j regular expression engine (linear time)\n instead of the JDK's backtracking regex implementation.\n\n <p>If the {@code com.google.re2j} library is found on the classpath, by default it will be used. You can override this\n by setting {@code -Djsoup.useRe2j=false} to explicitly disable, and use the JDK regex engine.</p>\n\n <p>(Currently this a simplified implementation for jsoup's specific use; can extend as required.)</p>\n */\npublic class Regex {\n    private static final boolean hasRe2j = hasRe2j();\n\n    private final Pattern jdkPattern;\n\n    Regex(Pattern jdkPattern) {\n        this.jdkPattern = jdkPattern;\n    }\n\n    /**\n     Compile a regex, using re2j if enabled and available; otherwise JDK regex.\n\n     @param regex the regex to compile\n     @return the compiled regex\n     @throws ValidationException if the regex is invalid\n     */\n    public static Regex compile(String regex) {\n        if (usingRe2j()) {\n            return Re2jRegex.compile(regex);\n        }\n\n        try {\n            return new Regex(Pattern.compile(regex));\n        } catch (PatternSyntaxException e) {\n            throw new ValidationException(\"Pattern syntax error: \" + e.getMessage());\n        }\n    }\n\n    /** Wraps an existing JDK Pattern (for API compat); doesn't switch */\n    public static Regex fromPattern(Pattern pattern) {\n        return new Regex(pattern);\n    }\n\n    /**\n     Checks if re2j is available (on classpath) and enabled (via system property).\n     @return true if re2j is available and enabled\n     */\n    public static boolean usingRe2j() {\n        return hasRe2j && wantsRe2j();\n    }\n\n    static boolean wantsRe2j() {\n        return Boolean.parseBoolean(System.getProperty(SharedConstants.UseRe2j, \"true\"));\n    }\n\n    static void wantsRe2j(boolean use) {\n        System.setProperty(SharedConstants.UseRe2j, Boolean.toString(use));\n    }\n\n    static boolean hasRe2j() {\n        try {\n            Class<?> re2 = Class.forName(\"com.google.re2j.Pattern\", false, Regex.class.getClassLoader()); // check if re2j is in classpath\n            try {\n                // if it is, and we are on JVM9+, we need to dork around with modules, because re2j doesn't publish a module name.\n                // done via reflection so we can still run on JVM 8.\n                // todo remove if re2j publishes as a module\n                Class<?> moduleCls = Class.forName(\"java.lang.Module\");\n                Method getModule = Class.class.getMethod(\"getModule\");\n                Object jsoupMod = getModule.invoke(Regex.class);\n                Object re2Mod = getModule.invoke(re2);\n                boolean reads = (boolean) moduleCls.getMethod(\"canRead\", moduleCls).invoke(jsoupMod, re2Mod);\n                if (!reads) moduleCls.getMethod(\"addReads\", moduleCls).invoke(jsoupMod, re2Mod);\n            } catch (ClassNotFoundException ignore) {\n                // jvm8 - no Module class; so we can use as-is\n            }\n            return true;\n        } catch (ClassNotFoundException e) {\n            return false; // no re2j\n        } catch (ReflectiveOperationException e) {\n            // unexpectedly couldn’t wire modules on 9+; return false to avoid IllegalAccessError later\n            System.err.println(\"Warning: (bug? please report) couldn't access re2j from jsoup due to modules: \" + e);\n            return false;\n        }\n    }\n\n    public Matcher matcher(CharSequence input) {\n        return new JdkMatcher(jdkPattern.matcher(input));\n    }\n\n    @Override\n    public String toString() {\n        return jdkPattern.toString();\n    }\n\n    public interface Matcher {\n        boolean find();\n    }\n\n    private static final class JdkMatcher implements Matcher {\n        private final java.util.regex.Matcher delegate;\n\n        JdkMatcher(java.util.regex.Matcher delegate) {\n            this.delegate = delegate;\n        }\n\n        @Override\n        public boolean find() {\n            return delegate.find();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/RequestAuthenticator.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jspecify.annotations.Nullable;\n\nimport java.net.Authenticator;\nimport java.net.PasswordAuthentication;\nimport java.net.URL;\n\n/**\n A {@code RequestAuthenticator} is used in {@link Connection} to authenticate if required to proxies and web\n servers. See {@link Connection#auth(RequestAuthenticator)}.\n */\n@FunctionalInterface\npublic interface RequestAuthenticator {\n\n    /**\n     Provide authentication credentials for the provided Request Context.\n     * @param auth the request context including URL, type (Server or Proxy), and realm.\n     * @return credentials for the request. May return {@code null} if they are not applicable -- but the request will\n     * likely fail, as this method is only called if the request asked for authentication.\n     */\n    @Nullable\n    PasswordAuthentication authenticate(Context auth);\n\n    /**\n     Provides details for the request, to determine the appropriate credentials to return.\n     */\n    class Context {\n        private final URL url;\n        private final Authenticator.RequestorType type;\n        private final String realm;\n\n        Context(URL url, Authenticator.RequestorType type, String realm) {\n            this.url = url;\n            this.type = type;\n            this.realm = realm;\n        }\n\n        /**\n         Get the URL that is being requested.\n         * @return URL\n         */\n        public URL url() {\n            return url;\n        }\n\n        /**\n         Get the requestor type: {@link Authenticator.RequestorType#PROXY PROXY} if a proxy is requesting\n         authentication, or {@link Authenticator.RequestorType#SERVER SERVER} if the URL's server is requesting.\n         * @return requestor type\n         */\n        public Authenticator.RequestorType type() {\n            return type;\n        }\n\n        /**\n         Get the realm of the authentication request.\n         * @return realm of the authentication request\n         */\n        public String realm() {\n            return realm;\n        }\n\n        /**\n         Gets if the authentication request is for a proxy.\n         * @return true if type==proxy.\n         */\n        public boolean isProxy() {\n            return type == Authenticator.RequestorType.PROXY;\n        }\n\n        /**\n         Gets if the authentication request is for a server.\n         * @return true if type==server.\n         */\n        public boolean isServer() {\n            return type == Authenticator.RequestorType.SERVER;\n        }\n\n        /**\n         Helper method to return a PasswordAuthentication object.\n         * @param username username credential\n         * @param password password credential\n         * @return a constructed PasswordAuthentication\n         */\n        public PasswordAuthentication credentials(String username, String password) {\n            return new PasswordAuthentication(username, password.toCharArray());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/RequestDispatch.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.internal.SharedConstants;\nimport org.jspecify.annotations.Nullable;\n\nimport static org.jsoup.helper.HttpConnection.Request;\nimport static org.jsoup.helper.HttpConnection.Response;\n\nimport java.net.Proxy;\nimport java.lang.reflect.Constructor;\n\n/**\n Handles requests using either HttpClient (available in JVM 11+) or HttpURLConnection. During initialization, the\n HttpClientExecutor class is used if it can be instantiated, unless the system property\n {@link SharedConstants#UseHttpClient} is explicitly set to {@code false}.\n */\nclass RequestDispatch {\n\n    @Nullable\n    static Constructor<RequestExecutor> clientConstructor;\n\n    static {\n        try {\n            //noinspection unchecked\n            Class<RequestExecutor> httpClass =\n                (Class<RequestExecutor>) Class.forName(\"org.jsoup.helper.HttpClientExecutor\");\n            clientConstructor = httpClass.getConstructor(Request.class, Response.class);\n        } catch (Exception ignored) {\n            // either not on Java11+, or on Android; will provide UrlConnectionExecutor\n        }\n\n    }\n\n    static RequestExecutor get(Request request, @Nullable Response previousResponse) {\n        boolean useHttpClient = Boolean.parseBoolean(System.getProperty(SharedConstants.UseHttpClient, \"true\"));\n\n        if (request.sslSocketFactory() != null) // downgrade if a socket factory is set, as it can't be supplied to the HttpClient\n            useHttpClient = false;\n        Proxy proxy = request.proxy();\n        if (proxy != null && proxy.type() == Proxy.Type.SOCKS) // HttpClient doesn't support SOCKS proxies\n            useHttpClient = false;\n\n        if (useHttpClient && clientConstructor != null) {\n            try {\n                return clientConstructor.newInstance(request, previousResponse);\n            } catch (Exception e) {\n                return new UrlConnectionExecutor(request, previousResponse);\n            }\n        } else {\n            return new UrlConnectionExecutor(request, previousResponse);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/RequestExecutor.java",
    "content": "package org.jsoup.helper;\n\nimport static org.jsoup.helper.HttpConnection.Request;\nimport static org.jsoup.helper.HttpConnection.Response;\n\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n A shim interface to support both HttpURLConnection and HttpClient implementations, in a multi-version jar.\n */\nabstract class RequestExecutor {\n    final Request req;\n    final @Nullable Response prevRes;\n\n    RequestExecutor(Request request, @Nullable Response previousResponse) {\n        this.req = request;\n        this.prevRes = previousResponse;\n    }\n\n    abstract Response execute() throws IOException;\n\n    abstract InputStream responseBody() throws IOException;\n\n    abstract void safeClose();\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/UrlBuilder.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jsoup.internal.StringUtil;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.IDN;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\n\nimport static org.jsoup.helper.DataUtil.UTF_8;\n\n/**\n A utility class to normalize input URLs. jsoup internal; API subject to change.\n <p>Normalization includes puny-coding the host, and encoding non-ascii path components. Any non-ascii characters in\n the query string (or the fragment/anchor) are escaped, but any existing escapes in those components are preserved.</p>\n */\nfinal class UrlBuilder {\n    URL u;\n    @Nullable StringBuilder q;\n\n    UrlBuilder(URL inputUrl) {\n        this.u = inputUrl;\n        if (u.getQuery() != null)\n            q = StringUtil.borrowBuilder().append(u.getQuery());\n    }\n\n    URL build() {\n        try {\n            // use the URI class to encode non-ascii in path\n            URI uri = new URI(\n                u.getProtocol(),\n                u.getUserInfo(),\n                IDN.toASCII(decodePart(u.getHost())), // puny-code\n                u.getPort(),\n                null, null, null // path, query and fragment appended later so as not to encode\n            );\n\n            StringBuilder normUrl = StringUtil.borrowBuilder().append(uri.toASCIIString());\n            appendToAscii(u.getPath(), false, normUrl);\n            if (q != null) {\n                normUrl.append('?');\n                appendToAscii(StringUtil.releaseBuilder(q), true, normUrl);\n            }\n            if (u.getRef() != null) {\n                normUrl.append('#');\n                appendToAscii(u.getRef(), false, normUrl);\n            }\n            u = new URL(StringUtil.releaseBuilder(normUrl));\n            return u;\n        } catch (MalformedURLException | URISyntaxException | UnsupportedEncodingException e) {\n            // we assert here so that any incomplete normalization issues can be caught in devel. but in practise,\n            // the remote end will be able to handle it, so in prod we just pass the original URL.\n            // The UnsupportedEncodingException would never happen as always UTF8\n            assert Validate.assertFail(e.toString());\n            return u;\n        }\n    }\n\n    void appendKeyVal(Connection.KeyVal kv) throws UnsupportedEncodingException {\n        if (q == null)\n            q = StringUtil.borrowBuilder();\n        else\n            q.append('&');\n        q\n            .append(URLEncoder.encode(kv.key(), UTF_8.name()))\n            .append('=')\n            .append(URLEncoder.encode(kv.value(), UTF_8.name()));\n    }\n\n    private static String decodePart(String encoded) {\n        try {\n            return URLDecoder.decode(encoded, UTF_8.name());\n        } catch (UnsupportedEncodingException e) {\n            throw new RuntimeException(e); // wtf!\n        }\n    }\n\n    private static final String unsafeCharacters = \"<>\\\"{}|\\\\^[]`\";\n\n    private static void appendToAscii(String s, boolean spaceAsPlus, StringBuilder sb) throws UnsupportedEncodingException {\n        for (int i = 0; i < s.length(); i++) {\n            int c = s.codePointAt(i);\n            if (c == ' ') {\n                sb.append(spaceAsPlus ? '+' : \"%20\");\n            } else if (c == '%') { // if already a valid escape, pass; otherwise, escape\n                if (i < s.length() - 2 && isHex(s.charAt(i + 1)) && isHex(s.charAt(i + 2))) {\n                    sb.append('%').append(s.charAt(i + 1)).append(s.charAt(i + 2));\n                    i += 2; // skip the next two characters\n                } else {\n                    sb.append(\"%25\");\n                }\n            } else if (c > 127 || unsafeCharacters.indexOf(c) != -1) { // past ascii, or otherwise unsafe\n                sb.append(URLEncoder.encode(new String(Character.toChars(c)), UTF_8.name()));\n                if (Character.charCount(c) == 2) i++; // advance past supplemental\n            } else {\n                sb.append((char) c);\n            }\n        }\n    }\n\n    private static boolean isHex(char c) {\n        return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/UrlConnectionExecutor.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jspecify.annotations.Nullable;\n\nimport javax.net.ssl.HttpsURLConnection;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.Proxy;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jsoup.helper.HttpConnection.Response;\n\n/**\n Execute HTTP requests using the HttpURLConnection implementation. The HttpClient is used by default if available; set system property\n {@code jsoup.useHttpClient} to {@code false} to explicitly prefer the HttpUrlConnection.\n */\nclass UrlConnectionExecutor extends RequestExecutor {\n    @Nullable\n    HttpURLConnection conn;\n\n    UrlConnectionExecutor(HttpConnection.Request req, HttpConnection.@Nullable Response prevRes) {\n        super(req, prevRes);\n    }\n\n    @Override\n    HttpConnection.Response execute() throws IOException {\n        try {\n            conn = createConnection(req);\n            conn.connect();\n            if (conn.getDoOutput()) {\n                try (OutputStream out = conn.getOutputStream()) {\n                    Response.writePost(req, out);\n                } catch (IOException e) {\n                    conn.disconnect();\n                    throw e;\n                }\n            }\n\n            // set up url, method, header, cookies\n            Response res = new Response(req);\n            res.executor = this;\n            res.method = Connection.Method.valueOf(conn.getRequestMethod());\n            res.url = conn.getURL();\n            res.statusCode = conn.getResponseCode();\n            res.statusMessage = conn.getResponseMessage();\n            if (res.statusMessage == null) res.statusMessage = \"\"; // getResponseMessage may be null but statusMessage() is not null\n            res.contentType = conn.getContentType();\n            res.contentLength = conn.getContentLength();\n            Map<String, List<String>> resHeaders = createHeaderMap(conn);\n            res.prepareResponse(resHeaders, prevRes);\n\n            return res;\n        } catch (IOException e) {\n            safeClose();\n            throw e;\n        }\n    }\n\n    @Override\n    InputStream responseBody() throws IOException {\n        if (conn == null) throw new IllegalStateException(\"Not yet executed\");\n        return conn.getErrorStream() != null ? conn.getErrorStream() : conn.getInputStream();\n    }\n\n    @Override\n    void safeClose() {\n        if (conn != null) {\n            conn.disconnect();\n            conn = null;\n        }\n    }\n\n    // set up connection defaults, and details from request\n    private static HttpURLConnection createConnection(HttpConnection.Request req) throws IOException {\n        Proxy proxy = req.proxy();\n        final HttpURLConnection conn = (HttpURLConnection) (\n            proxy == null ?\n                req.url().openConnection() :\n                req.url().openConnection(proxy)\n        );\n\n        conn.setRequestMethod(req.method().name());\n        conn.setInstanceFollowRedirects(false); // don't rely on native redirection support\n        conn.setConnectTimeout(req.timeout());\n        conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read\n\n        if (conn instanceof HttpsURLConnection) {\n            HttpsURLConnection scon = (HttpsURLConnection) conn;\n            if (req.sslContext != null)\n                scon.setSSLSocketFactory(req.sslContext.getSocketFactory());\n            else if (req.sslSocketFactory() != null)\n                scon.setSSLSocketFactory(req.sslSocketFactory());\n        }\n        if (req.authenticator != null)\n            AuthenticationHandler.handler.enable(req.authenticator, conn); // removed in finally\n        if (req.method().hasBody())\n            conn.setDoOutput(true);\n        CookieUtil.applyCookiesToRequest(req, conn::addRequestProperty); // from the Request key/val cookies and the Cookie Store\n        for (Map.Entry<String, List<String>> header : req.multiHeaders().entrySet()) {\n            for (String value : header.getValue()) {\n                conn.addRequestProperty(header.getKey(), value);\n            }\n        }\n        return conn;\n    }\n\n    private static LinkedHashMap<String, List<String>> createHeaderMap(HttpURLConnection conn) {\n        // the default sun impl of conn.getHeaderFields() returns header values out of order\n        final LinkedHashMap<String, List<String>> headers = new LinkedHashMap<>();\n        int i = 0;\n        while (true) {\n            final String key = conn.getHeaderFieldKey(i);\n            final String val = conn.getHeaderField(i);\n            if (key == null && val == null)\n                break;\n            i++;\n            if (key == null || val == null)\n                continue; // skip http1.1 line\n\n            final List<String> vals = headers.computeIfAbsent(key, k -> new java.util.ArrayList<>());\n            vals.add(val);\n        }\n        return headers;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/Validate.java",
    "content": "package org.jsoup.helper;\n\nimport org.jspecify.annotations.Nullable;\n\n/**\n * Validators to check that method arguments meet expectations. \n */\npublic final class Validate {\n    \n    private Validate() {}\n\n    /**\n     * Validates that the object is not null\n     * @param obj object to test\n     * @throws ValidationException if the object is null\n     */\n    public static void notNull(@Nullable Object obj) {\n        if (obj == null)\n            throw new ValidationException(\"Object must not be null\");\n    }\n\n    /**\n     Validates that the parameter is not null\n\n     * @param obj the parameter to test\n     * @param param the name of the parameter, for presentation in the validation exception.\n     * @throws ValidationException if the object is null\n     */\n    public static void notNullParam(@Nullable final Object obj, final String param) {\n        if (obj == null)\n            throw new ValidationException(String.format(\"The parameter '%s' must not be null.\", param));\n    }\n\n    /**\n     * Validates that the object is not null\n     * @param obj object to test\n     * @param msg message to include in the Exception if validation fails\n     * @throws ValidationException if the object is null\n     */\n    public static void notNull(@Nullable Object obj, String msg) {\n        if (obj == null)\n            throw new ValidationException(msg);\n    }\n\n    /**\n     Verifies the input object is not null, and returns that object. Effectively this casts a nullable object to a non-\n     null object. (Works around lack of Objects.requestNonNull in Android version.)\n     * @param obj nullable object to cast to not-null\n     * @return the object, or throws an exception if it is null\n     * @throws ValidationException if the object is null\n     * @deprecated prefer to use {@link #expectNotNull(Object, String, Object...)} instead; will be removed in jsoup 1.24.1\n     */\n    @Deprecated\n    public static Object ensureNotNull(@Nullable Object obj) {\n        if (obj == null)\n            throw new ValidationException(\"Object must not be null\");\n        else return obj;\n    }\n\n    /**\n     Verifies the input object is not null, and returns that object. Effectively this casts a nullable object to a non-\n     null object. (Works around lack of Objects.requestNonNull in Android version.)\n     * @param obj nullable object to cast to not-null\n     * @param msg the String format message to include in the validation exception when thrown\n     * @param args the arguments to the msg\n     * @return the object, or throws an exception if it is null\n     * @throws ValidationException if the object is null\n     * @deprecated prefer to use {@link #expectNotNull(Object, String, Object...)} instead; will be removed in jsoup 1.24.1\n     */\n    @Deprecated\n    public static Object ensureNotNull(@Nullable Object obj, String msg, Object... args) {\n        if (obj == null)\n            throw new ValidationException(String.format(msg, args));\n        else return obj;\n    }\n\n    /**\n     Verifies the input object is not null, and returns that object, maintaining its type. Effectively this casts a\n     nullable object to a non-null object.\n\n     @param obj nullable object to cast to not-null\n     @return the object, or throws an exception if it is null\n     @throws ValidationException if the object is null\n     */\n    public static <T> T expectNotNull(@Nullable T obj) {\n        if (obj == null)\n            throw new ValidationException(\"Object must not be null\");\n        else return obj;\n    }\n\n    /**\n     Verifies the input object is not null, and returns that object, maintaining its type. Effectively this casts a\n     nullable object to a non-null object.\n\n     @param obj nullable object to cast to not-null\n     @param msg the String format message to include in the validation exception when thrown\n     @param args the arguments to the msg\n     @return the object, or throws an exception if it is null\n     @throws ValidationException if the object is null\n     */\n    public static <T> T expectNotNull(@Nullable T obj, String msg, Object... args) {\n        if (obj == null)\n            throw new ValidationException(String.format(msg, args));\n        else return obj;\n    }\n\n    /**\n     * Validates that the value is true\n     * @param val object to test\n     * @throws ValidationException if the object is not true\n     */\n    public static void isTrue(boolean val) {\n        if (!val)\n            throw new ValidationException(\"Must be true\");\n    }\n\n    /**\n     * Validates that the value is true\n     * @param val object to test\n     * @param msg message to include in the Exception if validation fails\n     * @throws ValidationException if the object is not true\n     */\n    public static void isTrue(boolean val, String msg) {\n        if (!val)\n            throw new ValidationException(msg);\n    }\n\n    /**\n     * Validates that the value is false\n     * @param val object to test\n     * @throws ValidationException if the object is not false\n     */\n    public static void isFalse(boolean val) {\n        if (val)\n            throw new ValidationException(\"Must be false\");\n    }\n\n    /**\n     * Validates that the value is false\n     * @param val object to test\n     * @param msg message to include in the Exception if validation fails\n     * @throws ValidationException if the object is not false\n     */\n    public static void isFalse(boolean val, String msg) {\n        if (val)\n            throw new ValidationException(msg);\n    }\n\n    /**\n     * Validates that the array contains no null elements\n     * @param objects the array to test\n     * @throws ValidationException if the array contains a null element\n     */\n    public static void noNullElements(Object[] objects) {\n        noNullElements(objects, \"Array must not contain any null objects\");\n    }\n\n    /**\n     * Validates that the array contains no null elements\n     * @param objects the array to test\n     * @param msg message to include in the Exception if validation fails\n     * @throws ValidationException if the array contains a null element\n     */\n    public static void noNullElements(Object[] objects, String msg) {\n        for (Object obj : objects)\n            if (obj == null)\n                throw new ValidationException(msg);\n    }\n\n    /**\n     * Validates that the string is not null and is not empty\n     * @param string the string to test\n     * @throws ValidationException if the string is null or empty\n     */\n    public static void notEmpty(@Nullable String string) {\n        if (string == null || string.length() == 0)\n            throw new ValidationException(\"String must not be empty\");\n    }\n\n    /**\n     Validates that the string parameter is not null and is not empty\n     * @param string the string to test\n     * @param param the name of the parameter, for presentation in the validation exception.\n     * @throws ValidationException if the string is null or empty\n     */\n    public static void notEmptyParam(@Nullable final String string, final String param) {\n        if (string == null || string.length() == 0)\n            throw new ValidationException(String.format(\"The '%s' parameter must not be empty.\", param));\n    }\n\n    /**\n     * Validates that the string is not null and is not empty\n     * @param string the string to test\n     * @param msg message to include in the Exception if validation fails\n     * @throws ValidationException if the string is null or empty\n     */\n    public static void notEmpty(@Nullable String string, String msg) {\n        if (string == null || string.length() == 0)\n            throw new ValidationException(msg);\n    }\n\n    /**\n     * Blow up if we reach an unexpected state.\n     * @param msg message to think about\n     * @throws IllegalStateException if we reach this state\n     */\n    public static void wtf(String msg) {\n        throw new IllegalStateException(msg);\n    }\n\n    /**\n     Cause a failure.\n     @param msg message to output.\n     @throws IllegalStateException if we reach this state\n     */\n    public static void fail(String msg) {\n        throw new ValidationException(msg);\n    }\n\n    /**\n     Cause a failure, but return false so it can be used in an assert statement.\n     @param msg message to output.\n     @return false, always\n     @throws IllegalStateException if we reach this state\n     */\n    static boolean assertFail(String msg) {\n        fail(msg);\n        return false;\n    }\n\n    /**\n     Cause a failure.\n     @param msg message to output.\n     @param args the format arguments to the msg\n     @throws IllegalStateException if we reach this state\n     */\n    public static void fail(String msg, Object... args) {\n        throw new ValidationException(String.format(msg, args));\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/ValidationException.java",
    "content": "package org.jsoup.helper;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n Validation exceptions, as thrown by the methods in {@link Validate}.\n */\npublic class ValidationException extends IllegalArgumentException {\n\n    public static final String Validator = Validate.class.getName();\n\n    public ValidationException(String msg) {\n        super(msg);\n    }\n\n    @Override\n    public synchronized Throwable fillInStackTrace() {\n        // Filters out the Validate class from the stacktrace, to more clearly point at the root-cause.\n\n        super.fillInStackTrace();\n\n        StackTraceElement[] stackTrace = getStackTrace();\n        List<StackTraceElement> filteredTrace = new ArrayList<>();\n        for (StackTraceElement trace : stackTrace) {\n            if (trace.getClassName().equals(Validator)) continue;\n            filteredTrace.add(trace);\n        }\n\n        setStackTrace(filteredTrace.toArray(new StackTraceElement[0]));\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/W3CDom.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.parser.HtmlTreeBuilder;\nimport org.jsoup.select.NodeVisitor;\nimport org.jsoup.select.Selector;\nimport org.w3c.dom.Comment;\nimport org.w3c.dom.DOMException;\nimport org.w3c.dom.DOMImplementation;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.DocumentType;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.w3c.dom.Text;\nimport org.jspecify.annotations.Nullable;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.transform.OutputKeys;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerException;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\nimport javax.xml.xpath.XPathConstants;\nimport javax.xml.xpath.XPathExpression;\nimport javax.xml.xpath.XPathExpressionException;\nimport javax.xml.xpath.XPathFactory;\nimport javax.xml.xpath.XPathFactoryConfigurationException;\nimport java.io.StringWriter;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport static javax.xml.transform.OutputKeys.METHOD;\nimport static org.jsoup.nodes.Document.OutputSettings.Syntax;\n\n/**\n * Helper class to transform a {@link org.jsoup.nodes.Document} to a {@link org.w3c.dom.Document org.w3c.dom.Document},\n * for integration with toolsets that use the W3C DOM.\n */\npublic class W3CDom {\n    /** For W3C Documents created by this class, this property is set on each node to link back to the original jsoup node. */\n    public static final String SourceProperty = \"jsoupSource\";\n    private static final String ContextProperty = \"jsoupContextSource\"; // tracks the jsoup context element on w3c doc\n    private static final String ContextNodeProperty = \"jsoupContextNode\"; // the w3c node used as the creating context\n\n    /**\n     To get support for XPath versions &gt; 1, set this property to the classname of an alternate XPathFactory\n     implementation. (For e.g. {@code net.sf.saxon.xpath.XPathFactoryImpl}).\n     */\n    public static final String XPathFactoryProperty = \"javax.xml.xpath.XPathFactory:jsoup\";\n\n    protected DocumentBuilderFactory factory;\n    private boolean namespaceAware = true; // false when using selectXpath, for user's query convenience\n\n    public W3CDom() {\n        factory = DocumentBuilderFactory.newInstance();\n        factory.setNamespaceAware(true);\n    }\n\n    /**\n     Returns if this W3C DOM is namespace aware. By default, this will be {@code true}, but is disabled for simplicity\n     when using XPath selectors in {@link org.jsoup.nodes.Element#selectXpath(String)}.\n     @return the current namespace aware setting.\n     */\n    public boolean namespaceAware() {\n        return namespaceAware;\n    }\n\n    /**\n     Update the namespace aware setting. This impacts the factory that is used to create W3C nodes from jsoup nodes.\n     <p>For HTML documents, controls if the document will be in the default {@code http://www.w3.org/1999/xhtml}\n     namespace if otherwise unset.</p>.\n     @param namespaceAware the updated setting\n     @return this W3CDom, for chaining.\n     */\n    public W3CDom namespaceAware(boolean namespaceAware) {\n        this.namespaceAware = namespaceAware;\n        factory.setNamespaceAware(namespaceAware);\n        return this;\n    }\n\n    /**\n     * Converts a jsoup DOM to a W3C DOM.\n     *\n     * @param in jsoup Document\n     * @return W3C Document\n     */\n    public static Document convert(org.jsoup.nodes.Document in) {\n        return (new W3CDom().fromJsoup(in));\n    }\n\n    /**\n     * Serialize a W3C document to a String. Provide Properties to define output settings including if HTML or XML. If\n     * you don't provide the properties ({@code null}), the output will be auto-detected based on the content of the\n     * document.\n     *\n     * @param doc Document\n     * @param properties (optional/nullable) the output properties to use. See {@link\n     *     Transformer#setOutputProperties(Properties)} and {@link OutputKeys}\n     * @return Document as string\n     * @see #OutputHtml\n     * @see #OutputXml\n     * @see OutputKeys#ENCODING\n     * @see OutputKeys#OMIT_XML_DECLARATION\n     * @see OutputKeys#STANDALONE\n     * @see OutputKeys#DOCTYPE_PUBLIC\n     * @see OutputKeys#CDATA_SECTION_ELEMENTS\n     * @see OutputKeys#INDENT\n     * @see OutputKeys#MEDIA_TYPE\n     */\n    public static String asString(Document doc, @Nullable Map<String, String> properties) {\n        try {\n            DOMSource domSource = new DOMSource(doc);\n            StringWriter writer = new StringWriter();\n            StreamResult result = new StreamResult(writer);\n            TransformerFactory tf = TransformerFactory.newInstance();\n            Transformer transformer = tf.newTransformer();\n            if (properties != null)\n                transformer.setOutputProperties(propertiesFromMap(properties));\n\n            if (doc.getDoctype() != null) {\n                DocumentType doctype = doc.getDoctype();\n                if (!StringUtil.isBlank(doctype.getPublicId()))\n                    transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctype.getPublicId());\n                if (!StringUtil.isBlank(doctype.getSystemId()))\n                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctype.getSystemId());\n                    // handle <!doctype html> for legacy dom.\n                else if (doctype.getName().equalsIgnoreCase(\"html\")\n                    && StringUtil.isBlank(doctype.getPublicId())\n                    && StringUtil.isBlank(doctype.getSystemId()))\n                    transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, \"about:legacy-compat\");\n            }\n\n            transformer.transform(domSource, result);\n            return writer.toString();\n\n        } catch (TransformerException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    static Properties propertiesFromMap(Map<String, String> map) {\n        Properties props = new Properties();\n        props.putAll(map);\n        return props;\n    }\n\n    /** Canned default for HTML output. */\n    public static HashMap<String, String> OutputHtml() {\n        return methodMap(\"html\");\n    }\n\n    /** Canned default for XML output. */\n    public static HashMap<String, String> OutputXml() {\n        return methodMap(\"xml\");\n    }\n\n    private static HashMap<String, String> methodMap(String method) {\n        HashMap<String, String> map = new HashMap<>();\n        map.put(METHOD, method);\n        return map;\n    }\n\n    /**\n     * Convert a jsoup Document to a W3C Document. The created nodes will link back to the original\n     * jsoup nodes in the user property {@link #SourceProperty} (but after conversion, changes on one side will not\n     * flow to the other).\n     *\n     * @param in jsoup doc\n     * @return a W3C DOM Document representing the jsoup Document or Element contents.\n     */\n    public Document fromJsoup(org.jsoup.nodes.Document in) {\n        // just method API backcompat\n        return fromJsoup((org.jsoup.nodes.Element) in);\n    }\n\n    /**\n     * Convert a jsoup DOM to a W3C Document. The created nodes will link back to the original\n     * jsoup nodes in the user property {@link #SourceProperty} (but after conversion, changes on one side will not\n     * flow to the other). The input Element is used as a context node, but the whole surrounding jsoup Document is\n     * converted. (If you just want a subtree converted, use {@link #convert(org.jsoup.nodes.Element, Document)}.)\n     *\n     * @param in jsoup element or doc\n     * @return a W3C DOM Document representing the jsoup Document or Element contents.\n     * @see #sourceNodes(NodeList, Class)\n     * @see #contextNode(Document)\n     */\n    public Document fromJsoup(org.jsoup.nodes.Element in) {\n        Validate.notNull(in);\n        DocumentBuilder builder;\n        try {\n            builder = factory.newDocumentBuilder();\n            DOMImplementation impl = builder.getDOMImplementation();\n            Document out = builder.newDocument();\n            org.jsoup.nodes.Document inDoc = in.ownerDocument();\n            org.jsoup.nodes.DocumentType doctype = inDoc != null ? inDoc.documentType() : null;\n            if (doctype != null) {\n                try {\n                    org.w3c.dom.DocumentType documentType = impl.createDocumentType(doctype.name(), doctype.publicId(), doctype.systemId());\n                    out.appendChild(documentType);\n                } catch (DOMException ignored) {\n                    // invalid / empty doctype dropped\n                }\n            }\n            out.setXmlStandalone(true);\n            // if in is Document, use the root element, not the wrapping document, as the context:\n            org.jsoup.nodes.Element context = (in instanceof org.jsoup.nodes.Document) ? in.firstElementChild() : in;\n            out.setUserData(ContextProperty, context, null);\n            convert(inDoc != null ? inDoc : in, out);\n            return out;\n        } catch (ParserConfigurationException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    /**\n     * Converts a jsoup document into the provided W3C Document. If required, you can set options on the output\n     * document before converting.\n     *\n     * @param in jsoup doc\n     * @param out w3c doc\n     * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Element)\n     */\n    public void convert(org.jsoup.nodes.Document in, Document out) {\n        // just provides method API backcompat\n        convert((org.jsoup.nodes.Element) in, out);\n    }\n\n    /**\n     * Converts a jsoup element into the provided W3C Document. If required, you can set options on the output\n     * document before converting.\n     *\n     * @param in jsoup element\n     * @param out w3c doc\n     * @see org.jsoup.helper.W3CDom#fromJsoup(org.jsoup.nodes.Element)\n     */\n    public void convert(org.jsoup.nodes.Element in, Document out) {\n        W3CBuilder builder = new W3CBuilder(out);\n        builder.namespaceAware = namespaceAware;\n        org.jsoup.nodes.Document inDoc = in.ownerDocument();\n        if (inDoc != null) {\n            if (!StringUtil.isBlank(inDoc.location())) {\n                out.setDocumentURI(inDoc.location());\n            }\n            builder.syntax = inDoc.outputSettings().syntax();\n        }\n        org.jsoup.nodes.Element rootEl = in instanceof org.jsoup.nodes.Document ? in.firstElementChild() : in; // skip the #root node if a Document\n        assert rootEl != null;\n        builder.traverse(rootEl);\n    }\n\n    /**\n     Evaluate an XPath query against the supplied document, and return the results.\n     @param xpath an XPath query\n     @param doc the document to evaluate against\n     @return the matches nodes\n     */\n    public NodeList selectXpath(String xpath, Document doc) {\n        return selectXpath(xpath, (Node) doc);\n    }\n\n    /**\n     Evaluate an XPath query against the supplied context node, and return the results.\n     @param xpath an XPath query\n     @param contextNode the context node to evaluate against\n     @return the matches nodes\n     */\n    public NodeList selectXpath(String xpath, Node contextNode) {\n        Validate.notEmptyParam(xpath, \"xpath\");\n        Validate.notNullParam(contextNode, \"contextNode\");\n\n        NodeList nodeList;\n        try {\n            // if there is a configured XPath factory, use that instead of the Java base impl:\n            String property = System.getProperty(XPathFactoryProperty);\n            final XPathFactory xPathFactory = property != null ?\n                XPathFactory.newInstance(\"jsoup\") :\n                XPathFactory.newInstance();\n\n            XPathExpression expression = xPathFactory.newXPath().compile(xpath);\n            nodeList = (NodeList) expression.evaluate(contextNode, XPathConstants.NODESET); // love the strong typing here /s\n            Validate.notNull(nodeList);\n        } catch (XPathExpressionException | XPathFactoryConfigurationException e) {\n            throw new Selector.SelectorParseException(\n                e, \"Could not evaluate XPath query [%s]: %s\", xpath, e.getMessage());\n        }\n        return nodeList;\n    }\n\n    /**\n     Retrieves the original jsoup DOM nodes from a nodelist created by this convertor.\n     @param nodeList the W3C nodes to get the original jsoup nodes from\n     @param nodeType the jsoup node type to retrieve (e.g. Element, DataNode, etc)\n     @param <T> node type\n     @return a list of the original nodes\n     */\n    public <T extends org.jsoup.nodes.Node> List<T> sourceNodes(NodeList nodeList, Class<T> nodeType) {\n        Validate.notNull(nodeList);\n        Validate.notNull(nodeType);\n        List<T> nodes = new ArrayList<>(nodeList.getLength());\n\n        for (int i = 0; i < nodeList.getLength(); i++) {\n            org.w3c.dom.Node node = nodeList.item(i);\n            Object source = node.getUserData(W3CDom.SourceProperty);\n            if (nodeType.isInstance(source))\n                nodes.add(nodeType.cast(source));\n        }\n\n        return nodes;\n    }\n\n    /**\n     For a Document created by {@link #fromJsoup(org.jsoup.nodes.Element)}, retrieves the W3C context node.\n     @param wDoc Document created by this class\n     @return the corresponding W3C Node to the jsoup Element that was used as the creating context.\n     */\n    public Node contextNode(Document wDoc) {\n        return (Node) wDoc.getUserData(ContextNodeProperty);\n    }\n\n    /**\n     * Serialize a W3C document that was created by {@link #fromJsoup(org.jsoup.nodes.Element)} to a String.\n     * The output format will be XML or HTML depending on the content of the doc.\n     *\n     * @param doc Document\n     * @return Document as string\n     * @see W3CDom#asString(Document, Map)\n     */\n    public String asString(Document doc) {\n        return asString(doc, null);\n    }\n\n    /**\n     * Implements the conversion by walking the input.\n     */\n    protected static class W3CBuilder implements NodeVisitor {\n        private final Document doc;\n        private boolean namespaceAware = true;\n        private Node dest;\n        private Syntax syntax = Syntax.xml; // the syntax (to coerce attributes to). From the input doc if available.\n        /*@Nullable*/ private final org.jsoup.nodes.Element contextElement; // todo - unsure why this can't be marked nullable?\n\n        public W3CBuilder(Document doc) {\n            this.doc = doc;\n            dest = doc;\n            contextElement = (org.jsoup.nodes.Element) doc.getUserData(ContextProperty); // Track the context jsoup Element, so we can save the corresponding w3c element\n        }\n\n        @Override\n        public void head(org.jsoup.nodes.Node source, int depth) {\n            if (source instanceof org.jsoup.nodes.Element) {\n                org.jsoup.nodes.Element sourceEl = (org.jsoup.nodes.Element) source;\n                String namespace = namespaceAware ? sourceEl.tag().namespace() : null;\n                String tagName = Normalizer.xmlSafeTagName(sourceEl.tagName());\n                try {\n                    // use an empty namespace if none is present but the tag name has a prefix\n                    String imputedNamespace = namespace == null && tagName.contains(\":\") ? \"\" : namespace;\n                    Element el = doc.createElementNS(imputedNamespace, tagName);\n                    copyAttributes(sourceEl, el);\n                    append(el, sourceEl);\n                    if (sourceEl == contextElement)\n                        doc.setUserData(ContextNodeProperty, el, null);\n                    dest = el; // descend\n                } catch (DOMException e) {\n                    // If the Normalize didn't get it XML / W3C safe, inserts as plain text\n                    append(doc.createTextNode(\"<\" + tagName + \">\"), sourceEl);\n                }\n            } else if (source instanceof org.jsoup.nodes.TextNode) {\n                org.jsoup.nodes.TextNode sourceText = (org.jsoup.nodes.TextNode) source;\n                Text text = doc.createTextNode(sourceText.getWholeText());\n                append(text, sourceText);\n            } else if (source instanceof org.jsoup.nodes.Comment) {\n                org.jsoup.nodes.Comment sourceComment = (org.jsoup.nodes.Comment) source;\n                Comment comment = doc.createComment(sourceComment.getData());\n                append(comment, sourceComment);\n            } else if (source instanceof org.jsoup.nodes.DataNode) {\n                org.jsoup.nodes.DataNode sourceData = (org.jsoup.nodes.DataNode) source;\n                Text node = doc.createTextNode(sourceData.getWholeData());\n                append(node, sourceData);\n            } else {\n                // unhandled. note that doctype is not handled here - rather it is used in the initial doc creation\n            }\n        }\n\n        private void append(Node append, org.jsoup.nodes.Node source) {\n            append.setUserData(SourceProperty, source, null);\n            dest.appendChild(append);\n        }\n\n        @Override\n        public void tail(org.jsoup.nodes.Node source, int depth) {\n            if (source instanceof org.jsoup.nodes.Element && dest.getParentNode() instanceof Element) {\n                dest = dest.getParentNode(); // undescend\n            }\n        }\n\n        private void copyAttributes(org.jsoup.nodes.Element jEl, Element wEl) {\n            for (Attribute attribute : jEl.attributes()) {\n                try {\n                    setAttribute(jEl, wEl, attribute, syntax);\n                } catch (DOMException e) {\n                    if (syntax != Syntax.xml)\n                        setAttribute(jEl, wEl, attribute, Syntax.xml);\n                }\n            }\n        }\n\n        private void setAttribute(org.jsoup.nodes.Element jEl, Element wEl, Attribute attribute, Syntax syntax) throws DOMException {\n            String key = Attribute.getValidKey(attribute.getKey(), syntax);\n            if (key != null) {\n                String namespace = attribute.namespace();\n                if (namespaceAware && !namespace.isEmpty())\n                    wEl.setAttributeNS(namespace, key, attribute.getValue());\n                else\n                    wEl.setAttribute(key, attribute.getValue());\n                maybeAddUndeclaredNs(namespace, key, jEl, wEl);\n            }\n        }\n\n        /**\n         Add a namespace declaration for an attribute with a prefix if it is not already present. Ensures that attributes\n         with prefixes have the corresponding namespace declared, E.g. attribute \"v-bind:foo\" gets another attribute\n         \"xmlns:v-bind='undefined'. So that the asString() transformation pass is valid.\n         If the parser was HTML we don't have a discovered namespace but we are trying to coerce it, so walk up the\n         element stack and find it.\n         */\n        private void maybeAddUndeclaredNs(String namespace, String attrKey, org.jsoup.nodes.Element jEl, Element wEl) {\n            if (!namespaceAware || !namespace.isEmpty()) return;\n            int pos = attrKey.indexOf(':');\n            if (pos != -1) { // prefixed but no namespace defined during parse, add a fake so that w3c serialization doesn't blow up\n                String prefix = attrKey.substring(0, pos);\n                if (prefix.equals(\"xmlns\")) return;\n                org.jsoup.nodes.Document doc = jEl.ownerDocument();\n                if (doc != null && doc.parser().getTreeBuilder() instanceof HtmlTreeBuilder) {\n                    // try walking up the stack and seeing if there is a namespace declared for this prefix (and that we didn't parse because HTML)\n                    for (org.jsoup.nodes.Element el = jEl; el != null; el = el.parent()) {\n                        String ns = el.attr(\"xmlns:\" + prefix);\n                        if (!ns.isEmpty()) {\n                            namespace = ns;\n                            // found it, set it\n                            wEl.setAttributeNS(namespace, attrKey, jEl.attr(attrKey));\n                            return;\n                        }\n                    }\n                }\n\n                // otherwise, put in a fake one\n                wEl.setAttribute(\"xmlns:\" + prefix, undefinedNs);\n            }\n        }\n        private static final String undefinedNs = \"undefined\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/helper/package-info.java",
    "content": "/**\n Package containing classes supporting the core jsoup code.\n */\n@NullMarked\npackage org.jsoup.helper;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/ControllableInputStream.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.Progress;\nimport org.jsoup.helper.Validate;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.BufferedInputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.SocketTimeoutException;\nimport java.nio.ByteBuffer;\n\nimport static org.jsoup.internal.SharedConstants.DefaultBufferSize;\n\n/**\n * A jsoup internal class (so don't use it as there is no contract API) that enables controls on a buffered input stream,\n * namely a maximum read size, and the ability to Thread.interrupt() the read.\n */\n// reimplemented from ConstrainableInputStream for JDK21 - extending BufferedInputStream will pin threads during read\npublic class ControllableInputStream extends FilterInputStream {\n    private final SimpleBufferedInput buff; // super.in, but typed as SimpleBufferedInput\n    private int maxSize;                    // logical cap exposed to callers (0 == unlimited)\n    private long startTime;                 // start time for timeout checks, nanos\n    private long timeout = 0;               // optional max time of request\n    private int remaining;                  // how many bytes may still be returned to caller under the current cap\n    private int markPos;                    // logical readPos snapshot for InputStream.mark/reset (not a buffer cursor)\n    private boolean interrupted;            // true if Thread.interrupted() was detected, used to latch interrupted state\n    private boolean allowClose = true;      // for cases where we want to re-read the input, can ignore .close() from the parser\n\n    // if we are tracking progress, will have the expected content length, progress callback, connection\n    private @Nullable Progress<?> progress;\n    private @Nullable Object progressContext;\n    private int contentLength = -1;         // expected content length for progress; -1 == unknown\n    private int readPos = 0;                // amount read; can be reset()\n\n    private ControllableInputStream(SimpleBufferedInput in, int maxSize) {\n        super(in);\n        Validate.isTrue(maxSize >= 0);\n        buff = in;\n        this.maxSize = maxSize;\n        remaining = maxSize;\n        markPos = -1;\n        startTime = System.nanoTime();\n    }\n\n    /**\n     * If this InputStream is not already a ControllableInputStream, let it be one.\n     * @param in the input stream to (maybe) wrap. A {@code null} input will create an empty wrapped stream.\n     * @param maxSize the maximum size to allow to be read. 0 == infinite.\n     * @return a controllable input stream\n     */\n    public static ControllableInputStream wrap(@Nullable InputStream in, int maxSize) {\n        // bufferSize currently unused; consider implementing as a min size in the SoftPool recycler\n        if (in instanceof ControllableInputStream)\n            return (ControllableInputStream) in;\n        else\n            return new ControllableInputStream(new SimpleBufferedInput(in), maxSize);\n    }\n\n    /**\n     * If this InputStream is not already a ControllableInputStream, let it be one.\n     * @param in the input stream to (maybe) wrap\n     * @param bufferSize the buffer size to use when reading\n     * @param maxSize the maximum size to allow to be read. 0 == infinite.\n     * @return a controllable input stream\n     */\n    public static ControllableInputStream wrap(InputStream in, int bufferSize, int maxSize) {\n        // todo - bufferSize currently unused; consider implementing as a min size in the SoftPool recycler; or just deprecate if always DefaultBufferSize\n        return wrap(in, maxSize);\n    }\n\n    @Override\n    public int read(byte[] b, int off, int len) throws IOException {\n        if (readPos == 0) emitProgress(); // emits a progress\n\n        boolean capped = maxSize != 0;\n        if (interrupted || capped && remaining <= 0)\n            return -1;\n        if (Thread.currentThread().isInterrupted()) {\n            // interrupted latches, because parse() may call twice\n            interrupted = true;\n            return -1;\n        }\n\n        if (capped && len > remaining)\n            len = remaining; // don't read more than desired, even if available\n        buff.capRemaining(capped ? remaining : Integer.MAX_VALUE);\n\n        while (true) { // loop trying to read until we get some data or hit the overall timeout, if we have one\n            if (expired())\n                throw new SocketTimeoutException(\"Read timeout\");\n\n            try {\n                final int read = super.read(b, off, len);\n                if (read == -1) { // completed\n                    contentLength = readPos;\n                } else {\n                    if (capped && read > 0) {\n                        remaining -= read; // track bytes returned to the caller\n                    }\n                    readPos += read;\n                }\n                emitProgress();\n                return read;\n            } catch (SocketTimeoutException e) {\n                if (expired() || timeout == 0)\n                    throw e;\n            }\n        }\n    }\n\n    @Override\n    public boolean markSupported() {\n        return true;\n    }\n\n    /**\n     * Reads this inputstream to a ByteBuffer. The supplied max may be less than the inputstream's max, to support\n     * reading just the first bytes.\n     */\n    public static ByteBuffer readToByteBuffer(InputStream in, int max) throws IOException {\n        Validate.isTrue(max >= 0, \"maxSize must be 0 (unlimited) or larger\");\n        Validate.notNull(in);\n        final boolean capped = max > 0;\n        final byte[] readBuf = SimpleBufferedInput.BufferPool.borrow(); // Share the same byte[] pool as SBI\n        final int outSize = capped ? Math.min(max, DefaultBufferSize) : DefaultBufferSize;\n        ByteBuffer outBuf = ByteBuffer.allocate(outSize);\n\n        try {\n            int remaining = max;\n            int read;\n            while ((read = in.read(readBuf, 0, capped ? Math.min(remaining, DefaultBufferSize) : DefaultBufferSize)) != -1) {\n                if (outBuf.remaining() < read) { // needs to grow\n                    int newCapacity = (int) Math.max(outBuf.capacity() * 1.5, outBuf.capacity() + read);\n                    ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity);\n                    outBuf.flip();\n                    newBuffer.put(outBuf);\n                    outBuf = newBuffer;\n                }\n                outBuf.put(readBuf, 0, read);\n                if (capped) {\n                    remaining -= read;\n                    if (remaining <= 0) break;\n                }\n            }\n            outBuf.flip(); // Prepare the buffer for reading\n            return outBuf;\n        } finally {\n            SimpleBufferedInput.BufferPool.release(readBuf);\n        }\n    }\n\n    @SuppressWarnings(\"NonSynchronizedMethodOverridesSynchronizedMethod\") // not synchronized in later JDKs\n    @Override public void reset() throws IOException {\n        if (markPos < 0) throw new IOException(\"Resetting to invalid mark\");\n        buff.rewindToMark();\n        buff.clearMark();\n        if (maxSize != 0) {\n            remaining = maxSize - markPos;\n            buff.capRemaining(remaining);\n        } else {\n            remaining = 0;\n            buff.capRemaining(Integer.MAX_VALUE);\n        }\n        readPos = markPos; // readPos is used for progress emits\n        markPos = -1;\n    }\n\n    @SuppressWarnings(\"NonSynchronizedMethodOverridesSynchronizedMethod\") // not synchronized in later JDKs\n    @Override public void mark(int readlimit) {\n        markPos = readPos;\n        buff.setMark();\n    }\n\n    /**\n     Check if the underlying InputStream has been read fully. There may still content in buffers to be consumed, and\n     read methods may return -1 if hit the read limit.\n     @return true if the underlying inputstream has been read fully.\n     */\n    public boolean baseReadFully() {\n        return buff.baseReadFully();\n    }\n\n    public void resetFullyRead() {\n        buff.resetFullyRead();\n    }\n\n    /**\n     Get the max size of this stream (how far at most will be read from the underlying stream)\n     * @return the max size\n     */\n    public int max() {\n        return maxSize;\n    }\n\n    public void max(int newMax) {\n        remaining += newMax - maxSize; // update remaining to reflect the difference in the new maxsize\n        if (remaining < 0) remaining = 0;\n        maxSize = newMax;\n        buff.capRemaining(newMax == 0 ? Integer.MAX_VALUE : remaining);\n    }\n\n    public void allowClose(boolean allowClose) {\n        this.allowClose = allowClose;\n    }\n\n    @Override public void close() throws IOException {\n        if (allowClose) super.close();\n    }\n\n    public ControllableInputStream timeout(long startTimeNanos, long timeoutMillis) {\n        this.startTime = startTimeNanos;\n        this.timeout = timeoutMillis * 1000000;\n        return this;\n    }\n\n    private void emitProgress() {\n        if (progress == null) return;\n        // calculate percent complete if contentLength > 0 (and cap to 100.0 if totalRead > contentLength):\n        float percent = contentLength > 0 ? Math.min(100f, readPos * 100f / contentLength) : 0;\n        //noinspection unchecked\n        ((Progress<Object>) progress).onProgress(readPos, contentLength, percent, progressContext); // (not actually unchecked - verified when set)\n        if (percent == 100.0f) progress = null; // detach once we reach 100%, so that any subsequent buffer hits don't report 100 again\n    }\n\n    public <ProgressContext> ControllableInputStream onProgress(int contentLength, Progress<ProgressContext> callback, ProgressContext context) {\n        Validate.notNull(callback);\n        Validate.notNull(context);\n        this.contentLength = contentLength;\n        this.progress = callback;\n        this.progressContext = context;\n        return this;\n    }\n\n    private boolean expired() {\n        if (timeout == 0)\n            return false;\n\n        final long now = System.nanoTime();\n        final long dur = now - startTime;\n        return (dur > timeout);\n    }\n\n    public BufferedInputStream inputStream() {\n        // called via HttpConnection.Response.bodyStream(), needs an OG BufferedInputStream\n        return new BufferedInputStream(buff);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/Functions.java",
    "content": "package org.jsoup.internal;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\n\n/**\n * An internal class containing functions for use with {@link Map#computeIfAbsent(Object, Function)}.\n * @deprecated for removal in jsoup 1.23.1. Replace usages with direct constructor references / lambdas.\n */\n@SuppressWarnings({\"rawtypes\", \"unchecked\"})\n@Deprecated\npublic final class Functions {\n    private static final Function ListFunction = key -> new ArrayList<>();\n    private static final Function SetFunction = key -> new HashSet<>();\n    private static final Function MapFunction = key -> new HashMap<>();\n    private static final Function IdentityMapFunction = key -> new IdentityHashMap<>();\n\n    private Functions() {\n    }\n\n    public static <T, U> Function<T, List<U>> listFunction() {\n        return (Function<T, List<U>>) ListFunction;\n    }\n\n    public static <T, U> Function<T, Set<U>> setFunction() {\n        return (Function<T, Set<U>>) SetFunction;\n    }\n\n    public static <T, K, V> Function<T, Map<K, V>> mapFunction() {\n        return (Function<T, Map<K, V>>) MapFunction;\n    }\n\n    public static <T, K, V> Function<T, IdentityHashMap<K, V>> identityMapFunction() {\n        return (Function<T, IdentityHashMap<K, V>>) IdentityMapFunction;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/Normalizer.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Document;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.Locale;\n\n/**\n * Util methods for normalizing strings. Jsoup internal use only, please don't depend on this API.\n */\npublic final class Normalizer {\n\n    /** Drops the input string to lower case. */\n    public static String lowerCase(final String input) {\n        return input != null ? input.toLowerCase(Locale.ROOT) : \"\";\n    }\n\n    /** Lower-cases and trims the input string. */\n    public static String normalize(final String input) {\n        return lowerCase(input).trim();\n    }\n\n    /**\n     If a string literal, just lower case the string; otherwise lower-case and trim.\n     @deprecated internal helper; replace with {@link #lowerCase(String)} for no-trim, or {@link #normalize(String)} for trim + lowercase.\n     Will be removed in jsoup 1.24.1.\n     */\n    @Deprecated\n    public static String normalize(final String input, boolean isStringLiteral) {\n        return isStringLiteral ? lowerCase(input) : normalize(input);\n    }\n\n    /** Minimal helper to get an otherwise OK HTML name like \"foo&lt;bar\" to \"foo_bar\". */\n    @Nullable public static String xmlSafeTagName(final String tagname) {\n        return Attribute.getValidKey(tagname, Document.OutputSettings.Syntax.xml); // Reuses the Attribute key normal, which is same for xml tag names\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/QuietAppendable.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.SerializationException;\n\nimport java.io.IOException;\n\n/**\n A jsoup internal class to wrap an Appendable and throw IOExceptions as SerializationExceptions.\n <p>Only implements the appendable methods we actually use.</p>\n */\npublic abstract class QuietAppendable {\n\n    public abstract QuietAppendable append(CharSequence csq);\n\n    public abstract QuietAppendable append(char c);\n\n    public abstract QuietAppendable append(char[] chars, int offset, int len); // via StringBuilder, not Appendable\n\n    static final class BaseAppendable extends QuietAppendable {\n        private final Appendable a;\n\n        @FunctionalInterface\n        private interface Action {\n            void append() throws IOException;\n        }\n\n        private BaseAppendable(Appendable appendable) {\n            this.a = appendable;\n        }\n\n        private BaseAppendable quiet(Action action) {\n            try {\n                action.append();\n            } catch (IOException e) {\n                throw new SerializationException(e);\n            }\n            return this;\n        }\n\n        @Override\n        public BaseAppendable append(CharSequence csq) {\n            return quiet(() -> a.append(csq));\n        }\n\n        @Override\n        public BaseAppendable append(char c) {\n            return quiet(() -> a.append(c));\n        }\n\n        @Override\n        public QuietAppendable append(char[] chars, int offset, int len) {\n            return quiet(() -> a.append(new String(chars, offset, len)));\n        }\n    }\n\n    /** A version that wraps a StringBuilder, and so doesn't need the exception wrap. */\n    static final class StringBuilderAppendable extends QuietAppendable {\n        private final StringBuilder sb;\n\n        private StringBuilderAppendable(StringBuilder sb) {\n            this.sb = sb;\n        }\n\n        @Override\n        public StringBuilderAppendable append(CharSequence csq) {\n            sb.append(csq);\n            return this;\n        }\n\n        @Override\n        public StringBuilderAppendable append(char c) {\n            sb.append(c);\n            return this;\n        }\n\n        @Override\n        public QuietAppendable append(char[] chars, int offset, int len) {\n            sb.append(chars, offset, len);\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            return sb.toString();\n        }\n    }\n\n    public static QuietAppendable wrap(Appendable a) {\n        if (a instanceof StringBuilder) return new StringBuilderAppendable((StringBuilder) a);\n        else                            return new BaseAppendable(a);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/SharedConstants.java",
    "content": "package org.jsoup.internal;\n\n/**\n jsoup constants used between packages. Do not use as they may change without warning. Users will not be able to see\n this package when modules are enabled.\n */\npublic final class SharedConstants {\n    public static final String UserDataKey = \"/jsoup.userdata\";\n    public final static String AttrRangeKey = \"jsoup.attrs\";\n    public static final String RangeKey = \"jsoup.start\";\n    public static final String EndRangeKey = \"jsoup.end\";\n    public static final String XmlnsAttr = \"jsoup.xmlns-\";\n\n    public static final int DefaultBufferSize = 8 * 1024;\n\n    public static final String[] FormSubmitTags = {\n        \"input\", \"keygen\", \"object\", \"select\", \"textarea\"\n    };\n\n    public static final String DummyUri = \"https://dummy.example/\"; // used as a base URI if none provided, to allow abs url resolution to preserve relative links\n\n    public static final String UseHttpClient = \"jsoup.useHttpClient\";\n\n    public static final String UseRe2j = \"jsoup.useRe2j\"; // enables use of the re2j regular expression engine when true and it's on the classpath\n\n    private SharedConstants() {}\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/SimpleBufferedInput.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.helper.Validate;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static org.jsoup.internal.SharedConstants.DefaultBufferSize;\n\n/**\n A simple implementation of a buffered input stream, in which we can control the byte[] buffer to recycle it. Not safe for\n use between threads; no sync or locks. The buffer is borrowed on initial demand in fill.\n @since 1.18.2\n */\nclass SimpleBufferedInput extends FilterInputStream {\n    static final int BufferSize = DefaultBufferSize;\n    static final SoftPool<byte[]> BufferPool = new SoftPool<>(() -> new byte[BufferSize]);\n    private int capRemaining = Integer.MAX_VALUE; // how many bytes we are allowed to pull from the underlying stream\n\n    private byte @Nullable [] byteBuf; // the byte buffer; recycled via SoftPool. Created in fill if required\n    private int bufPos;\n    private int bufLength;\n    private int bufMark = -1; // mark set by ControllableInputStream; -1 when unset\n    private boolean inReadFully = false; // true when the underlying inputstream has been read fully\n\n    SimpleBufferedInput(@Nullable InputStream in) {\n        super(in);\n        if (in == null) inReadFully = true; // effectively an empty stream\n    }\n\n    @Override\n    public int read() throws IOException {\n        if (bufPos >= bufLength) {\n            fill();\n            if (bufPos >= bufLength)\n                return -1;\n        }\n        return getBuf()[bufPos++] & 0xff;\n    }\n\n    @Override\n    public int read(byte[] dest, int offset, int desiredLen) throws IOException {\n        Validate.notNull(dest);\n        if (offset < 0 || desiredLen < 0 || desiredLen > dest.length - offset) {\n            throw new IndexOutOfBoundsException();\n        } else if (desiredLen == 0) {\n            return 0;\n        }\n\n        int bufAvail = bufLength - bufPos;\n        if (bufAvail <= 0) { // can't serve from the buffer\n            fill();\n            bufAvail = bufLength - bufPos;\n        }\n\n        int read = Math.min(bufAvail, desiredLen);\n        if (read <= 0) {\n            return -1;\n        }\n\n        System.arraycopy(getBuf(), bufPos, dest, offset, read);\n        bufPos += read;\n        return read;\n    }\n\n    private void fill() throws IOException {\n        if (inReadFully) return;\n        if (byteBuf == null) { // get one on first demand\n            byteBuf = BufferPool.borrow();\n        }\n\n        compact();\n        bufLength = bufPos;\n        int toRead = Math.min(byteBuf.length - bufPos, capRemaining);\n        if (toRead <= 0) return;\n        int read = in.read(byteBuf, bufPos, toRead);\n        if (read > 0) {\n            bufLength = read + bufPos;\n            capRemaining -= read;\n            while (byteBuf.length - bufLength > 0 && capRemaining > 0) { // read in more if we have space, without blocking\n                try {\n                    if (in.available() < 1) break;\n                } catch (IOException e) {\n                    break; // available() is advisory; keep the bytes we've already buffered\n                }\n                toRead = Math.min(byteBuf.length - bufLength, capRemaining);\n                if (toRead <= 0) break;\n                read = in.read(byteBuf, bufLength, toRead);\n                if (read <= 0) break;\n                bufLength += read;\n                capRemaining -= read;\n            }\n        }\n        if (read == -1) inReadFully = true;\n    }\n\n    byte[] getBuf() {\n        Validate.notNull(byteBuf);\n        return byteBuf;\n    }\n\n    /**\n     Check if the underlying InputStream has been read fully. There may still content in this buffer to be consumed.\n     @return true if the underlying inputstream has been read fully.\n     */\n    boolean baseReadFully() {\n        return inReadFully;\n    }\n\n    void resetFullyRead() {\n        if (in != null) // for null-wrapped streams, leave as fully read to avoid fill() on a null input\n            inReadFully = false;\n    }\n\n    @Override\n    public int available() throws IOException {\n        int buffered = (byteBuf != null) ? (bufLength - bufPos) : 0;\n        if (buffered > 0) {\n            return buffered; // doesn't include those in.available(), but mostly used as a block test\n        }\n        return inReadFully ? 0 : in.available();\n    }\n\n    void capRemaining(int newRemaining) {\n        capRemaining = Math.max(0, newRemaining);\n    }\n\n    void setMark() {\n        bufMark = bufPos;\n    }\n\n    void rewindToMark() throws IOException {\n        if (bufMark < 0)\n            throw new IOException(\"Resetting to invalid mark\");\n        bufPos = bufMark;\n    }\n\n    void clearMark() {\n        bufMark = -1;\n    }\n\n    private void compact() {\n        if (byteBuf == null || bufPos == 0) return;\n        int keepFrom = bufMark >= 0 ? bufMark : bufPos;\n        if (keepFrom <= 0) return;\n\n        int remaining = bufLength - keepFrom;\n        if (remaining > 0) {\n            System.arraycopy(byteBuf, keepFrom, byteBuf, 0, remaining);\n        }\n        bufLength = remaining;\n        bufPos -= keepFrom;\n        if (bufMark >= 0) {\n            bufMark -= keepFrom;\n        }\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (in != null) super.close();\n        if (byteBuf == null) return; // already closed, or never allocated\n        BufferPool.release(byteBuf); // return the buffer to the pool\n        byteBuf = null; // NPE further attempts to read\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/SimpleStreamReader.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.helper.Validate;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetDecoder;\nimport java.nio.charset.CoderResult;\nimport java.nio.charset.CodingErrorAction;\n\nimport static org.jsoup.internal.SimpleBufferedInput.BufferPool;\n\n/**\n A simple decoding InputStreamReader that recycles internal buffers.\n */\npublic class SimpleStreamReader extends Reader {\n    private final InputStream in;\n    private final CharsetDecoder decoder;\n    private @Nullable ByteBuffer byteBuf; // null after close\n\n    public SimpleStreamReader(InputStream in, Charset charset) {\n        this.in = in;\n        this.decoder = charset.newDecoder()\n            .onMalformedInput(CodingErrorAction.REPLACE)\n            .onUnmappableCharacter(CodingErrorAction.REPLACE);\n        byte[] buf = BufferPool.borrow(); // shared w/ SimpleBufferedInput, ControllableInput\n        byteBuf = ByteBuffer.wrap(buf);\n        byteBuf.flip(); // limit(0)\n    }\n\n    @Override\n    public int read(char[] charArray, int off, int len) throws IOException {\n        Validate.notNull(byteBuf); // can't read after close\n        CharBuffer charBuf = CharBuffer.wrap(charArray, off, len);\n        if (charBuf.position() != 0) charBuf = charBuf.slice();\n\n        boolean readFully = false;\n        while (true) {\n            CoderResult result = decoder.decode(byteBuf, charBuf, readFully);\n            if (result.isUnderflow()) {\n                if (readFully || !charBuf.hasRemaining() || (charBuf.position() > 0) && !hasAvailableBytes())\n                    break;\n                int read = bufferUp();\n                if (read < 0) {\n                    readFully = true;\n                    if ((charBuf.position() == 0) && (!byteBuf.hasRemaining()))\n                        break;\n                }\n                continue;\n            }\n            if (result.isOverflow()) break;\n            result.throwException();\n        }\n\n        if (readFully) decoder.reset();\n        if (charBuf.position() == 0) {\n            return readFully ? -1 : 0; // 0 if there was a surrogate and reader tried to read only 1.\n        }\n        return charBuf.position();\n    }\n\n    private boolean hasAvailableBytes() {\n        try {\n            return in.available() > 0;\n        } catch (IOException e) {\n            return false; // available() is advisory; a real read can still consume buffered bytes or reach EOF\n        }\n    }\n\n    private int bufferUp() throws IOException {\n        assert byteBuf != null; // already validated ^\n        byteBuf.compact();\n        try {\n            int pos = byteBuf.position();\n            int remaining = (byteBuf.limit() - pos);\n            int read = in.read(byteBuf.array(), byteBuf.arrayOffset() + pos, remaining);\n            if (read < 0) return read;\n            if (read == 0) throw new IOException(\"Underlying input stream returned zero bytes\");\n            byteBuf.position(pos + read);\n        } finally {\n            byteBuf.flip();\n        }\n        return byteBuf.remaining();\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (byteBuf == null) return;\n        BufferPool.release(byteBuf.array());\n        byteBuf = null;\n        in.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/SoftPool.java",
    "content": "package org.jsoup.internal;\n\nimport java.lang.ref.SoftReference;\nimport java.util.ArrayDeque;\nimport java.util.function.Supplier;\n\n/**\n A SoftPool is a ThreadLocal that holds a SoftReference to a pool of initializable objects. This allows us to reuse\n expensive objects (buffers, etc.) between invocations (the ThreadLocal), but also for those objects to be reaped if\n they are no longer in use.\n <p>Like a ThreadLocal, should be stored in a static field.</p>\n @param <T> the type of object to pool.\n @since 1.18.2\n */\npublic class SoftPool<T> {\n    final ThreadLocal<SoftReference<ArrayDeque<T>>> threadLocalStack;\n    private final Supplier<T> initializer;\n    /**\n     How many total uses of the creating object might be instantiated on the same thread at once. More than this and\n     those objects aren't recycled. Doesn't need to be too conservative, as they can still be GCed as SoftRefs.\n     */\n    static final int MaxIdle = 12;\n\n    /**\n     Create a new SoftPool.\n     @param initializer a supplier that creates a new object when one is needed.\n     */\n    public SoftPool(Supplier<T> initializer) {\n        this.initializer = initializer;\n        this.threadLocalStack = ThreadLocal.withInitial(() -> new SoftReference<>(new ArrayDeque<>()));\n    }\n\n    /**\n     Borrow an object from the pool, creating a new one if the pool is empty. Make sure to release it back to the pool\n     when done, so that it can be reused.\n     @return an object from the pool, as defined by the initializer.\n     */\n    public T borrow() {\n        ArrayDeque<T> stack = getStack();\n        if (!stack.isEmpty()) {\n            return stack.pop();\n        }\n        return initializer.get();\n    }\n\n    /**\n     Release an object back to the pool. If the pool is full, the object is not retained. If you don't want to reuse a\n     borrowed object (for e.g. a StringBuilder that grew too large), just don't release it.\n     @param value the object to release back to the pool.\n     */\n    public void release(T value) {\n        ArrayDeque<T> stack = getStack();\n        if (stack.size() < MaxIdle) {\n            stack.push(value);\n        }\n    }\n\n    ArrayDeque<T> getStack() {\n        ArrayDeque<T> stack = threadLocalStack.get().get();\n        if (stack == null) {\n            stack = new ArrayDeque<>();\n            threadLocalStack.set(new SoftReference<>(stack));\n        }\n        return stack;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/StringUtil.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.helper.Validate;\nimport org.jspecify.annotations.Nullable;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collector;\nimport java.util.stream.Collectors;\n\n/**\n A minimal String utility class. Designed for <b>internal</b> jsoup use only - the API and outcome may change without\n notice.\n */\npublic final class StringUtil {\n    // memoised padding up to 21 (blocks 0 to 20 spaces)\n    static final String[] padding = {\"\", \" \", \"  \", \"   \", \"    \", \"     \", \"      \", \"       \", \"        \",\n        \"         \", \"          \", \"           \", \"            \", \"             \", \"              \", \"               \",\n        \"                \", \"                 \", \"                  \", \"                   \", \"                    \"};\n\n    /**\n     * Join a collection of strings by a separator\n     * @param strings collection of string objects\n     * @param sep string to place between strings\n     * @return joined string\n     */\n    public static String join(Collection<?> strings, String sep) {\n        return join(strings.iterator(), sep);\n    }\n\n    /**\n     * Join a collection of strings by a separator\n     * @param strings iterator of string objects\n     * @param sep string to place between strings\n     * @return joined string\n     */\n    public static String join(Iterator<?> strings, String sep) {\n        if (!strings.hasNext())\n            return \"\";\n\n        String start = strings.next().toString();\n        if (!strings.hasNext()) // only one, avoid builder\n            return start;\n\n        StringJoiner j = new StringJoiner(sep);\n        j.add(start);\n        while (strings.hasNext()) {\n            j.add(strings.next());\n        }\n        return j.complete();\n    }\n\n    /**\n     * Join an array of strings by a separator\n     * @param strings collection of string objects\n     * @param sep string to place between strings\n     * @return joined string\n     */\n    public static String join(String[] strings, String sep) {\n        return join(Arrays.asList(strings), sep);\n    }\n\n    /**\n     A StringJoiner allows incremental / filtered joining of a set of stringable objects.\n     @since 1.14.1\n     */\n    public static class StringJoiner {\n        @Nullable StringBuilder sb = borrowBuilder(); // sets null on builder release so can't accidentally be reused\n        final String separator;\n        boolean first = true;\n\n        /**\n         Create a new joiner, that uses the specified separator. MUST call {@link #complete()} or will leak a thread\n         local string builder.\n\n         @param separator the token to insert between strings\n         */\n        public StringJoiner(String separator) {\n            this.separator = separator;\n        }\n\n        /**\n         Add another item to the joiner, will be separated\n         */\n        public StringJoiner add(Object stringy) {\n            Validate.notNull(sb); // don't reuse\n            if (!first)\n                sb.append(separator);\n            sb.append(stringy);\n            first = false;\n            return this;\n        }\n\n        /**\n         Append content to the current item; not separated\n         */\n        public StringJoiner append(Object stringy) {\n            Validate.notNull(sb); // don't reuse\n            sb.append(stringy);\n            return this;\n        }\n\n        /**\n         Return the joined string, and release the builder back to the pool. This joiner cannot be reused.\n         */\n        public String complete() {\n            String string = releaseBuilder(sb);\n            sb = null;\n            return string;\n        }\n    }\n\n    /**\n     * Returns space padding (up to the default max of 30). Use {@link #padding(int, int)} to specify a different limit.\n     * @param width amount of padding desired\n     * @return string of spaces * width\n     * @see #padding(int, int) \n      */\n    public static String padding(int width) {\n        return padding(width, 30);\n    }\n\n    /**\n     * Returns space padding, up to a max of maxPaddingWidth.\n     * @param width amount of padding desired\n     * @param maxPaddingWidth maximum padding to apply. Set to {@code -1} for unlimited.\n     * @return string of spaces * width\n     */\n    public static String padding(int width, int maxPaddingWidth) {\n        Validate.isTrue(width >= 0, \"width must be >= 0\");\n        Validate.isTrue(maxPaddingWidth >= -1);\n        if (maxPaddingWidth != -1)\n            width = Math.min(width, maxPaddingWidth);\n        if (width < padding.length)\n            return padding[width];        \n        char[] out = new char[width];\n        for (int i = 0; i < width; i++)\n            out[i] = ' ';\n        return String.valueOf(out);\n    }\n\n    /**\n     * Tests if a string is blank: null, empty, or only whitespace (\" \", \\r\\n, \\t, etc)\n     * @param string string to test\n     * @return if string is blank\n     */\n    public static boolean isBlank(@Nullable String string) {\n        if (string == null || string.isEmpty())\n            return true;\n\n        int l = string.length();\n        for (int i = 0; i < l; i++) {\n            if (!StringUtil.isWhitespace(string.codePointAt(i)))\n                return false;\n        }\n        return true;\n    }\n\n    /**\n     Tests if a string starts with a newline character\n     @param string string to test\n     @return if its first character is a newline\n     */\n    public static boolean startsWithNewline(final String string) {\n        if (string == null || string.length() == 0)\n            return false;\n        return string.charAt(0) == '\\n';\n    }\n\n    /**\n     * Tests if a string is numeric, i.e. contains only ASCII digit characters\n     * @param string string to test\n     * @return true if only digit chars, false if empty or null or contains non-digit chars\n     */\n    public static boolean isNumeric(String string) {\n        if (string == null || string.length() == 0)\n            return false;\n\n        int l = string.length();\n        for (int i = 0; i < l; i++) {\n            if (!isDigit(string.charAt(i)))\n                return false;\n        }\n        return true;\n    }\n\n    /**\n     * Tests if a code point is \"whitespace\" as defined in the HTML spec. Used for output HTML.\n     * @param c code point to test\n     * @return true if code point is whitespace, false otherwise\n     * @see #isActuallyWhitespace(int)\n     */\n    public static boolean isWhitespace(int c){\n        return c == ' ' || c == '\\t' || c == '\\n' || c == '\\f' || c == '\\r';\n    }\n\n    /**\n     * Tests if a code point is \"whitespace\" as defined by what it looks like. Used for Element.text etc.\n     * @param c code point to test\n     * @return true if code point is whitespace, false otherwise\n     */\n    public static boolean isActuallyWhitespace(int c){\n        return c == ' ' || c == '\\t' || c == '\\n' || c == '\\f' || c == '\\r' || c == 160;\n        // 160 is &nbsp; (non-breaking space). Not in the spec but expected.\n    }\n\n    public static boolean isInvisibleChar(int c) {\n        return c == 8203 || c == 173; // zero width sp, soft hyphen\n        // previously also included zw non join, zw join - but removing those breaks semantic meaning of text\n    }\n\n    /**\n     * Normalise the whitespace within this string; multiple spaces collapse to a single, and all whitespace characters\n     * (e.g. newline, tab) convert to a simple space.\n     * @param string content to normalise\n     * @return normalised string\n     */\n    public static String normaliseWhitespace(String string) {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        appendNormalisedWhitespace(sb, string, false);\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    /**\n     * After normalizing the whitespace within a string, appends it to a string builder.\n     * @param accum builder to append to\n     * @param string string to normalize whitespace within\n     * @param stripLeading set to true if you wish to remove any leading whitespace\n     */\n    public static void appendNormalisedWhitespace(StringBuilder accum, String string, boolean stripLeading) {\n        boolean lastWasWhite = false;\n        boolean reachedNonWhite = false;\n\n        int len = string.length();\n        int c;\n        for (int i = 0; i < len; i+= Character.charCount(c)) {\n            c = string.codePointAt(i);\n            if (isActuallyWhitespace(c)) {\n                if ((stripLeading && !reachedNonWhite) || lastWasWhite)\n                    continue;\n                accum.append(' ');\n                lastWasWhite = true;\n            }\n            else if (!isInvisibleChar(c)) {\n                accum.appendCodePoint(c);\n                lastWasWhite = false;\n                reachedNonWhite = true;\n            }\n        }\n    }\n\n    public static boolean in(final String needle, final String... haystack) {\n        final int len = haystack.length;\n        for (int i = 0; i < len; i++) {\n            if (haystack[i].equals(needle))\n               return true;\n        }\n        return false;\n    }\n\n    public static boolean inSorted(String needle, String[] haystack) {\n        return Arrays.binarySearch(haystack, needle) >= 0;\n    }\n\n    /**\n     Tests that a String contains only ASCII characters.\n     @param string scanned string\n     @return true if all characters are in range 0 - 127\n     */\n    public static boolean isAscii(String string) {\n        Validate.notNull(string);\n        for (int i = 0; i < string.length(); i++) {\n            int c = string.charAt(i);\n            if (c > 127) { // ascii range\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private static final Pattern extraDotSegmentsPattern = Pattern.compile(\"^/(?>(?>\\\\.\\\\.?/)+)\");\n    /**\n     * Create a new absolute URL, from a provided existing absolute URL and a relative URL component.\n     * @param base the existing absolute base URL\n     * @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned)\n     * @return the resolved absolute URL\n     * @throws MalformedURLException if an error occurred generating the URL\n     */\n    public static URL resolve(URL base, String relUrl) throws MalformedURLException {\n        relUrl = stripControlChars(relUrl);\n        // workaround: java resolves '//path/file + ?foo' to '//path/?foo', not '//path/file?foo' as desired\n        if (relUrl.startsWith(\"?\"))\n            relUrl = base.getPath() + relUrl;\n        // workaround: //example.com + ./foo = //example.com/./foo, not //example.com/foo\n        URL url = new URL(base, relUrl);\n        String fixedFile = extraDotSegmentsPattern.matcher(url.getFile()).replaceFirst(\"/\");\n        if (url.getRef() != null) {\n            fixedFile = fixedFile + \"#\" + url.getRef();\n        }\n        return new URL(url.getProtocol(), url.getHost(), url.getPort(), fixedFile);\n    }\n\n    /**\n     * Create a new absolute URL, from a provided existing absolute URL and a relative URL component.\n     * @param baseUrl the existing absolute base URL\n     * @param relUrl the relative URL to resolve. (If it's already absolute, it will be returned)\n     * @return an absolute URL if one was able to be generated, or the empty string if not\n     */\n    public static String resolve(String baseUrl, String relUrl) {\n        // workaround: java will allow control chars in a path URL and may treat as relative, but Chrome / Firefox will strip and may see as a scheme. Normalize to browser's view.\n        baseUrl = stripControlChars(baseUrl); relUrl = stripControlChars(relUrl);\n        try {\n            URL base;\n            try {\n                base = new URL(baseUrl);\n            } catch (MalformedURLException e) {\n                // the base is unsuitable, but the attribute/rel may be abs on its own, so try that\n                URL abs = new URL(relUrl);\n                return abs.toExternalForm();\n            }\n            return resolve(base, relUrl).toExternalForm();\n        } catch (MalformedURLException e) {\n            // it may still be valid, just that Java doesn't have a registered stream handler for it, e.g. tel\n            // we test here vs at start to normalize supported URLs (e.g. HTTP -> http)\n            return validUriScheme.matcher(relUrl).find() ? relUrl : \"\";\n        }\n    }\n    private static final Pattern validUriScheme = Pattern.compile(\"^[a-zA-Z][a-zA-Z0-9+-.]*:\");\n\n    private static final Pattern controlChars = Pattern.compile(\"[\\\\x00-\\\\x1f]*\"); // matches ascii 0 - 31, to strip from url\n    private static String stripControlChars(final String input) {\n        return controlChars.matcher(input).replaceAll(\"\");\n    }\n\n    private static final int InitBuilderSize = 1024;\n    private static final int MaxBuilderSize = 8 * 1024;\n    private static final SoftPool<StringBuilder> BuilderPool = new SoftPool<>(\n        () -> new StringBuilder(InitBuilderSize));\n\n    /**\n     * Maintains cached StringBuilders in a flyweight pattern, to minimize new StringBuilder GCs. The StringBuilder is\n     * prevented from growing too large.\n     * <p>\n     * Care must be taken to release the builder once its work has been completed, with {@link #releaseBuilder}\n     * @return an empty StringBuilder\n     */\n    public static StringBuilder borrowBuilder() {\n        return BuilderPool.borrow();\n    }\n\n    /**\n     * Release a borrowed builder. Care must be taken not to use the builder after it has been returned, as its\n     * contents may be changed by this method, or by a concurrent thread.\n     * @param sb the StringBuilder to release.\n     * @return the string value of the released String Builder (as an incentive to release it!).\n     */\n    public static String releaseBuilder(StringBuilder sb) {\n        Validate.notNull(sb);\n        String string = sb.toString();\n        releaseBuilderVoid(sb);\n        return string;\n    }\n\n    /**\n     Releases a borrowed builder, but does not call .toString() on it. Useful in case you already have that string.\n     @param sb the StringBuilder to release.\n     @see #releaseBuilder(StringBuilder)\n     */\n    public static void releaseBuilderVoid(StringBuilder sb) {\n        // if it hasn't grown too big, reset it and return it to the pool:\n        if (sb.length() <= MaxBuilderSize) {\n            sb.delete(0, sb.length()); // make sure it's emptied on release\n            BuilderPool.release(sb);\n        }\n    }\n\n    /**\n     * Return a {@link Collector} similar to the one returned by {@link Collectors#joining(CharSequence)},\n     * but backed by jsoup's {@link StringJoiner}, which allows for more efficient garbage collection.\n     *\n     * @param delimiter The delimiter for separating the strings.\n     * @return A {@code Collector} which concatenates CharSequence elements, separated by the specified delimiter\n     */\n    public static Collector<CharSequence, ?, String> joining(String delimiter) {\n        return Collector.of(() -> new StringJoiner(delimiter),\n            StringJoiner::add,\n            (j1, j2) -> {\n                j1.append(j2.complete());\n                return j1;\n            },\n            StringJoiner::complete);\n    }\n\n    public static boolean isAsciiLetter(char c) {\n        return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';\n    }\n\n    public static boolean isDigit(char c) {\n        return c >= '0' && c <= '9';\n    }\n\n    public static boolean isHexDigit(char c) {\n        return isDigit(c) || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F';\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/internal/package-info.java",
    "content": "/**\n * Util methods used by Jsoup. Please don't depend on the APIs implemented here as the contents may change without\n * notice.\n */\n@NullMarked\npackage org.jsoup.internal;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Attribute.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document.OutputSettings.Syntax;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\n\n/**\n A single key + value attribute. (Only used for presentation.)\n */\npublic class Attribute implements Map.Entry<String, String>, Cloneable  {\n    private static final String[] booleanAttributes = {\n            \"allowfullscreen\", \"async\", \"autofocus\", \"checked\", \"compact\", \"declare\", \"default\", \"defer\", \"disabled\",\n            \"formnovalidate\", \"hidden\", \"inert\", \"ismap\", \"itemscope\", \"multiple\", \"muted\", \"nohref\", \"noresize\",\n            \"noshade\", \"novalidate\", \"nowrap\", \"open\", \"readonly\", \"required\", \"reversed\", \"seamless\", \"selected\",\n            \"sortable\", \"truespeed\", \"typemustmatch\"\n    };\n\n    private String key;\n    @Nullable private String val;\n    @Nullable Attributes parent; // used to update the holding Attributes when the key / value is changed via this interface\n\n    /**\n     * Create a new attribute from unencoded (raw) key and value.\n     * @param key attribute key; case is preserved.\n     * @param value attribute value (may be null)\n     * @see #createFromEncoded\n     */\n    public Attribute(String key, @Nullable String value) {\n        this(key, value, null);\n    }\n\n    /**\n     * Create a new attribute from unencoded (raw) key and value.\n     * @param key attribute key; case is preserved.\n     * @param val attribute value (may be null)\n     * @param parent the containing Attributes (this Attribute is not automatically added to said Attributes)\n     * @see #createFromEncoded*/\n    public Attribute(String key, @Nullable String val, @Nullable Attributes parent) {\n        Validate.notNull(key);\n        key = key.trim();\n        Validate.notEmpty(key); // trimming could potentially make empty, so validate here\n        this.key = key;\n        this.val = val;\n        this.parent = parent;\n    }\n\n    /**\n     Get the attribute's key (aka name).\n     @return the attribute key\n     */\n    @Override\n    public String getKey() {\n        return key;\n    }\n\n    /**\n     Set the attribute key; case is preserved.\n     @param key the new key; must not be null\n     */\n    public void setKey(String key) {\n        Validate.notNull(key);\n        key = key.trim();\n        Validate.notEmpty(key); // trimming could potentially make empty, so validate here\n        if (parent != null) {\n            int i = parent.indexOfKey(this.key);\n            if (i != Attributes.NotFound) {\n                String oldKey = parent.keys[i];\n                parent.keys[i] = key;\n\n                // if tracking source positions, update the key in the range map\n                Map<String, Range.AttributeRange> ranges = parent.getRanges();\n                if (ranges != null) {\n                    Range.AttributeRange range = ranges.remove(oldKey);\n                    ranges.put(key, range);\n                }\n            }\n        }\n        this.key = key;\n    }\n\n    /**\n     Get the attribute value. Will return an empty string if the value is not set.\n     @return the attribute value\n     */\n    @Override\n    public String getValue() {\n        return Attributes.checkNotNull(val);\n    }\n\n    /**\n     * Check if this Attribute has a value. Set boolean attributes have no value.\n     * @return if this is a boolean attribute / attribute without a value\n     */\n    public boolean hasDeclaredValue() {\n        return val != null;\n    }\n\n    /**\n     Set the attribute value.\n     @param val the new attribute value; may be null (to set an enabled boolean attribute)\n     @return the previous value (if was null; an empty string)\n     */\n    @Override public String setValue(@Nullable String val) {\n        String oldVal = this.val;\n        if (parent != null) {\n            int i = parent.indexOfKey(this.key);\n            if (i != Attributes.NotFound) {\n                oldVal = parent.get(this.key); // trust the container more\n                parent.vals[i] = val;\n            }\n        }\n        this.val = val;\n        return Attributes.checkNotNull(oldVal);\n    }\n\n    /**\n     Get this attribute's key prefix, if it has one; else the empty string.\n     <p>For example, the attribute {@code og:title} has prefix {@code og}, and local {@code title}.</p>\n\n     @return the tag's prefix\n     @since 1.20.1\n     */\n    public String prefix() {\n        int pos = key.indexOf(':');\n        if (pos == -1) return \"\";\n        else return key.substring(0, pos);\n    }\n\n    /**\n     Get this attribute's local name. The local name is the name without the prefix (if any).\n     <p>For example, the attribute key {@code og:title} has local name {@code title}.</p>\n\n     @return the tag's local name\n     @since 1.20.1\n     */\n    public String localName() {\n        int pos = key.indexOf(':');\n        if (pos == -1) return key;\n        else return key.substring(pos + 1);\n    }\n\n    /**\n     Get this attribute's namespace URI, if the attribute was prefixed with a defined namespace name. Otherwise, returns\n     the empty string. These will only be defined if using the XML parser.\n     @return the tag's namespace URI, or empty string if not defined\n     @since 1.20.1\n     */\n    public String namespace() {\n        // set as el.attributes.userData(SharedConstants.XmlnsAttr + prefix, ns)\n        if (parent != null) {\n            String ns = (String) parent.userData(SharedConstants.XmlnsAttr + prefix());\n            if (ns != null)\n                return ns;\n        }\n        return \"\";\n    }\n\n    /**\n     Get the HTML representation of this attribute; e.g. {@code href=\"index.html\"}.\n     @return HTML\n     */\n    public String html() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        html(QuietAppendable.wrap(sb), new Document.OutputSettings());\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    /**\n     Get the source ranges (start to end positions) in the original input source from which this attribute's <b>name</b>\n     and <b>value</b> were parsed.\n     <p>Position tracking must be enabled prior to parsing the content.</p>\n     @return the ranges for the attribute's name and value, or {@code untracked} if the attribute does not exist or its range\n     was not tracked.\n     @see org.jsoup.parser.Parser#setTrackPosition(boolean)\n     @see Attributes#sourceRange(String)\n     @see Node#sourceRange()\n     @see Element#endSourceRange()\n     @since 1.17.1\n     */\n    public Range.AttributeRange sourceRange() {\n        if (parent == null) return Range.AttributeRange.UntrackedAttr;\n        return parent.sourceRange(key);\n    }\n\n    void html(QuietAppendable accum, Document.OutputSettings out) {\n        html(key, val, accum, out);\n    }\n\n    static void html(String key, @Nullable String val, QuietAppendable accum, Document.OutputSettings out) {\n        key = getValidKey(key, out.syntax());\n        if (key == null) return; // can't write it :(\n        htmlNoValidate(key, val, accum, out);\n    }\n\n    /** @deprecated internal method; use {@link #html(String, String, QuietAppendable, Document.OutputSettings)} with {@link org.jsoup.internal.QuietAppendable#wrap(Appendable)} instead. Will be removed in jsoup 1.24.1. */\n    @Deprecated\n    protected void html(Appendable accum, Document.OutputSettings out) throws IOException {\n        html(key, val, accum, out);\n    }\n\n    /** @deprecated internal method; use {@link #html(String, String, QuietAppendable, Document.OutputSettings)} with {@link org.jsoup.internal.QuietAppendable#wrap(Appendable)} instead. Will be removed in jsoup 1.24.1. */\n    @Deprecated\n    protected static void html(String key, @Nullable String val, Appendable accum, Document.OutputSettings out) throws IOException {\n        html(key, val, QuietAppendable.wrap(accum), out);\n    }\n\n    static void htmlNoValidate(String key, @Nullable String val, QuietAppendable accum, Document.OutputSettings out) {\n        // structured like this so that Attributes can check we can write first, so it can add whitespace correctly\n        accum.append(key);\n        if (!shouldCollapseAttribute(key, val, out)) {\n            accum.append(\"=\\\"\");\n            Entities.escape(accum, Attributes.checkNotNull(val), out, Entities.ForAttribute); // preserves whitespace\n            accum.append('\"');\n        }\n    }\n\n    private static final Pattern xmlKeyReplace = Pattern.compile(\"[^-a-zA-Z0-9_:.]+\");\n    private static final Pattern htmlKeyReplace = Pattern.compile(\"[\\\\x00-\\\\x1f\\\\x7f-\\\\x9f \\\"'/=]+\");\n    /**\n     * Get a valid attribute key for the given syntax. If the key is not valid, it will be coerced into a valid key.\n     * @param key the original attribute key\n     * @param syntax HTML or XML\n     * @return the original key if it's valid; a key with invalid characters replaced with \"_\" otherwise; or null if a valid key could not be created.\n     */\n    @Nullable public static String getValidKey(String key, Syntax syntax) {\n        if (syntax == Syntax.xml && !isValidXmlKey(key)) {\n            key = xmlKeyReplace.matcher(key).replaceAll(\"_\");\n            return isValidXmlKey(key) ? key : null; // null if could not be coerced\n        }\n        else if (syntax == Syntax.html && !isValidHtmlKey(key)) {\n            key = htmlKeyReplace.matcher(key).replaceAll(\"_\");\n            return isValidHtmlKey(key) ? key : null; // null if could not be coerced\n        }\n        return key;\n    }\n\n    // perf critical in html() so using manual scan vs regex:\n    // note that we aren't using anything in supplemental space, so OK to iter charAt\n    private static boolean isValidXmlKey(String key) {\n        // =~ [a-zA-Z_:][-a-zA-Z0-9_:.]*\n        final int length = key.length();\n        if (length == 0) return false;\n        char c = key.charAt(0);\n        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':'))\n            return false;\n        for (int i = 1; i < length; i++) {\n            c = key.charAt(i);\n            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == ':' || c == '.'))\n                return false;\n        }\n        return true;\n    }\n\n    private static boolean isValidHtmlKey(String key) {\n        // =~ [\\x00-\\x1f\\x7f-\\x9f \"'/=]+\n        final int length = key.length();\n        if (length == 0) return false;\n        for (int i = 0; i < length; i++) {\n            char c = key.charAt(i);\n            if ((c <= 0x1f) || (c >= 0x7f && c <= 0x9f) || c == ' ' || c == '\"' || c == '\\'' || c == '/' || c == '=')\n                return false;\n        }\n        return true;\n    }\n\n    /**\n     Get the string representation of this attribute, implemented as {@link #html()}.\n     @return string\n     */\n    @Override\n    public String toString() {\n        return html();\n    }\n\n    /**\n     * Create a new Attribute from an unencoded key and a HTML attribute encoded value.\n     * @param unencodedKey assumes the key is not encoded, as can be only run of simple \\w chars.\n     * @param encodedValue HTML attribute encoded value\n     * @return attribute\n     */\n    public static Attribute createFromEncoded(String unencodedKey, String encodedValue) {\n        String value = Entities.unescape(encodedValue, true);\n        return new Attribute(unencodedKey, value, null); // parent will get set when Put\n    }\n\n    protected boolean isDataAttribute() {\n        return isDataAttribute(key);\n    }\n\n    protected static boolean isDataAttribute(String key) {\n        return key.startsWith(Attributes.dataPrefix) && key.length() > Attributes.dataPrefix.length();\n    }\n\n    /**\n     * Collapsible if it's a boolean attribute and value is empty or same as name\n     * \n     * @param out output settings\n     * @return  Returns whether collapsible or not\n     * @deprecated internal method; use {@link #shouldCollapseAttribute(String, String, Document.OutputSettings)} instead. Will be removed in jsoup 1.24.1.\n     */\n    @Deprecated\n    protected final boolean shouldCollapseAttribute(Document.OutputSettings out) {\n        return shouldCollapseAttribute(key, val, out);\n    }\n\n    // collapse unknown foo=null, known checked=null, checked=\"\", checked=checked; write out others\n    protected static boolean shouldCollapseAttribute(final String key, @Nullable final String val, final Document.OutputSettings out) {\n        return (out.syntax() == Syntax.html &&\n                (val == null || (val.isEmpty() || val.equalsIgnoreCase(key)) && Attribute.isBooleanAttribute(key)));\n    }\n\n    /**\n     * Checks if this attribute name is defined as a boolean attribute in HTML5\n     */\n    public static boolean isBooleanAttribute(final String key) {\n        return Arrays.binarySearch(booleanAttributes, Normalizer.lowerCase(key)) >= 0;\n    }\n\n    @Override\n    public boolean equals(@Nullable Object o) { // note parent not considered\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        Attribute attribute = (Attribute) o;\n        return Objects.equals(key, attribute.key) && Objects.equals(val, attribute.val);\n    }\n\n    @Override\n    public int hashCode() { // note parent not considered\n        return Objects.hash(key, val);\n    }\n\n    @Override\n    public Attribute clone() {\n        try {\n            return (Attribute) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Attributes.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.ParseSettings;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.AbstractMap;\nimport java.util.AbstractSet;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.ConcurrentModificationException;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport static org.jsoup.internal.Normalizer.lowerCase;\nimport static org.jsoup.internal.SharedConstants.AttrRangeKey;\nimport static org.jsoup.nodes.Range.AttributeRange.UntrackedAttr;\n\n/**\n * The attributes of an Element.\n * <p>\n * During parsing, attributes in with the same name in an element are deduplicated, according to the configured parser's\n * attribute case-sensitive setting. It is possible to have duplicate attributes subsequently if\n * {@link #add(String, String)} vs {@link #put(String, String)} is used.\n * </p>\n * <p>\n * Attribute name and value comparisons are generally <b>case sensitive</b>. By default for HTML, attribute names are\n * normalized to lower-case on parsing. That means you should use lower-case strings when referring to attributes by\n * name.\n * </p>\n *\n * @author Jonathan Hedley, jonathan@hedley.net\n */\npublic class Attributes implements Iterable<Attribute>, Cloneable {\n    // The Attributes object is only created on the first use of an attribute; the Element will just have a null\n    // Attribute slot otherwise\n\n    static final char InternalPrefix = '/'; // Indicates an internal key. Can't be set via HTML. (It could be set via accessor, but not too worried about that. Suppressed from list, iter, size.)\n    protected static final String dataPrefix = \"data-\"; // data attributes\n    private static final String EmptyString = \"\";\n\n    // manages the key/val arrays\n    private static final int InitialCapacity = 3; // sampling found mean count when attrs present = 1.49; 1.08 overall. 2.6:1 don't have any attrs.\n    private static final int GrowthFactor = 2;\n    static final int NotFound = -1;\n\n    // the number of instance fields is kept as low as possible giving an object size of 24 bytes\n    int size = 0; // number of slots used (not total capacity, which is keys.length). Package visible for actual size (incl internal)\n    @Nullable String[] keys = new String[InitialCapacity]; // keys is not null, but contents may be. Same for vals\n    @Nullable Object[] vals = new Object[InitialCapacity]; // Genericish: all non-internal attribute values must be Strings and are cast on access.\n    // todo - make keys iterable without creating Attribute objects\n\n    // check there's room for more\n    private void checkCapacity(int minNewSize) {\n        Validate.isTrue(minNewSize >= size);\n        int curCap = keys.length;\n        if (curCap >= minNewSize)\n            return;\n        int newCap = curCap >= InitialCapacity ? size * GrowthFactor : InitialCapacity;\n        if (minNewSize > newCap)\n            newCap = minNewSize;\n\n        keys = Arrays.copyOf(keys, newCap);\n        vals = Arrays.copyOf(vals, newCap);\n    }\n\n    int indexOfKey(String key) {\n        Validate.notNull(key);\n        for (int i = 0; i < size; i++) {\n            if (key.equals(keys[i]))\n                return i;\n        }\n        return NotFound;\n    }\n\n    private int indexOfKeyIgnoreCase(String key) {\n        Validate.notNull(key);\n        for (int i = 0; i < size; i++) {\n            if (key.equalsIgnoreCase(keys[i]))\n                return i;\n        }\n        return NotFound;\n    }\n\n    // we track boolean attributes as null in values - they're just keys. so returns empty for consumers\n    // casts to String, so only for non-internal attributes\n    static String checkNotNull(@Nullable Object val) {\n        return val == null ? EmptyString : (String) val;\n    }\n\n    /**\n     Get an attribute value by key.\n     @param key the (case-sensitive) attribute key\n     @return the attribute value if set; or empty string if not set (or a boolean attribute).\n     @see #hasKey(String)\n     */\n    public String get(String key) {\n        int i = indexOfKey(key);\n        return i == NotFound ? EmptyString : checkNotNull(vals[i]);\n    }\n\n    /**\n     Get an Attribute by key. The Attribute will remain connected to these Attributes, so changes made via\n     {@link Attribute#setKey(String)}, {@link Attribute#setValue(String)} etc will cascade back to these Attributes and\n     their owning Element.\n     @param key the (case-sensitive) attribute key\n     @return the Attribute for this key, or null if not present.\n     @since 1.17.2\n     */\n    @Nullable public Attribute attribute(String key) {\n        int i = indexOfKey(key);\n        return i == NotFound ? null : new Attribute(key, checkNotNull(vals[i]), this);\n    }\n\n    /**\n     * Get an attribute's value by case-insensitive key\n     * @param key the attribute name\n     * @return the first matching attribute value if set; or empty string if not set (ora boolean attribute).\n     */\n    public String getIgnoreCase(String key) {\n        int i = indexOfKeyIgnoreCase(key);\n        return i == NotFound ? EmptyString : checkNotNull(vals[i]);\n    }\n\n    /**\n     * Adds a new attribute. Will produce duplicates if the key already exists.\n     * @see Attributes#put(String, String)\n     */\n    public Attributes add(String key, @Nullable String value) {\n        addObject(key, value);\n        return this;\n    }\n\n    private void addObject(String key, @Nullable Object value) {\n        checkCapacity(size + 1);\n        keys[size] = key;\n        vals[size] = value;\n        size++;\n    }\n\n    /**\n     * Set a new attribute, or replace an existing one by key.\n     * @param key case sensitive attribute key (not null)\n     * @param value attribute value (which can be null, to set a true boolean attribute)\n     * @return these attributes, for chaining\n     */\n    public Attributes put(String key, @Nullable String value) {\n        Validate.notNull(key);\n        int i = indexOfKey(key);\n        if (i != NotFound)\n            vals[i] = value;\n        else\n            addObject(key, value);\n        return this;\n    }\n\n    /**\n     Get the map holding any user-data associated with these Attributes. Will be created empty on first use. Held as\n     an internal attribute, not a field member, to reduce the memory footprint of Attributes when not used. Can hold\n     arbitrary objects; use for source ranges, connecting W3C nodes to Elements, etc.\n     * @return the map holding user-data\n     */\n    Map<String, Object> userData() {\n        final Map<String, Object> userData;\n        int i = indexOfKey(SharedConstants.UserDataKey);\n        if (i == NotFound) {\n            userData = new HashMap<>();\n            addObject(SharedConstants.UserDataKey, userData);\n        } else {\n            //noinspection unchecked\n            userData = (Map<String, Object>) vals[i];\n        }\n        assert userData != null;\n        return userData;\n    }\n\n    /**\n     Check if these attributes have any user data associated with them.\n     */\n    boolean hasUserData() {\n        return hasKey(SharedConstants.UserDataKey);\n    }\n\n    /**\n     Get an arbitrary user-data object by key.\n     * @param key case-sensitive key to the object.\n     * @return the object associated to this key, or {@code null} if not found.\n     * @see #userData(String key, Object val)\n     * @since 1.17.1\n     */\n    @Nullable\n    public Object userData(String key) {\n        Validate.notNull(key);\n        if (!hasUserData()) return null; // no user data exists\n        Map<String, Object> userData = userData();\n        return userData.get(key);\n    }\n\n    /**\n     Set an arbitrary user-data object by key. Will be treated as an internal attribute, so will not be emitted in HTML.\n     * @param key case-sensitive key\n     * @param value object value. Providing a {@code null} value has the effect of removing the key from the userData map.\n     * @return these attributes\n     * @see #userData(String key)\n     * @since 1.17.1\n     */\n    public Attributes userData(String key, @Nullable Object value) {\n        Validate.notNull(key);\n        if (value == null && !hasKey(SharedConstants.UserDataKey)) return this; // no user data exists, so short-circuit\n        Map<String, Object> userData = userData();\n        if (value == null)  userData.remove(key);\n        else                userData.put(key, value);\n        return this;\n    }\n\n    void putIgnoreCase(String key, @Nullable String value) {\n        int i = indexOfKeyIgnoreCase(key);\n        if (i != NotFound) {\n            vals[i] = value;\n            String old = keys[i];\n            assert old != null;\n            if (!old.equals(key)) // case changed, update\n                keys[i] = key;\n        }\n        else\n            addObject(key, value);\n    }\n\n    /**\n     * Set a new boolean attribute. Removes the attribute if the value is false.\n     * @param key case <b>insensitive</b> attribute key\n     * @param value attribute value\n     * @return these attributes, for chaining\n     */\n    public Attributes put(String key, boolean value) {\n        if (value)\n            putIgnoreCase(key, null);\n        else\n            remove(key);\n        return this;\n    }\n\n    /**\n     Set a new attribute, or replace an existing one by key.\n     @param attribute attribute with case-sensitive key\n     @return these attributes, for chaining\n     */\n    public Attributes put(Attribute attribute) {\n        Validate.notNull(attribute);\n        put(attribute.getKey(), attribute.getValue());\n        attribute.parent = this;\n        return this;\n    }\n\n    // removes and shifts up\n    @SuppressWarnings(\"AssignmentToNull\")\n    private void remove(int index) {\n        Validate.isFalse(index >= size);\n        int shifted = size - index - 1;\n        if (shifted > 0) {\n            System.arraycopy(keys, index + 1, keys, index, shifted);\n            System.arraycopy(vals, index + 1, vals, index, shifted);\n        }\n        size--;\n        keys[size] = null; // release hold\n        vals[size] = null;\n    }\n\n    /**\n     Remove an attribute by key. <b>Case sensitive.</b>\n     @param key attribute key to remove\n     */\n    public void remove(String key) {\n        int i = indexOfKey(key);\n        if (i != NotFound)\n            remove(i);\n    }\n\n    /**\n     Remove an attribute by key. <b>Case insensitive.</b>\n     @param key attribute key to remove\n     */\n    public void removeIgnoreCase(String key) {\n        int i = indexOfKeyIgnoreCase(key);\n        if (i != NotFound)\n            remove(i);\n    }\n\n    /**\n     Tests if these attributes contain an attribute with this key.\n     @param key case-sensitive key to check for\n     @return true if key exists, false otherwise\n     */\n    public boolean hasKey(String key) {\n        return indexOfKey(key) != NotFound;\n    }\n\n    /**\n     Tests if these attributes contain an attribute with this key.\n     @param key key to check for\n     @return true if key exists, false otherwise\n     */\n    public boolean hasKeyIgnoreCase(String key) {\n        return indexOfKeyIgnoreCase(key) != NotFound;\n    }\n\n    /**\n     * Check if these attributes contain an attribute with a value for this key.\n     * @param key key to check for\n     * @return true if key exists, and it has a value\n     */\n    public boolean hasDeclaredValueForKey(String key) {\n        int i = indexOfKey(key);\n        return i != NotFound && vals[i] != null;\n    }\n\n    /**\n     * Check if these attributes contain an attribute with a value for this key.\n     * @param key case-insensitive key to check for\n     * @return true if key exists, and it has a value\n     */\n    public boolean hasDeclaredValueForKeyIgnoreCase(String key) {\n        int i = indexOfKeyIgnoreCase(key);\n        return i != NotFound && vals[i] != null;\n    }\n\n    /**\n     Get the number of attributes in this set, excluding any internal-only attributes (e.g. user data).\n     <p>Internal attributes are excluded from the {@link #html()}, {@link #asList()}, and {@link #iterator()}\n     methods.</p>\n\n     @return size\n     */\n    public int size() {\n        if (size == 0) return 0;\n        int count = 0;\n        for (int i = 0; i < size; i++) {\n            if (!isInternalKey(keys[i]))  count++;\n        }\n        return count;\n    }\n\n    /**\n     Test if this Attributes list is empty.\n     <p>This does not include internal attributes, such as user data.</p>\n     */\n    public boolean isEmpty() {\n        return size() == 0;\n    }\n\n    /**\n     Add all the attributes from the incoming set to this set.\n     @param incoming attributes to add to these attributes.\n     */\n    public void addAll(Attributes incoming) {\n        int incomingSize = incoming.size(); // not adding internal\n        if (incomingSize == 0) return;\n        checkCapacity(size + incomingSize);\n\n        boolean needsPut = size != 0; // if this set is empty, no need to check existing set, so can add() vs put()\n        // (and save bashing on the indexOfKey()\n        for (Attribute attr : incoming) {\n            if (needsPut)\n                put(attr);\n            else\n                addObject(attr.getKey(), attr.getValue());\n        }\n    }\n\n    /**\n     Get the source ranges (start to end position) in the original input source from which this attribute's <b>name</b>\n     and <b>value</b> were parsed.\n     <p>Position tracking must be enabled prior to parsing the content.</p>\n     @param key the attribute name\n     @return the ranges for the attribute's name and value, or {@code untracked} if the attribute does not exist or its range\n     was not tracked.\n     @see org.jsoup.parser.Parser#setTrackPosition(boolean)\n     @see Attribute#sourceRange()\n     @see Node#sourceRange()\n     @see Element#endSourceRange()\n     @since 1.17.1\n     */\n    public Range.AttributeRange sourceRange(String key) {\n        if (!hasKey(key)) return UntrackedAttr;\n        Map<String, Range.AttributeRange> ranges = getRanges();\n        if (ranges == null) return Range.AttributeRange.UntrackedAttr;\n        Range.AttributeRange range = ranges.get(key);\n        return range != null ? range : Range.AttributeRange.UntrackedAttr;\n    }\n\n    /** Get the Ranges, if tracking is enabled; null otherwise. */\n    @Nullable Map<String, Range.AttributeRange> getRanges() {\n        //noinspection unchecked\n        return (Map<String, Range.AttributeRange>) userData(AttrRangeKey);\n    }\n\n    /**\n     Set the source ranges (start to end position) from which this attribute's <b>name</b> and <b>value</b> were parsed.\n     @param key the attribute name\n     @param range the range for the attribute's name and value\n     @return these attributes, for chaining\n     @since 1.18.2\n     */\n    public Attributes sourceRange(String key, Range.AttributeRange range) {\n        Validate.notNull(key);\n        Validate.notNull(range);\n        Map<String, Range.AttributeRange> ranges = getRanges();\n        if (ranges == null) {\n            ranges = new HashMap<>();\n            userData(AttrRangeKey, ranges);\n        }\n        ranges.put(key, range);\n        return this;\n    }\n\n\n    @Override\n    public Iterator<Attribute> iterator() {\n        //noinspection ReturnOfInnerClass\n        return new Iterator<Attribute>() {\n            int expectedSize = size;\n            int i = 0;\n\n            @Override\n            public boolean hasNext() {\n                checkModified();\n                while (i < size) {\n                    String key = keys[i];\n                    assert key != null;\n                    if (isInternalKey(key)) // skip over internal keys\n                        i++;\n                    else\n                        break;\n                }\n\n                return i < size;\n            }\n\n            @Override\n            public Attribute next() {\n                checkModified();\n                if (i >= size) throw new NoSuchElementException();\n                String key = keys[i];\n                assert key != null;\n                final Attribute attr = new Attribute(key, (String) vals[i], Attributes.this);\n                i++;\n                return attr;\n            }\n\n            private void checkModified() {\n                if (size != expectedSize) throw new ConcurrentModificationException(\"Use Iterator#remove() instead to remove attributes while iterating.\");\n            }\n\n            @Override\n            public void remove() {\n                Attributes.this.remove(--i); // next() advanced, so rewind\n                expectedSize--;\n            }\n        };\n    }\n\n    /**\n     Get the attributes as a List, for iteration.\n     @return a view of the attributes as an unmodifiable List.\n     */\n    public List<Attribute> asList() {\n        ArrayList<Attribute> list = new ArrayList<>(size);\n        for (int i = 0; i < size; i++) {\n            String key = keys[i];\n            assert key != null;\n            if (isInternalKey(key))\n                continue; // skip internal keys\n            Attribute attr = new Attribute(key, (String) vals[i], Attributes.this);\n            list.add(attr);\n        }\n        return Collections.unmodifiableList(list);\n    }\n\n    /**\n     * Retrieves a filtered view of attributes that are HTML5 custom data attributes; that is, attributes with keys\n     * starting with {@code data-}.\n     * @return map of custom data attributes.\n     */\n    public Map<String, String> dataset() {\n        return new Dataset(this);\n    }\n\n    /**\n     Get the HTML representation of these attributes.\n     @return HTML\n     */\n    public String html() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        html(QuietAppendable.wrap(sb), new Document.OutputSettings()); // output settings a bit funky, but this html() seldom used\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    final void html(final QuietAppendable accum, final Document.OutputSettings out) {\n        final int sz = size;\n        for (int i = 0; i < sz; i++) {\n            String key = keys[i];\n            assert key != null;\n            if (isInternalKey(key))\n                continue;\n            final String validated = Attribute.getValidKey(key, out.syntax());\n            if (validated != null)\n                Attribute.htmlNoValidate(validated, (String) vals[i], accum.append(' '), out);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return html();\n    }\n\n    /**\n     * Checks if these attributes are equal to another set of attributes, by comparing the two sets. Note that the order\n     * of the attributes does not impact this equality (as per the Map interface equals()).\n     * @param o attributes to compare with\n     * @return if both sets of attributes have the same content\n     */\n    @Override\n    public boolean equals(@Nullable Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        Attributes that = (Attributes) o;\n        if (size != that.size) return false;\n        for (int i = 0; i < size; i++) {\n            String key = keys[i];\n            assert key != null;\n            int thatI = that.indexOfKey(key);\n            if (thatI == NotFound || !Objects.equals(vals[i], that.vals[thatI]))\n                return false;\n        }\n        return true;\n    }\n\n    /**\n     * Calculates the hashcode of these attributes, by iterating all attributes and summing their hashcodes.\n     * @return calculated hashcode\n     */\n    @Override\n    public int hashCode() {\n        int result = size;\n        result = 31 * result + Arrays.hashCode(keys);\n        result = 31 * result + Arrays.hashCode(vals);\n        return result;\n    }\n\n    @Override\n    public Attributes clone() {\n        Attributes clone;\n        try {\n            clone = (Attributes) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new RuntimeException(e);\n        }\n        clone.size = size;\n        clone.keys = Arrays.copyOf(keys, size);\n        clone.vals = Arrays.copyOf(vals, size);\n\n        // make a copy of the user data map. (Contents are shallow).\n        int i = indexOfKey(SharedConstants.UserDataKey);\n        if (i != NotFound) {\n            //noinspection unchecked\n            vals[i] = new HashMap<>((Map<String, Object>) vals[i]);\n        }\n\n        return clone;\n    }\n\n    /**\n     * Internal method. Lowercases all (non-internal) keys.\n     */\n    public void normalize() {\n        for (int i = 0; i < size; i++) {\n            assert keys[i] != null;\n            String key = keys[i];\n            assert key != null;\n            if (!isInternalKey(key))\n                keys[i] = lowerCase(key);\n        }\n    }\n\n    /**\n     * Internal method. Removes duplicate attribute by name. Settings for case sensitivity of key names.\n     * @param settings case sensitivity\n     * @return number of removed dupes\n     */\n    public int deduplicate(ParseSettings settings) {\n        if (size == 0) return 0;\n        boolean preserve = settings.preserveAttributeCase();\n        int dupes = 0;\n        for (int i = 0; i < size; i++) {\n            String keyI = keys[i];\n            assert keyI != null;\n            for (int j = i + 1; j < size; j++) {\n                if ((preserve && keyI.equals(keys[j])) || (!preserve && keyI.equalsIgnoreCase(keys[j]))) {\n                    dupes++;\n                    remove(j);\n                    j--;\n                }\n            }\n        }\n        return dupes;\n    }\n\n    private static class Dataset extends AbstractMap<String, String> {\n        private final Attributes attributes;\n\n        private Dataset(Attributes attributes) {\n            this.attributes = attributes;\n        }\n\n        @Override\n        public Set<Entry<String, String>> entrySet() {\n            return new EntrySet();\n        }\n\n        @Override\n        public String put(String key, String value) {\n            String dataKey = dataKey(key);\n            String oldValue = attributes.hasKey(dataKey) ? attributes.get(dataKey) : null;\n            attributes.put(dataKey, value);\n            return oldValue;\n        }\n\n        private class EntrySet extends AbstractSet<Map.Entry<String, String>> {\n\n            @Override\n            public Iterator<Map.Entry<String, String>> iterator() {\n                return new DatasetIterator();\n            }\n\n            @Override\n            public int size() {\n                int count = 0;\n                Iterator<Entry<String, String>> iter = new DatasetIterator();\n                while (iter.hasNext())\n                    count++;\n                return count;\n            }\n        }\n\n        private class DatasetIterator implements Iterator<Map.Entry<String, String>> {\n            private final Iterator<Attribute> attrIter = attributes.iterator();\n            private Attribute attr;\n            @Override public boolean hasNext() {\n                while (attrIter.hasNext()) {\n                    attr = attrIter.next();\n                    if (attr.isDataAttribute()) return true;\n                }\n                return false;\n            }\n\n            @Override public Entry<String, String> next() {\n                return new Attribute(attr.getKey().substring(dataPrefix.length()), attr.getValue());\n            }\n\n            @Override public void remove() {\n                attributes.remove(attr.getKey());\n            }\n        }\n    }\n\n    private static String dataKey(String key) {\n        return dataPrefix + key;\n    }\n\n    static String internalKey(String key) {\n        return InternalPrefix + key;\n    }\n\n    static boolean isInternalKey(String key) {\n        return key.length() > 1 && key.charAt(0) == InternalPrefix;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/CDataNode.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\n\n/**\n * A Character Data node, to support CDATA sections.\n */\npublic class CDataNode extends TextNode {\n    public CDataNode(String text) {\n        super(text);\n    }\n\n    @Override\n    public String nodeName() {\n        return \"#cdata\";\n    }\n\n    /**\n     * Get the un-encoded, <b>non-normalized</b> text content of this CDataNode.\n     * @return un-encoded, non-normalized text\n     */\n    @Override\n    public String text() {\n        return getWholeText();\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        accum\n            .append(\"<![CDATA[\")\n            .append(getWholeText())\n            .append(\"]]>\");\n    }\n\n    @Override\n    public CDataNode clone() {\n        return (CDataNode) super.clone();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Comment.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.parser.Parser;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.List;\n\n/**\n A comment node.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class Comment extends LeafNode {\n    /**\n     Create a new comment node.\n     @param data The contents of the comment\n     */\n    public Comment(String data) {\n        super(data);\n    }\n\n    @Override public String nodeName() {\n        return \"#comment\";\n    }\n\n    /**\n     Get the contents of the comment.\n     @return comment content\n     */\n    public String getData() {\n        return coreValue();\n    }\n\n    public Comment setData(String data) {\n        coreValue(data);\n        return this;\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        accum\n            .append(\"<!--\")\n            .append(getData())\n            .append(\"-->\");\n    }\n\n    @Override\n    public Comment clone() {\n        return (Comment) super.clone();\n    }\n\n    /**\n     * Check if this comment looks like an XML Declaration. This is the case when the HTML parser sees an XML\n     * declaration or processing instruction. Other than doctypes, those aren't part of HTML, and will be parsed as a\n     * bogus comment.\n     * @return true if it looks like, maybe, it's an XML Declaration.\n     * @see #asXmlDeclaration()\n     */\n    public boolean isXmlDeclaration() {\n        String data = getData();\n        return isXmlDeclarationData(data);\n    }\n\n    private static boolean isXmlDeclarationData(String data) {\n        return (data.length() > 1 && (data.startsWith(\"!\") || data.startsWith(\"?\")));\n    }\n\n    /**\n     * Attempt to cast this comment to an XML Declaration node.\n     * @return an XML declaration if it could be parsed as one, null otherwise.\n     * @see #isXmlDeclaration()\n     */\n    public @Nullable XmlDeclaration asXmlDeclaration() {\n        String fragment = \"<\" + getData() + \">\";\n        Parser parser = Parser.xmlParser();\n        List<Node> nodes = parser.parseFragmentInput(fragment, null, \"\");\n        if (!nodes.isEmpty() && nodes.get(0) instanceof XmlDeclaration)\n            return (XmlDeclaration) nodes.get(0);\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/DataNode.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\n\n/**\n A data node, for contents of style, script tags etc, where contents should not show in text().\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class DataNode extends LeafNode {\n\n    /**\n     Create a new DataNode.\n     @param data data contents\n     */\n    public DataNode(String data) {\n        super(data);\n    }\n\n    @Override public String nodeName() {\n        return \"#data\";\n    }\n\n    /**\n     Get the data contents of this node. Will be unescaped and with original new lines, space etc.\n     @return data\n     */\n    public String getWholeData() {\n        return coreValue();\n    }\n\n    /**\n     * Set the data contents of this node.\n     * @param data un-encoded data\n     * @return this node, for chaining\n     */\n    public DataNode setWholeData(String data) {\n        coreValue(data);\n        return this;\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        /* For XML output, escape the DataNode in a CData section. The data may contain pseudo-CData content if it was\n        parsed as HTML, so don't double up Cdata. Output in polyglot HTML / XHTML / XML format. */\n        final String data = getWholeData();\n        if (out.syntax() == Document.OutputSettings.Syntax.xml && !data.contains(\"<![CDATA[\")) {\n            if (parentNameIs(\"script\"))\n                accum.append(\"//<![CDATA[\\n\").append(data).append(\"\\n//]]>\");\n            else if (parentNameIs(\"style\"))\n                accum.append(\"/*<![CDATA[*/\\n\").append(data).append(\"\\n/*]]>*/\");\n            else\n                accum.append(\"<![CDATA[\").append(data).append(\"]]>\");\n        } else {\n            // In HTML, data is not escaped in the output of data nodes, so < and & in script, style is OK\n            accum.append(data);\n        }\n    }\n\n    @Override\n    public DataNode clone() {\n        return (DataNode) super.clone();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Document.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.Selector;\nimport org.jspecify.annotations.Nullable;\n\nimport java.nio.charset.Charset;\nimport java.util.List;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\n\n/**\n A HTML Document.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class Document extends Element {\n    private @Nullable Connection connection; // the connection this doc was fetched from, if any\n    private OutputSettings outputSettings = new OutputSettings();\n    private Parser parser; // the parser used to parse this document\n    private QuirksMode quirksMode = QuirksMode.noQuirks;\n    private final String location;\n\n    /**\n     Create a new, empty Document, in the specified namespace.\n     @param namespace the namespace of this Document's root node.\n     @param baseUri base URI of document\n     @see org.jsoup.Jsoup#parse\n     @see #createShell\n     */\n    public Document(String namespace, String baseUri) {\n        this(namespace, baseUri, Parser.htmlParser()); // default HTML parser, but overridable\n    }\n\n    private Document(String namespace, String baseUri, Parser parser) {\n        super(new Tag(\"#root\", namespace), baseUri);\n        this.location = baseUri;\n        this.parser = parser;\n    }\n\n    /**\n     Create a new, empty Document, in the HTML namespace.\n     @param baseUri base URI of document\n     @see org.jsoup.Jsoup#parse\n     @see #Document(String namespace, String baseUri)\n     */\n    public Document(String baseUri) {\n        this(NamespaceHtml, baseUri);\n    }\n\n    /**\n     Create a valid, empty shell of an HTML document, suitable for adding more elements to.\n     @param baseUri baseUri of document\n     @return document with html, head, and body elements.\n     */\n    public static Document createShell(String baseUri) {\n        Validate.notNull(baseUri);\n\n        Document doc = new Document(baseUri);\n        Element html = doc.appendElement(\"html\");\n        html.appendElement(\"head\");\n        html.appendElement(\"body\");\n\n        return doc;\n    }\n\n    /**\n     * Get the URL this Document was parsed from. If the starting URL is a redirect,\n     * this will return the final URL from which the document was served from.\n     * <p>Will return an empty string if the location is unknown (e.g. if parsed from a String).\n     * @return location\n     */\n    public String location() {\n        return location;\n    }\n\n    /**\n     Returns the Connection (Request/Response) object that was used to fetch this document, if any; otherwise, a new\n     default Connection object. This can be used to continue a session, preserving settings and cookies, etc.\n     @return the Connection (session) associated with this Document, or an empty one otherwise.\n     @see Connection#newRequest()\n     */\n    public Connection connection() {\n        if (connection == null)\n            return Jsoup.newSession();\n        else\n            return connection;\n    }\n\n    /**\n     * Returns this Document's doctype.\n     * @return document type, or null if not set\n     */\n    public @Nullable DocumentType documentType() {\n        for (Node node : childNodes) {\n            if (node instanceof DocumentType)\n                return (DocumentType) node;\n            else if (!(node instanceof LeafNode)) // scans forward across comments, text, processing instructions etc\n                break;\n        }\n        return null;\n    }\n\n    /**\n     Find the root HTML element, or create it if it doesn't exist.\n     @return the root HTML element.\n     */\n    private Element htmlEl() {\n        Element el = firstElementChild();\n        while (el != null) {\n            if (el.nameIs(\"html\"))\n                return el;\n            el = el.nextElementSibling();\n        }\n        return appendElement(\"html\");\n    }\n\n    /**\n     Get this document's {@code head} element.\n     <p>\n     As a side effect, if this Document does not already have an HTML structure, it will be created. If you do not want\n     that, use {@code #selectFirst(\"head\")} instead.\n\n     @return {@code head} element.\n     */\n    public Element head() {\n        final Element html = htmlEl();\n        Element el = html.firstElementChild();\n        while (el != null) {\n            if (el.nameIs(\"head\"))\n                return el;\n            el = el.nextElementSibling();\n        }\n        return html.prependElement(\"head\");\n    }\n\n    /**\n     Get this document's {@code <body>} or {@code <frameset>} element.\n     <p>\n     As a <b>side-effect</b>, if this Document does not already have an HTML structure, it will be created with a {@code\n    <body>} element. If you do not want that, use {@code #selectFirst(\"body\")} instead.\n\n     @return {@code body} element for documents with a {@code <body>}, a new {@code <body>} element if the document\n     had no contents, or the outermost {@code <frameset> element} for frameset documents.\n     */\n    public Element body() {\n        final Element html = htmlEl();\n        Element el = html.firstElementChild();\n        while (el != null) {\n            if (el.nameIs(\"body\") || el.nameIs(\"frameset\"))\n                return el;\n            el = el.nextElementSibling();\n        }\n        return html.appendElement(\"body\");\n    }\n\n    /**\n     Get each of the {@code <form>} elements contained in this document.\n     @return a List of FormElement objects, which will be empty if there are none.\n     @see Elements#forms()\n     @see FormElement#elements()\n     @since 1.15.4\n     */\n    public List<FormElement> forms() {\n        return select(\"form\").forms();\n    }\n\n    /**\n     Selects the first {@link FormElement} in this document that matches the query. If none match, throws an\n     {@link IllegalArgumentException}.\n     @param cssQuery a {@link Selector} CSS query\n     @return the first matching {@code <form>} element\n     @throws IllegalArgumentException if no match is found\n     @since 1.15.4\n     */\n    public FormElement expectForm(String cssQuery) {\n        Elements els = select(cssQuery);\n        for (Element el : els) {\n            if (el instanceof FormElement) return (FormElement) el;\n        }\n        Validate.fail(\"No form elements matched the query '%s' in the document.\", cssQuery);\n        return null; // (not really)\n    }\n\n    /**\n     Get the string contents of the document's {@code title} element.\n     @return Trimmed title, or empty string if none set.\n     */\n    public String title() {\n        // title is a preserve whitespace tag (for document output), but normalised here\n        Element titleEl = head().selectFirst(titleEval);\n        return titleEl != null ? StringUtil.normaliseWhitespace(titleEl.text()).trim() : \"\";\n    }\n    private static final Evaluator titleEval = new Evaluator.Tag(\"title\");\n\n    /**\n     Set the document's {@code title} element. Updates the existing element, or adds {@code title} to {@code head} if\n     not present\n     @param title string to set as title\n     */\n    public void title(String title) {\n        Validate.notNull(title);\n        Element titleEl = head().selectFirst(titleEval);\n        if (titleEl == null) // add to head\n            titleEl = head().appendElement(\"title\");\n        titleEl.text(title);\n    }\n\n    /**\n     Create a new Element, with this document's base uri. Does not make the new element a child of this document.\n     @param tagName element tag name (e.g. {@code a})\n     @return new element\n     */\n    public Element createElement(String tagName) {\n        return new Element(\n            parser.tagSet().valueOf(tagName, parser.defaultNamespace(), ParseSettings.preserveCase),\n            searchUpForAttribute(this, BaseUriKey)\n        );\n    }\n\n    @Override\n    public String outerHtml() {\n        return super.html(); // no outer wrapper tag\n    }\n\n    /**\n     Set the text of the {@code body} of this document. Any existing nodes within the body will be cleared.\n     @param text un-encoded text\n     @return this document\n     */\n    @Override\n    public Element text(String text) {\n        body().text(text); // overridden to not nuke doc structure\n        return this;\n    }\n\n    @Override\n    public String nodeName() {\n        return \"#document\";\n    }\n\n    /**\n     Set the output character set of this Document. This method is equivalent to\n     {@link OutputSettings#charset(java.nio.charset.Charset) OutputSettings.charset(Charset)}, but additionally adds or\n     updates the charset / encoding element within the Document.\n\n     <p>If there's no existing element with charset / encoding information yet, one will\n     be created. Obsolete charset / encoding definitions are removed.</p>\n\n     <p><b>Elements used:</b></p>\n\n     <ul>\n     <li><b>HTML:</b> <i>&lt;meta charset=\"CHARSET\"&gt;</i></li>\n     <li><b>XML:</b> <i>&lt;?xml version=\"1.0\" encoding=\"CHARSET\"&gt;</i></li>\n     </ul>\n\n     @param charset Charset\n     @see OutputSettings#charset(java.nio.charset.Charset)\n     */\n    public void charset(Charset charset) {\n        outputSettings.charset(charset);\n        ensureMetaCharsetElement();\n    }\n\n    /**\n     Get the output character set of this Document. This method is equivalent to {@link OutputSettings#charset()}.\n\n     @return the current Charset\n     @see OutputSettings#charset()\n     */\n    public Charset charset() {\n        return outputSettings.charset();\n    }\n\n    @Override\n    public Document clone() {\n        Document clone = (Document) super.clone();\n        if (attributes != null) clone.attributes = attributes.clone();\n        clone.outputSettings = this.outputSettings.clone();\n        // parser is pointer copy\n        return clone;\n    }\n\n    @Override\n    public Document shallowClone() {\n        Document clone = new Document(this.tag().namespace(), baseUri(), parser); // preserves parser pointer\n        if (attributes != null) clone.attributes = attributes.clone();\n        clone.outputSettings = this.outputSettings.clone();\n        return clone;\n    }\n    \n\n    private void ensureMetaCharsetElement() {\n        OutputSettings.Syntax syntax = outputSettings().syntax();\n\n        if (syntax == OutputSettings.Syntax.html) {\n            Element metaCharset = selectFirst(\"meta[charset]\");\n            if (metaCharset != null) {\n                metaCharset.attr(\"charset\", charset().displayName());\n            } else {\n                head().appendElement(\"meta\").attr(\"charset\", charset().displayName());\n            }\n            select(\"meta[name=charset]\").remove(); // Remove obsolete elements\n        } else if (syntax == OutputSettings.Syntax.xml) {\n            XmlDeclaration decl = ensureXmlDecl();\n            decl.attr(\"version\", \"1.0\");\n            decl.attr(\"encoding\", charset().displayName());\n        }\n    }\n\n    private XmlDeclaration ensureXmlDecl() {\n        Node node = firstChild();\n        if (node instanceof XmlDeclaration) {\n            XmlDeclaration decl = (XmlDeclaration) node;\n            if (decl.name().equals(\"xml\")) return decl;\n        }\n        XmlDeclaration decl = new XmlDeclaration(\"xml\", false);\n        prependChild(decl);\n        return decl;\n    }\n\n\n    /**\n     * A Document's output settings control the form of the text() and html() methods.\n     */\n    public static class OutputSettings implements Cloneable {\n        /**\n         * The output serialization syntax.\n         */\n        public enum Syntax {html, xml}\n        private Entities.EscapeMode escapeMode = Entities.EscapeMode.base;\n        private Charset charset = DataUtil.UTF_8;\n        private boolean prettyPrint = true;\n        private boolean outline = false;\n        private int indentAmount = 1;\n        private int maxPaddingWidth = 30;\n        private Syntax syntax = Syntax.html;\n\n        /**\n         Create a new OutputSettings object, with the default settings (UTF-8, HTML, EscapeMode.base, pretty-printing,\n         indent amount of 1).\n         */\n        public OutputSettings() {\n        }\n\n        /**\n         Get the document's current entity escape mode:\n         <ul>\n         <li><code>xhtml</code>, the minimal named entities in XHTML / XML</li>\n         <li><code>base</code>, which provides a limited set of named HTML\n         entities and escapes other characters as numbered entities for maximum compatibility</li>\n         <li><code>extended</code>,\n         which uses the complete set of HTML named entities.</li>\n         </ul>\n         <p>The default escape mode is <code>base</code>.\n         @return the document's current escape mode\n         */\n        public Entities.EscapeMode escapeMode() {\n            return escapeMode;\n        }\n\n        /**\n         * Set the document's escape mode, which determines how characters are escaped when the output character set\n         * does not support a given character:- using either a named or a numbered escape.\n         * @param escapeMode the new escape mode to use\n         * @return the document's output settings, for chaining\n         */\n        public OutputSettings escapeMode(Entities.EscapeMode escapeMode) {\n            this.escapeMode = escapeMode;\n            return this;\n        }\n\n        /**\n         * Get the document's current output charset, which is used to control which characters are escaped when\n         * generating HTML (via the <code>html()</code> methods), and which are kept intact.\n         * <p>\n         * Where possible (when parsing from a URL or File), the document's output charset is automatically set to the\n         * input charset. Otherwise, it defaults to UTF-8.\n         * @return the document's current charset.\n         */\n        public Charset charset() {\n            return charset;\n        }\n\n        /**\n         * Update the document's output charset.\n         * @param charset the new charset to use.\n         * @return the document's output settings, for chaining\n         */\n        public OutputSettings charset(Charset charset) {\n            this.charset = charset;\n            return this;\n        }\n\n        /**\n         * Update the document's output charset.\n         * @param charset the new charset (by name) to use.\n         * @return the document's output settings, for chaining\n         */\n        public OutputSettings charset(String charset) {\n            charset(Charset.forName(charset));\n            return this;\n        }\n\n        /**\n         * Get the document's current output syntax.\n         * @return current syntax\n         */\n        public Syntax syntax() {\n            return syntax;\n        }\n\n        /**\n         * Set the document's output syntax. Either {@code html}, with empty tags and boolean attributes (etc), or\n         * {@code xml}, with self-closing tags.\n         * <p>When set to {@link Document.OutputSettings.Syntax#xml xml}, the {@link #escapeMode() escapeMode} is\n         * automatically set to {@link Entities.EscapeMode#xhtml}, but may be subsequently changed if desired.</p>\n         * @param syntax serialization syntax\n         * @return the document's output settings, for chaining\n         */\n        public OutputSettings syntax(Syntax syntax) {\n            this.syntax = syntax;\n            if (syntax == Syntax.xml)\n                this.escapeMode(Entities.EscapeMode.xhtml);\n            return this;\n        }\n\n        /**\n         * Get if pretty printing is enabled. Default is true. If disabled, the HTML output methods will not re-format\n         * the output, and the output will generally look like the input.\n         * @return if pretty printing is enabled.\n         */\n        public boolean prettyPrint() {\n            return prettyPrint;\n        }\n\n        /**\n         * Enable or disable pretty printing.\n         * @param pretty new pretty print setting\n         * @return this, for chaining\n         */\n        public OutputSettings prettyPrint(boolean pretty) {\n            prettyPrint = pretty;\n            return this;\n        }\n        \n        /**\n         * Get if outline mode is enabled. Default is false. If enabled, the HTML output methods will consider\n         * all tags as block.\n         * @return if outline mode is enabled.\n         */\n        public boolean outline() {\n            return outline;\n        }\n        \n        /**\n         * Enable or disable HTML outline mode.\n         * @param outlineMode new outline setting\n         * @return this, for chaining\n         */\n        public OutputSettings outline(boolean outlineMode) {\n            outline = outlineMode;\n            return this;\n        }\n\n        /**\n         * Get the current tag indent amount, used when pretty printing.\n         * @return the current indent amount\n         */\n        public int indentAmount() {\n            return indentAmount;\n        }\n\n        /**\n         * Set the indent amount for pretty printing\n         * @param indentAmount number of spaces to use for indenting each level. Must be {@literal >=} 0.\n         * @return this, for chaining\n         */\n        public OutputSettings indentAmount(int indentAmount) {\n            Validate.isTrue(indentAmount >= 0);\n            this.indentAmount = indentAmount;\n            return this;\n        }\n\n        /**\n         * Get the current max padding amount, used when pretty printing\n         * so very deeply nested nodes don't get insane padding amounts.\n         * @return the current indent amount\n         */\n        public int maxPaddingWidth() {\n            return maxPaddingWidth;\n        }\n\n        /**\n         * Set the max padding amount for pretty printing so very deeply nested nodes don't get insane padding amounts.\n         * @param maxPaddingWidth number of spaces to use for indenting each level of nested nodes. Must be {@literal >=} -1.\n         *        Default is 30 and -1 means unlimited.\n         * @return this, for chaining\n         */\n        public OutputSettings maxPaddingWidth(int maxPaddingWidth) {\n            Validate.isTrue(maxPaddingWidth >= -1);\n            this.maxPaddingWidth = maxPaddingWidth;\n            return this;\n        }\n\n        @Override\n        public OutputSettings clone() {\n            OutputSettings clone;\n            try {\n                clone = (OutputSettings) super.clone();\n            } catch (CloneNotSupportedException e) {\n                throw new RuntimeException(e);\n            }\n            clone.charset(charset.name()); // new charset, coreCharset, and charset encoder\n            clone.escapeMode = Entities.EscapeMode.valueOf(escapeMode.name());\n            // indentAmount, maxPaddingWidth, and prettyPrint are primitives so object.clone() will handle\n            return clone;\n        }\n    }\n\n    /**\n     * Get the document's current output settings.\n     * @return the document's current output settings.\n     */\n    public OutputSettings outputSettings() {\n        return outputSettings;\n    }\n\n    /**\n     * Set the document's output settings.\n     * @param outputSettings new output settings.\n     * @return this document, for chaining.\n     */\n    public Document outputSettings(OutputSettings outputSettings) {\n        Validate.notNull(outputSettings);\n        this.outputSettings = outputSettings;\n        return this;\n    }\n\n    public enum QuirksMode {\n        noQuirks, quirks, limitedQuirks\n    }\n\n    public QuirksMode quirksMode() {\n        return quirksMode;\n    }\n\n    public Document quirksMode(QuirksMode quirksMode) {\n        this.quirksMode = quirksMode;\n        return this;\n    }\n\n    /**\n     * Get the parser that was used to parse this document.\n     * @return the parser\n     */\n    public Parser parser() {\n        return parser;\n    }\n\n    /**\n     * Set the parser used to create this document. This parser is then used when further parsing within this document\n     * is required.\n     * @param parser the configured parser to use when further parsing is required for this document.\n     * @return this document, for chaining.\n     */\n    public Document parser(Parser parser) {\n        this.parser = parser;\n        return this;\n    }\n\n    /**\n     Set the Connection used to fetch this document. This Connection is used as a session object when further requests are\n     made (e.g. when a form is submitted).\n\n     @param connection to set\n     @return this document, for chaining\n     @see Connection#newRequest()\n     @since 1.14.1\n     */\n    public Document connection(Connection connection) {\n        Validate.notNull(connection);\n        this.connection = connection;\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/DocumentType.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document.OutputSettings.Syntax;\nimport org.jspecify.annotations.Nullable;\n\n\n/**\n * A {@code <!DOCTYPE>} node.\n */\npublic class DocumentType extends LeafNode {\n    // todo needs a bit of a chunky cleanup. this level of detail isn't needed\n    public static final String PUBLIC_KEY = \"PUBLIC\";\n    public static final String SYSTEM_KEY = \"SYSTEM\";\n    private static final String NameKey = \"name\";\n    private static final String PubSysKey = \"pubSysKey\"; // PUBLIC or SYSTEM\n    private static final String PublicId = \"publicId\";\n    private static final String SystemId = \"systemId\";\n\n    /**\n     * Create a new doctype element.\n     * @param name the doctype's name\n     * @param publicId the doctype's public ID\n     * @param systemId the doctype's system ID\n     */\n    public DocumentType(String name, String publicId, String systemId) {\n        super(name);\n        Validate.notNull(publicId);\n        Validate.notNull(systemId);\n        attributes()\n            .add(NameKey, name)\n            .add(PublicId, publicId)\n            .add(SystemId, systemId);\n        updatePubSyskey();\n    }\n\n    public void setPubSysKey(@Nullable String value) {\n        if (value != null)\n            attr(PubSysKey, value);\n    }\n\n    private void updatePubSyskey() {\n        if (has(PublicId)) {\n            attributes().add(PubSysKey, PUBLIC_KEY);\n        } else if (has(SystemId))\n            attributes().add(PubSysKey, SYSTEM_KEY);\n    }\n\n    /**\n     * Get this doctype's name (when set, or empty string)\n     * @return doctype name\n     */\n    public String name() {\n        return attr(NameKey);\n    }\n\n    /**\n     * Get this doctype's Public ID (when set, or empty string)\n     * @return doctype Public ID\n     */\n    public String publicId() {\n        return attr(PublicId);\n    }\n\n    /**\n     * Get this doctype's System ID (when set, or empty string)\n     * @return doctype System ID\n     */\n    public String systemId() {\n        return attr(SystemId);\n    }\n\n    @Override\n    public String nodeName() {\n        return \"#doctype\";\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        if (out.syntax() == Syntax.html && !has(PublicId) && !has(SystemId)) {\n            // looks like a html5 doctype, go lowercase for aesthetics\n            accum.append(\"<!doctype\");\n        } else {\n            accum.append(\"<!DOCTYPE\");\n        }\n        if (has(NameKey))\n            accum.append(\" \").append(attr(NameKey));\n        if (has(PubSysKey))\n            accum.append(\" \").append(attr(PubSysKey));\n        if (has(PublicId))\n            accum.append(\" \\\"\").append(attr(PublicId)).append('\"');\n        if (has(SystemId))\n            accum.append(\" \\\"\").append(attr(SystemId)).append('\"');\n        accum.append('>');\n    }\n\n\n    private boolean has(final String attribute) {\n        return !StringUtil.isBlank(attr(attribute));\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Element.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.helper.Regex;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.parser.TokenQueue;\nimport org.jsoup.select.Collector;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.NodeFilter;\nimport org.jsoup.select.NodeVisitor;\nimport org.jsoup.select.Nodes;\nimport org.jsoup.select.Selector;\nimport org.jspecify.annotations.Nullable;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.internal.Normalizer.normalize;\nimport static org.jsoup.nodes.Document.OutputSettings.Syntax.xml;\nimport static org.jsoup.nodes.TextNode.lastCharIsWhitespace;\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.jsoup.parser.TokenQueue.escapeCssIdentifier;\nimport static org.jsoup.select.Selector.evaluatorOf;\n\n/**\n An HTML Element consists of a tag name, attributes, and child nodes (including text nodes and other elements).\n <p>\n From an Element, you can extract data, traverse the node graph, and manipulate the HTML.\n*/\npublic class Element extends Node implements Iterable<Element> {\n    private static final List<Element> EmptyChildren = Collections.emptyList();\n    private static final NodeList EmptyNodeList = new NodeList(0);\n    private static final Pattern ClassSplit = Pattern.compile(\"\\\\s+\");\n    static final String BaseUriKey = Attributes.internalKey(\"baseUri\");\n    Tag tag;\n    NodeList childNodes;\n    @Nullable Attributes attributes; // field is nullable but all methods for attributes are non-null\n\n    /**\n     * Create a new, standalone element, in the specified namespace.\n     * @param tag tag name\n     * @param namespace namespace for this element\n     */\n    public Element(String tag, String namespace) {\n        this(Tag.valueOf(tag, namespace, ParseSettings.preserveCase), null);\n    }\n\n    /**\n     * Create a new, standalone element, in the HTML namespace.\n     * @param tag tag name\n     * @see #Element(String tag, String namespace)\n     */\n    public Element(String tag) {\n        this(tag, Parser.NamespaceHtml);\n    }\n\n    /**\n     * Create a new, standalone Element. (Standalone in that it has no parent.)\n     *\n     * @param tag tag of this element\n     * @param baseUri the base URI (optional, may be null to inherit from parent, or \"\" to clear parent's)\n     * @param attributes initial attributes (optional, may be null)\n     * @see #appendChild(Node)\n     * @see #appendElement(String)\n     */\n    public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attributes) {\n        Validate.notNull(tag);\n        childNodes = EmptyNodeList;\n        this.attributes = attributes;\n        this.tag = tag;\n        if (!StringUtil.isBlank(baseUri)) this.setBaseUri(baseUri);\n    }\n\n    /**\n     * Create a new Element from a Tag and a base URI.\n     *\n     * @param tag element tag\n     * @param baseUri the base URI of this element. Optional, and will inherit from its parent, if any.\n     * @see Tag#valueOf(String, ParseSettings)\n     */\n    public Element(Tag tag, @Nullable String baseUri) {\n        this(tag, baseUri, null);\n    }\n\n    /**\n     Internal test to check if a nodelist object has been created.\n     */\n    protected boolean hasChildNodes() {\n        return childNodes != EmptyNodeList;\n    }\n\n    @Override protected List<Node> ensureChildNodes() {\n        if (childNodes == EmptyNodeList) {\n            childNodes = new NodeList(4);\n        }\n        return childNodes;\n    }\n\n    @Override\n    protected boolean hasAttributes() {\n        return attributes != null;\n    }\n\n    @Override\n    public Attributes attributes() {\n        if (attributes == null) // not using hasAttributes, as doesn't clear warning\n            attributes = new Attributes();\n        return attributes;\n    }\n\n    @Override\n    public String baseUri() {\n        String baseUri = searchUpForAttribute(this, BaseUriKey);\n        return baseUri != null ? baseUri : \"\";\n    }\n\n    @Nullable\n    static String searchUpForAttribute(final Element start, final String key) {\n        Element el = start;\n        while (el != null) {\n            if (el.attributes != null && el.attributes.hasKey(key))\n                return el.attributes.get(key);\n            el = el.parent();\n        }\n        return null;\n    }\n\n    @Override\n    protected void doSetBaseUri(String baseUri) {\n        attributes().put(BaseUriKey, baseUri);\n    }\n\n    @Override\n    public int childNodeSize() {\n        return childNodes.size();\n    }\n\n    @Override\n    public String nodeName() {\n        return tag.getName();\n    }\n\n    /**\n     * Get the name of the tag for this element. E.g. {@code div}. If you are using {@link ParseSettings#preserveCase\n     * case preserving parsing}, this will return the source's original case.\n     *\n     * @return the tag name\n     */\n    public String tagName() {\n        return tag.getName();\n    }\n\n    /**\n     * Get the normalized name of this Element's tag. This will always be the lower-cased version of the tag, regardless\n     * of the tag case preserving setting of the parser. For e.g., {@code <DIV>} and {@code <div>} both have a\n     * normal name of {@code div}.\n     * @return normal name\n     */\n    @Override\n    public String normalName() {\n        return tag.normalName();\n    }\n\n    /**\n     Test if this Element has the specified normalized name, and is in the specified namespace.\n     * @param normalName a normalized element name (e.g. {@code div}).\n     * @param namespace the namespace\n     * @return true if the element's normal name matches exactly, and is in the specified namespace\n     * @since 1.17.2\n     */\n    public boolean elementIs(String normalName, String namespace) {\n        return tag.normalName().equals(normalName) && tag.namespace().equals(namespace);\n    }\n\n    /**\n     * Change (rename) the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with\n     * {@code el.tagName(\"div\");}.\n     *\n     * @param tagName new tag name for this element\n     * @return this element, for chaining\n     * @see Elements#tagName(String)\n     */\n    public Element tagName(String tagName) {\n        return tagName(tagName, tag.namespace());\n    }\n\n    /**\n     * Change (rename) the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with\n     * {@code el.tagName(\"div\");}.\n     *\n     * @param tagName new tag name for this element\n     * @param namespace the new namespace for this element\n     * @return this element, for chaining\n     * @see Elements#tagName(String)\n     */\n    public Element tagName(String tagName, String namespace) {\n        Validate.notEmptyParam(tagName, \"tagName\");\n        Validate.notEmptyParam(namespace, \"namespace\");\n        Parser parser = NodeUtils.parser(this);\n        tag = parser.tagSet().valueOf(tagName, namespace, parser.settings()); // maintains the case option of the original parse\n        return this;\n    }\n\n    /**\n     * Get the Tag for this element.\n     *\n     * @return the tag object\n     */\n    public Tag tag() {\n        return tag;\n    }\n\n    /**\n     Change the Tag of this element.\n     @param tag the new tag\n     @return this element, for chaining\n     @since 1.20.1\n     */\n    public Element tag(Tag tag) {\n        Validate.notNull(tag);\n        this.tag = tag;\n        return this;\n    }\n\n    /**\n     * Test if this element is a block-level element. (E.g. {@code <div> == true} or an inline element\n     * {@code <span> == false}).\n     *\n     * @return true if block, false if not (and thus inline)\n     */\n    public boolean isBlock() {\n        return tag.isBlock();\n    }\n\n    /**\n     * Get the {@code id} attribute of this element.\n     *\n     * @return The id attribute, if present, or an empty string if not.\n     */\n    public String id() {\n        return attributes != null ? attributes.getIgnoreCase(\"id\") :\"\";\n    }\n\n    /**\n     Set the {@code id} attribute of this element.\n     @param id the ID value to use\n     @return this Element, for chaining\n     */\n    public Element id(String id) {\n        Validate.notNull(id);\n        attr(\"id\", id);\n        return this;\n    }\n\n    /**\n     * Set an attribute value on this element. If this element already has an attribute with the\n     * key, its value is updated; otherwise, a new attribute is added.\n     *\n     * @return this element\n     */\n    @Override public Element attr(String attributeKey, String attributeValue) {\n        super.attr(attributeKey, attributeValue);\n        return this;\n    }\n\n    /**\n     * Set a boolean attribute value on this element. Setting to <code>true</code> sets the attribute value to \"\" and\n     * marks the attribute as boolean so no value is written out. Setting to <code>false</code> removes the attribute\n     * with the same key if it exists.\n     *\n     * @param attributeKey the attribute key\n     * @param attributeValue the attribute value\n     *\n     * @return this element\n     */\n    public Element attr(String attributeKey, boolean attributeValue) {\n        attributes().put(attributeKey, attributeValue);\n        return this;\n    }\n\n    /**\n     Get an Attribute by key. Changes made via {@link Attribute#setKey(String)}, {@link Attribute#setValue(String)} etc\n     will cascade back to this Element.\n     @param key the (case-sensitive) attribute key\n     @return the Attribute for this key, or null if not present.\n     @since 1.17.2\n     */\n    @Nullable public Attribute attribute(String key) {\n        return hasAttributes() ? attributes().attribute(key) : null;\n    }\n\n    /**\n     * Get this element's HTML5 custom data attributes. Each attribute in the element that has a key\n     * starting with \"data-\" is included the dataset.\n     * <p>\n     * E.g., the element {@code <div data-package=\"jsoup\" data-language=\"Java\" class=\"group\">...} has the dataset\n     * {@code package=jsoup, language=java}.\n     * <p>\n     * This map is a filtered view of the element's attribute map. Changes to one map (add, remove, update) are reflected\n     * in the other map.\n     * <p>\n     * You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector.\n     * @return a map of {@code key=value} custom data attributes.\n     */\n    public Map<String, String> dataset() {\n        return attributes().dataset();\n    }\n\n    @Override @Nullable\n    public final Element parent() {\n        return (Element) parentNode;\n    }\n\n    /**\n     * Get this element's parent and ancestors, up to the document root.\n     * @return this element's stack of parents, starting with the closest first.\n     */\n    public Elements parents() {\n        Elements parents = new Elements();\n        Element parent = this.parent();\n        while (parent != null && !parent.nameIs(\"#root\")) {\n            parents.add(parent);\n            parent = parent.parent();\n        }\n        return parents;\n    }\n\n    /**\n     * Get a child element of this element, by its 0-based index number.\n     * <p>\n     * Note that an element can have both mixed Nodes and Elements as children. This method inspects\n     * a filtered list of children that are elements, and the index is based on that filtered list.\n     * </p>\n     *\n     * @param index the index number of the element to retrieve\n     * @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException}\n     * @see #childNode(int)\n     */\n    public Element child(int index) {\n        Validate.isTrue(index >= 0, \"Index must be >= 0\");\n        List<Element> cached = cachedChildren();\n        if (cached != null) return cached.get(index);\n        // otherwise, iter on elementChild; saves creating list\n        int size = childNodes.size();\n        for (int i = 0, e = 0; i < size; i++) { // direct iter is faster than chasing firstElSib, nextElSibd\n            Node node = childNodes.get(i);\n            if (node instanceof Element) {\n                if (e++ == index) return (Element) node;\n            }\n        }\n        throw new IndexOutOfBoundsException(\"No child at index: \" + index);\n    }\n\n    /**\n     * Get the number of child nodes of this element that are elements.\n     * <p>\n     * This method works on the same filtered list like {@link #child(int)}. Use {@link #childNodes()} and {@link\n     * #childNodeSize()} to get the unfiltered Nodes (e.g. includes TextNodes etc.)\n     * </p>\n     *\n     * @return the number of child nodes that are elements\n     * @see #children()\n     * @see #child(int)\n     */\n    public int childrenSize() {\n        if (childNodeSize() == 0) return 0;\n        return childElementsList().size(); // gets children into cache; faster subsequent child(i) if unmodified\n    }\n\n    /**\n     * Get this element's child elements.\n     * <p>\n     * This is effectively a filter on {@link #childNodes()} to get Element nodes.\n     * </p>\n     * @return child elements. If this element has no children, returns an empty list.\n     * @see #childNodes()\n     */\n    public Elements children() {\n        return new Elements(childElementsList());\n    }\n\n    /**\n     * Maintains a shadow copy of this element's child elements. If the nodelist is changed, this cache is invalidated.\n     * @return a list of child elements\n     */\n    List<Element> childElementsList() {\n        if (childNodeSize() == 0) return EmptyChildren; // short circuit creating empty\n        // set atomically, so works in multi-thread. Calling methods look like reads, so should be thread-safe\n        synchronized (childNodes) { // sync vs re-entrant lock, to save another field\n            List<Element> children = cachedChildren();\n            if (children == null) {\n                children = filterNodes(Element.class);\n                stashChildren(children);\n            }\n            return children;\n        }\n    }\n\n    private static final String childElsKey = \"jsoup.childEls\";\n    private static final String childElsMod = \"jsoup.childElsMod\";\n\n    /** returns the cached child els, if they exist, and the modcount of our childnodes matches the stashed modcount */\n    @Nullable List<Element> cachedChildren() {\n        if (attributes == null || !attributes.hasUserData()) return null; // don't create empty userdata\n        Map<String, Object> userData = attributes.userData();\n        //noinspection unchecked\n        WeakReference<List<Element>> ref = (WeakReference<List<Element>>) userData.get(childElsKey);\n        if (ref != null) {\n            List<Element> els = ref.get();\n            if (els != null) {\n                Integer modCount = (Integer) userData.get(childElsMod);\n                if (modCount != null && modCount == childNodes.modCount())\n                    return els;\n            }\n        }\n        return null;\n    }\n\n    /** caches the child els into the Attribute user data. */\n    private void stashChildren(List<Element> els) {\n        Map<String, Object> userData = attributes().userData();\n        WeakReference<List<Element>> ref = new WeakReference<>(els);\n        userData.put(childElsKey, ref);\n        userData.put(childElsMod, childNodes.modCount());\n    }\n\n    /**\n     Returns a Stream of this Element and all of its descendant Elements. The stream has document order.\n     @return a stream of this element and its descendants.\n     @see #nodeStream()\n     @since 1.17.1\n     */\n    public Stream<Element> stream() {\n        return NodeUtils.stream(this, Element.class);\n    }\n\n    private <T> List<T> filterNodes(Class<T> clazz) {\n        return childNodes.stream()\n                .filter(clazz::isInstance)\n                .map(clazz::cast)\n                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));\n    }\n\n    /**\n     * Get this element's child text nodes. The list is unmodifiable but the text nodes may be manipulated.\n     * <p>\n     * This is effectively a filter on {@link #childNodes()} to get Text nodes.\n     * @return child text nodes. If this element has no text nodes, returns an\n     * empty list.\n     * </p>\n     * For example, with the input HTML: {@code <p>One <span>Two</span> Three <br> Four</p>} with the {@code p} element selected:\n     * <ul>\n     *     <li>{@code p.text()} = {@code \"One Two Three Four\"}</li>\n     *     <li>{@code p.ownText()} = {@code \"One Three Four\"}</li>\n     *     <li>{@code p.children()} = {@code Elements[<span>, <br>]}</li>\n     *     <li>{@code p.childNodes()} = {@code List<Node>[\"One \", <span>, \" Three \", <br>, \" Four\"]}</li>\n     *     <li>{@code p.textNodes()} = {@code List<TextNode>[\"One \", \" Three \", \" Four\"]}</li>\n     * </ul>\n     */\n    public List<TextNode> textNodes() {\n        return filterNodes(TextNode.class);\n    }\n\n    /**\n     * Get this element's child data nodes. The list is unmodifiable but the data nodes may be manipulated.\n     * <p>\n     * This is effectively a filter on {@link #childNodes()} to get Data nodes.\n     * </p>\n     * @return child data nodes. If this element has no data nodes, returns an\n     * empty list.\n     * @see #data()\n     */\n    public List<DataNode> dataNodes() {\n        return filterNodes(DataNode.class);\n    }\n\n    /**\n     * Find elements that match the {@link Selector} CSS query, with this element as the starting context. Matched elements\n     * may include this element, or any of its descendents.\n     * <p>If the query starts with a combinator (e.g. {@code *} or {@code >}), that will combine to this element.</p>\n     * <p>This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because\n     * multiple filters can be combined, e.g.:</p>\n     * <ul>\n     * <li>{@code el.select(\"a[href]\")} - finds links ({@code a} tags with {@code href} attributes)</li>\n     * <li>{@code el.select(\"a[href*=example.com]\")} - finds links pointing to example.com (loosely)</li>\n     * <li>{@code el.select(\"* div\")} - finds all divs that descend from this element (and excludes this element)</li>\n     * <li>{@code el.select(\"> div\")} - finds all divs that are direct children of this element (and excludes this element)</li>\n     * </ul>\n     * <p>See the query syntax documentation in {@link org.jsoup.select.Selector}.</p>\n     * <p>Also known as {@code querySelectorAll()} in the Web DOM.</p>\n     *\n     * @param cssQuery a {@link Selector} CSS-like query\n     * @return an {@link Elements} list containing elements that match the query (empty if none match)\n     * @see Selector selector query syntax\n     * @see #select(Evaluator)\n     * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.\n     */\n    public Elements select(String cssQuery) {\n        return Selector.select(cssQuery, this);\n    }\n\n    /**\n     * Find elements that match the supplied Evaluator. This has the same functionality as {@link #select(String)}, but\n     * may be useful if you are running the same query many times (on many documents) and want to save the overhead of\n     * repeatedly parsing the CSS query.\n     * @param evaluator an element evaluator\n     * @return an {@link Elements} list containing elements that match the query (empty if none match)\n     * @see Selector#evaluatorOf(String css)\n     */\n    public Elements select(Evaluator evaluator) {\n        return Selector.select(evaluator, this);\n    }\n\n    /**\n     Selects elements from the given root that match the specified {@link Selector} CSS query, with this element as the\n     starting context, and returns them as a lazy Stream. Matched elements may include this element, or any of its\n     children.\n     <p>\n     Unlike {@link #select(String query)}, which returns a complete list of all matching elements, this method returns a\n     {@link Stream} that processes elements lazily as they are needed. The stream operates in a \"pull\" model — elements\n     are fetched from the root as the stream is traversed. You can use standard {@code Stream} operations such as\n     {@code filter}, {@code map}, or {@code findFirst} to process elements on demand.\n     </p>\n\n     @param cssQuery a {@link Selector} CSS-like query\n     @return a {@link Stream} containing elements that match the query (empty if none match)\n     @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.\n     @see Selector selector query syntax\n     @see #selectStream(Evaluator eval)\n     @since 1.19.1\n     */\n    public Stream<Element> selectStream(String cssQuery) {\n        return Selector.selectStream(cssQuery, this);\n    }\n\n    /**\n     Find a Stream of elements that match the supplied Evaluator.\n\n     @param evaluator an element Evaluator\n     @return a {@link Stream} containing elements that match the query (empty if none match)\n     @see Selector#evaluatorOf(String css)\n     @since 1.19.1\n     */\n    public Stream<Element> selectStream(Evaluator evaluator) {\n        return Selector.selectStream(evaluator, this);\n    }\n\n    /**\n     * Find the first Element that matches the {@link Selector} CSS query, with this element as the starting context.\n     * <p>This is effectively the same as calling {@code element.select(query).first()}, but is more efficient as query\n     * execution stops on the first hit.</p>\n     * <p>Also known as {@code querySelector()} in the Web DOM.</p>\n     * @param cssQuery cssQuery a {@link Selector} CSS-like query\n     * @return the first matching element, or <b>{@code null}</b> if there is no match.\n     * @see #expectFirst(String)\n     */\n    public @Nullable Element selectFirst(String cssQuery) {\n        return Selector.selectFirst(cssQuery, this);\n    }\n\n    /**\n     * Finds the first Element that matches the supplied Evaluator, with this element as the starting context, or\n     * {@code null} if none match.\n     *\n     * @param evaluator an element evaluator\n     * @return the first matching element (walking down the tree, starting from this element), or {@code null} if none\n     * match.\n     */\n    public @Nullable Element selectFirst(Evaluator evaluator) {\n        return Collector.findFirst(evaluator, this);\n    }\n\n    /**\n     Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}. This\n     is useful if you want to simply abort processing on a failed match.\n     @param cssQuery a {@link Selector} CSS-like query\n     @return the first matching element\n     @throws IllegalArgumentException if no match is found\n     @since 1.15.2\n     */\n    public Element expectFirst(String cssQuery) {\n        return Validate.expectNotNull(\n            Selector.selectFirst(cssQuery, this),\n            parent() != null ?\n                \"No elements matched the query '%s' on element '%s'.\" :\n                \"No elements matched the query '%s' in the document.\"\n            , cssQuery, this.tagName()\n        );\n    }\n\n    /**\n     Find nodes that match the supplied {@link Evaluator}, with this element as the starting context. Matched\n     nodes may include this element, or any of its descendents.\n\n     @param evaluator an evaluator\n     @return a list of nodes that match the query (empty if none match)\n     @since 1.21.1\n     */\n    public Nodes<Node> selectNodes(Evaluator evaluator) {\n        return selectNodes(evaluator, Node.class);\n    }\n\n    /**\n     Find nodes that match the supplied {@link Selector} CSS query, with this element as the starting context. Matched\n     nodes may include this element, or any of its descendents.\n     <p>To select leaf nodes, the query should specify the node type, e.g. {@code ::text},\n     {@code ::comment}, {@code ::data}, {@code ::leafnode}.</p>\n\n     @param cssQuery a {@link Selector} CSS query\n     @return a list of nodes that match the query (empty if none match)\n     @since 1.21.1\n     */\n    public Nodes<Node> selectNodes(String cssQuery) {\n        return selectNodes(cssQuery, Node.class);\n    }\n\n    /**\n     Find nodes that match the supplied Evaluator, with this element as the starting context. Matched\n     nodes may include this element, or any of its descendents.\n\n     @param evaluator an evaluator\n     @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc)\n     @param <T> the type of node to collect\n     @return a list of nodes that match the query (empty if none match)\n     @since 1.21.1\n     */\n    public <T extends Node> Nodes<T> selectNodes(Evaluator evaluator, Class<T> type) {\n        Validate.notNull(evaluator);\n        return Collector.collectNodes(evaluator, this, type);\n    }\n\n    /**\n     Find nodes that match the supplied {@link Selector} CSS query, with this element as the starting context. Matched\n     nodes may include this element, or any of its descendents.\n     <p>To select specific node types, use {@code ::text}, {@code ::comment}, {@code ::leafnode}, etc. For example, to\n     select all text nodes under {@code p} elements: </p>\n     <pre>    Nodes&lt;TextNode&gt; textNodes = doc.selectNodes(\"p ::text\", TextNode.class);</pre>\n\n     @param cssQuery a {@link Selector} CSS query\n     @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc)\n     @param <T> the type of node to collect\n     @return a list of nodes that match the query (empty if none match)\n     @since 1.21.1\n     */\n    public <T extends Node> Nodes<T> selectNodes(String cssQuery, Class<T> type) {\n        Validate.notEmpty(cssQuery);\n        return selectNodes(evaluatorOf(cssQuery), type);\n    }\n\n    /**\n     Find the first Node that matches the {@link Selector} CSS query, with this element as the starting context.\n     <p>This is effectively the same as calling {@code element.selectNodes(query).first()}, but is more efficient as\n     query\n     execution stops on the first hit.</p>\n     <p>Also known as {@code querySelector()} in the Web DOM.</p>\n\n     @param cssQuery cssQuery a {@link Selector} CSS-like query\n     @return the first matching node, or <b>{@code null}</b> if there is no match.\n     @since 1.21.1\n     @see #expectFirst(String)\n     */\n    public @Nullable <T extends Node> T selectFirstNode(String cssQuery, Class<T> type) {\n        return selectFirstNode(evaluatorOf(cssQuery), type);\n    }\n\n    /**\n     Finds the first Node that matches the supplied Evaluator, with this element as the starting context, or\n     {@code null} if none match.\n\n     @param evaluator an element evaluator\n     @return the first matching node (walking down the tree, starting from this element), or {@code null} if none\n     match.\n     @since 1.21.1\n     */\n    public @Nullable <T extends Node> T selectFirstNode(Evaluator evaluator, Class<T> type) {\n        return Collector.findFirstNode(evaluator, this, type);\n    }\n\n    /**\n     Just like {@link #selectFirstNode(String, Class)}, but if there is no match, throws an\n     {@link IllegalArgumentException}. This is useful if you want to simply abort processing on a failed match.\n\n     @param cssQuery a {@link Selector} CSS-like query\n     @return the first matching node\n     @throws IllegalArgumentException if no match is found\n     @since 1.21.1\n     */\n    public <T extends Node> T expectFirstNode(String cssQuery, Class<T> type) {\n        return Validate.expectNotNull(\n            selectFirstNode(cssQuery, type),\n            parent() != null ?\n                \"No nodes matched the query '%s' on element '%s'.\":\n                \"No nodes matched the query '%s' in the document.\"\n            , cssQuery, this.tagName()\n        );\n    }\n\n    /**\n     * Checks if this element matches the given {@link Selector} CSS query. Also knows as {@code matches()} in the Web\n     * DOM.\n     *\n     * @param cssQuery a {@link Selector} CSS query\n     * @return if this element matches the query\n     */\n    public boolean is(String cssQuery) {\n        return is(evaluatorOf(cssQuery));\n    }\n\n    /**\n     * Check if this element matches the given evaluator.\n     * @param evaluator an element evaluator\n     * @return if this element matches\n     */\n    public boolean is(Evaluator evaluator) {\n        return evaluator.matches(this.root(), this);\n    }\n\n    /**\n     * Find the closest element up the tree of parents that matches the specified CSS query. Will return itself, an\n     * ancestor, or {@code null} if there is no such matching element.\n     * @param cssQuery a {@link Selector} CSS query\n     * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not\n     * found.\n     */\n    public @Nullable Element closest(String cssQuery) {\n        return closest(evaluatorOf(cssQuery));\n    }\n\n    /**\n     * Find the closest element up the tree of parents that matches the specified evaluator. Will return itself, an\n     * ancestor, or {@code null} if there is no such matching element.\n     * @param evaluator a query evaluator\n     * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not\n     * found.\n     */\n    public @Nullable Element closest(Evaluator evaluator) {\n        Validate.notNull(evaluator);\n        Element el = this;\n        final Element root = root();\n        do {\n            if (evaluator.matches(root, el))\n                return el;\n            el = el.parent();\n        } while (el != null);\n        return null;\n    }\n\n    /**\n     Find Elements that match the supplied {@index XPath} expression.\n     <p>Note that for convenience of writing the Xpath expression, namespaces are disabled, and queries can be\n     expressed using the element's local name only.</p>\n     <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an\n     alternate XPathFactory implementation:</p>\n     <ol>\n     <li>Add the implementation to your classpath. E.g. to use <a href=\"https://www.saxonica.com/products/products.xml\">Saxon-HE</a>, add <a href=\"https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE\">net.sf.saxon:Saxon-HE</a> to your build.</li>\n     <li>Set the system property <code>javax.xml.xpath.XPathFactory:jsoup</code> to the implementing classname. E.g.:<br>\n     <code>System.setProperty(W3CDom.XPathFactoryProperty, \"net.sf.saxon.xpath.XPathFactoryImpl\");</code>\n     </li>\n     </ol>\n\n     @param xpath XPath expression\n     @return matching elements, or an empty list if none match.\n     @see #selectXpath(String, Class)\n     @since 1.14.3\n     */\n    public Elements selectXpath(String xpath) {\n        return new Elements(NodeUtils.selectXpath(xpath, this, Element.class));\n    }\n\n    /**\n     Find Nodes that match the supplied XPath expression.\n     <p>For example, to select TextNodes under {@code p} elements: </p>\n     <pre>List&lt;TextNode&gt; textNodes = doc.selectXpath(\"//body//p//text()\", TextNode.class);</pre>\n     <p>Note that in the jsoup DOM, Attribute objects are not Nodes. To directly select attribute values, do something\n     like:</p>\n     <pre>List&lt;String&gt; hrefs = doc.selectXpath(\"//a\").eachAttr(\"href\");</pre>\n     @param xpath XPath expression\n     @param nodeType the jsoup node type to return\n     @see #selectXpath(String)\n     @return a list of matching nodes\n     @since 1.14.3\n     */\n    public <T extends Node> List<T> selectXpath(String xpath, Class<T> nodeType) {\n        return NodeUtils.selectXpath(xpath, this, nodeType);\n    }\n\n    /**\n     * Insert a node to the end of this Element's children. The incoming node will be re-parented.\n     *\n     * @param child node to add.\n     * @return this Element, for chaining\n     * @see #prependChild(Node)\n     * @see #insertChildren(int, Collection)\n     */\n    public Element appendChild(Node child) {\n        Validate.notNull(child);\n\n        // was - Node#addChildren(child). short-circuits an array create and a loop.\n        reparentChild(child);\n        ensureChildNodes();\n        childNodes.add(child);\n        child.setSiblingIndex(childNodes.size() - 1);\n        return this;\n    }\n\n    /**\n     Insert the given nodes to the end of this Element's children.\n\n     @param children nodes to add\n     @return this Element, for chaining\n     @see #insertChildren(int, Collection)\n     */\n    public Element appendChildren(Collection<? extends Node> children) {\n        insertChildren(-1, children);\n        return this;\n    }\n\n    /**\n     * Add this element to the supplied parent element, as its next child.\n     *\n     * @param parent element to which this element will be appended\n     * @return this element, so that you can continue modifying the element\n     */\n    public Element appendTo(Element parent) {\n        Validate.notNull(parent);\n        parent.appendChild(this);\n        return this;\n    }\n\n    /**\n     * Add a node to the start of this element's children.\n     *\n     * @param child node to add.\n     * @return this element, so that you can add more child nodes or elements.\n     */\n    public Element prependChild(Node child) {\n        Validate.notNull(child);\n\n        addChildren(0, child);\n        return this;\n    }\n\n    /**\n     Insert the given nodes to the start of this Element's children.\n\n     @param children nodes to add\n     @return this Element, for chaining\n     @see #insertChildren(int, Collection)\n     */\n    public Element prependChildren(Collection<? extends Node> children) {\n        insertChildren(0, children);\n        return this;\n    }\n\n\n    /**\n     * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the\n     * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first.\n     *\n     * @param index 0-based index to insert children at. Specify {@code 0} to insert at the start, {@code -1} at the\n     * end\n     * @param children child nodes to insert\n     * @return this element, for chaining.\n     */\n    public Element insertChildren(int index, Collection<? extends Node> children) {\n        Validate.notNull(children, \"Children collection to be inserted must not be null.\");\n        int currentSize = childNodeSize();\n        if (index < 0) index += currentSize +1; // roll around\n        Validate.isTrue(index >= 0 && index <= currentSize, \"Insert position out of bounds.\");\n        addChildren(index, children.toArray(new Node[0]));\n        return this;\n    }\n\n    /**\n     * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the\n     * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first.\n     *\n     * @param index 0-based index to insert children at. Specify {@code 0} to insert at the start, {@code -1} at the\n     * end\n     * @param children child nodes to insert\n     * @return this element, for chaining.\n     */\n    public Element insertChildren(int index, Node... children) {\n        Validate.notNull(children, \"Children collection to be inserted must not be null.\");\n        int currentSize = childNodeSize();\n        if (index < 0) index += currentSize +1; // roll around\n        Validate.isTrue(index >= 0 && index <= currentSize, \"Insert position out of bounds.\");\n\n        addChildren(index, children);\n        return this;\n    }\n\n    /**\n     * Create a new element by tag name, and add it as this Element's last child.\n     *\n     * @param tagName the name of the tag (e.g. {@code div}).\n     * @return the new element, to allow you to add content to it, e.g.:\n     *  {@code parent.appendElement(\"h1\").attr(\"id\", \"header\").text(\"Welcome\");}\n     */\n    public Element appendElement(String tagName) {\n        return appendElement(tagName, tag.namespace());\n    }\n\n    /**\n     * Create a new element by tag name and namespace, add it as this Element's last child.\n     *\n     * @param tagName the name of the tag (e.g. {@code div}).\n     * @param namespace the namespace of the tag (e.g. {@link Parser#NamespaceHtml})\n     * @return the new element, in the specified namespace\n     */\n    public Element appendElement(String tagName, String namespace) {\n        Parser parser = NodeUtils.parser(this);\n        Element child = new Element(parser.tagSet().valueOf(tagName, namespace, parser.settings()), baseUri());\n        appendChild(child);\n        return child;\n    }\n\n    /**\n     * Create a new element by tag name, and add it as this Element's first child.\n     *\n     * @param tagName the name of the tag (e.g. {@code div}).\n     * @return the new element, to allow you to add content to it, e.g.:\n     *  {@code parent.prependElement(\"h1\").attr(\"id\", \"header\").text(\"Welcome\");}\n     */\n    public Element prependElement(String tagName) {\n        return prependElement(tagName, tag.namespace());\n    }\n\n    /**\n     * Create a new element by tag name and namespace, and add it as this Element's first child.\n     *\n     * @param tagName the name of the tag (e.g. {@code div}).\n     * @param namespace the namespace of the tag (e.g. {@link Parser#NamespaceHtml})\n     * @return the new element, in the specified namespace\n     */\n    public Element prependElement(String tagName, String namespace) {\n        Parser parser = NodeUtils.parser(this);\n        Element child = new Element(parser.tagSet().valueOf(tagName, namespace, parser.settings()), baseUri());\n        prependChild(child);\n        return child;\n    }\n\n    /**\n     * Create and append a new TextNode to this element.\n     *\n     * @param text the (un-encoded) text to add\n     * @return this element\n     */\n    public Element appendText(String text) {\n        Validate.notNull(text);\n        TextNode node = new TextNode(text);\n        appendChild(node);\n        return this;\n    }\n\n    /**\n     * Create and prepend a new TextNode to this element.\n     *\n     * @param text the decoded text to add\n     * @return this element\n     */\n    public Element prependText(String text) {\n        Validate.notNull(text);\n        TextNode node = new TextNode(text);\n        prependChild(node);\n        return this;\n    }\n\n    /**\n     * Add inner HTML to this element. The supplied HTML will be parsed, and each node appended to the end of the children.\n     * @param html HTML to add inside this element, after the existing HTML\n     * @return this element\n     * @see #html(String)\n     */\n    public Element append(String html) {\n        Validate.notNull(html);\n        List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri());\n        addChildren(nodes.toArray(new Node[0]));\n        return this;\n    }\n\n    /**\n     * Add inner HTML into this element. The supplied HTML will be parsed, and each node prepended to the start of the element's children.\n     * @param html HTML to add inside this element, before the existing HTML\n     * @return this element\n     * @see #html(String)\n     */\n    public Element prepend(String html) {\n        Validate.notNull(html);\n        List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri());\n        addChildren(0, nodes.toArray(new Node[0]));\n        return this;\n    }\n\n    /**\n     * Insert the specified HTML into the DOM before this element (as a preceding sibling).\n     *\n     * @param html HTML to add before this element\n     * @return this element, for chaining\n     * @see #after(String)\n     */\n    @Override\n    public Element before(String html) {\n        return (Element) super.before(html);\n    }\n\n    /**\n     * Insert the specified node into the DOM before this node (as a preceding sibling).\n     * @param node to add before this element\n     * @return this Element, for chaining\n     * @see #after(Node)\n     */\n    @Override\n    public Element before(Node node) {\n        return (Element) super.before(node);\n    }\n\n    /**\n     * Insert the specified HTML into the DOM after this element (as a following sibling).\n     *\n     * @param html HTML to add after this element\n     * @return this element, for chaining\n     * @see #before(String)\n     */\n    @Override\n    public Element after(String html) {\n        return (Element) super.after(html);\n    }\n\n    /**\n     * Insert the specified node into the DOM after this node (as a following sibling).\n     * @param node to add after this element\n     * @return this element, for chaining\n     * @see #before(Node)\n     */\n    @Override\n    public Element after(Node node) {\n        return (Element) super.after(node);\n    }\n\n    /**\n     * Remove all the element's child nodes. Any attributes are left as-is. Each child node has its parent set to\n     * {@code null}.\n     * @return this element\n     */\n    @Override\n    public Element empty() {\n        // Detach each of the children -> parent links:\n        int size = childNodes.size();\n        for (int i = 0; i < size; i++)\n            childNodes.get(i).parentNode = null;\n        childNodes.clear();\n        return this;\n    }\n\n    /**\n     * Wrap the supplied HTML around this element.\n     *\n     * @param html HTML to wrap around this element, e.g. {@code <div class=\"head\"></div>}. Can be arbitrarily deep.\n     * @return this element, for chaining.\n     */\n    @Override\n    public Element wrap(String html) {\n        return (Element) super.wrap(html);\n    }\n\n    /**\n     Gets an #id selector for this element, if it has a unique ID. Otherwise, returns an empty string.\n\n     @param ownerDoc the document that owns this element, if there is one\n     */\n    private String uniqueIdSelector(@Nullable Document ownerDoc) {\n        String id = id();\n        if (!id.isEmpty()) { // check if the ID is unique and matches this\n            String idSel = \"#\" + escapeCssIdentifier(id);\n            if (ownerDoc != null) {\n                Elements els = ownerDoc.select(idSel);\n                if (els.size() == 1 && els.get(0) == this) return idSel;\n            } else {\n                return idSel;\n            }\n        }\n        return EmptyString;\n    }\n\n    /**\n     Get a CSS selector that will uniquely select this element.\n     <p>\n     If the element has an ID, returns #id; otherwise returns the parent (if any) CSS selector, followed by\n     {@literal '>'}, followed by a unique selector for the element (tag.class.class:nth-child(n)).\n     </p>\n\n     @return the CSS Path that can be used to retrieve the element in a selector.\n     */\n    public String cssSelector() {\n        Document ownerDoc = ownerDocument();\n        String idSel = uniqueIdSelector(ownerDoc);\n        if (!idSel.isEmpty()) return idSel;\n\n        // No unique ID, work up the parent stack and find either a unique ID to hang from, or just a GP > Parent > Child chain\n        StringBuilder selector = StringUtil.borrowBuilder();\n        Element el = this;\n        while (el != null && !(el instanceof Document)) {\n            idSel = el.uniqueIdSelector(ownerDoc);\n            if (!idSel.isEmpty()) {\n                selector.insert(0, idSel);\n                break; // found a unique ID to use as ancestor; stop\n            }\n            selector.insert(0, el.cssSelectorComponent());\n            el = el.parent();\n        }\n        return StringUtil.releaseBuilder(selector);\n    }\n\n    private String cssSelectorComponent() {\n        // Escape tagname, and translate HTML namespace ns:tag to CSS namespace syntax ns|tag\n        String tagName = escapeCssIdentifier(tagName()).replace(\"\\\\:\", \"|\");\n        StringBuilder selector = StringUtil.borrowBuilder().append(tagName);\n        String classes = classNames().stream().map(TokenQueue::escapeCssIdentifier)\n                .collect(StringUtil.joining(\".\"));\n        if (!classes.isEmpty())\n            selector.append('.').append(classes);\n\n        if (parent() == null || parent() instanceof Document) // don't add Document to selector, as will always have a html node\n            return StringUtil.releaseBuilder(selector);\n\n        selector.insert(0, \" > \");\n        if (parent().select(selector.toString()).size() > 1)\n            selector.append(String.format(\n                \":nth-child(%d)\", elementSiblingIndex() + 1));\n\n        return StringUtil.releaseBuilder(selector);\n    }\n\n    /**\n     * Get sibling elements. If the element has no sibling elements, returns an empty list. An element is not a sibling\n     * of itself, so will not be included in the returned list.\n     * @return sibling elements\n     */\n    public Elements siblingElements() {\n        if (parentNode == null)\n            return new Elements(0);\n\n        List<Element> elements = parent().childElementsList();\n        Elements siblings = new Elements(elements.size() - 1);\n        for (Element el: elements)\n            if (el != this)\n                siblings.add(el);\n        return siblings;\n    }\n\n\n\n    /**\n     * Get each of the sibling elements that come after this element.\n     *\n     * @return each of the element siblings after this element, or an empty list if there are no next sibling elements\n     */\n    public Elements nextElementSiblings() {\n        return nextElementSiblings(true);\n    }\n\n    /**\n     * Get each of the element siblings before this element.\n     *\n     * @return the previous element siblings, or an empty list if there are none.\n     */\n    public Elements previousElementSiblings() {\n        return nextElementSiblings(false);\n    }\n\n    private Elements nextElementSiblings(boolean next) {\n        Elements els = new Elements();\n        if (parentNode == null)\n            return  els;\n        els.add(this);\n        return next ?  els.nextAll() : els.prevAll();\n    }\n\n    /**\n     * Gets the first Element sibling of this element. That may be this element.\n     * @return the first sibling that is an element (aka the parent's first element child)\n     */\n    public Element firstElementSibling() {\n        if (parent() != null) {\n            //noinspection DataFlowIssue (not nullable, would be this is no other sibs)\n            return parent().firstElementChild();\n        } else\n            return this; // orphan is its own first sibling\n    }\n\n    /**\n     * Get the list index of this element in its element sibling list. I.e. if this is the first element\n     * sibling, returns 0.\n     * @return position in element sibling list\n     */\n    public int elementSiblingIndex() {\n       if (parent() == null) return 0;\n       return indexInList(this, parent().childElementsList());\n    }\n\n    /**\n     * Gets the last element sibling of this element. That may be this element.\n     * @return the last sibling that is an element (aka the parent's last element child)\n     */\n    public Element lastElementSibling() {\n        if (parent() != null) {\n            //noinspection DataFlowIssue (not nullable, would be this if no other sibs)\n            return parent().lastElementChild();\n        } else\n            return this;\n    }\n\n    private static <E extends Element> int indexInList(Element search, List<E> elements) {\n        final int size = elements.size();\n        for (int i = 0; i < size; i++) {\n            if (elements.get(i) == search)\n                return i;\n        }\n        return 0;\n    }\n\n    /**\n     Gets the first child of this Element that is an Element, or {@code null} if there is none.\n     @return the first Element child node, or null.\n     @see #firstChild()\n     @see #lastElementChild()\n     @since 1.15.2\n     */\n    public @Nullable Element firstElementChild() {\n        int size = childNodes.size();\n        for (int i = 0; i < size; i++) {\n            Node node = childNodes.get(i);\n            if (node instanceof Element) return (Element) node;\n        }\n        return null;\n    }\n\n    /**\n     Gets the last child of this Element that is an Element, or @{code null} if there is none.\n     @return the last Element child node, or null.\n     @see #lastChild()\n     @see #firstElementChild()\n     @since 1.15.2\n     */\n    public @Nullable Element lastElementChild() {\n        for (int i = childNodes.size() - 1; i >= 0; i--) {\n            Node node = childNodes.get(i);\n            if (node instanceof Element) return (Element) node;\n        }\n        return null;\n    }\n\n    // DOM type methods\n\n    /**\n     * Finds elements, including and recursively under this element, with the specified tag name.\n     * @param tagName The tag name to search for (case insensitively).\n     * @return a matching unmodifiable list of elements. Will be empty if this element and none of its children match.\n     */\n    public Elements getElementsByTag(String tagName) {\n        Validate.notEmpty(tagName);\n        tagName = normalize(tagName);\n\n        return Collector.collect(new Evaluator.Tag(tagName), this);\n    }\n\n    /**\n     * Find an element by ID, including or under this element.\n     * <p>\n     * Note that this finds the first matching ID, starting with this element. If you search down from a different\n     * starting point, it is possible to find a different element by ID. For unique element by ID within a Document,\n     * use {@link Document#getElementById(String)}\n     * @param id The ID to search for.\n     * @return The first matching element by ID, starting with this element, or null if none found.\n     */\n    public @Nullable Element getElementById(String id) {\n        Validate.notEmpty(id);\n        return Collector.findFirst(new Evaluator.Id(id), this);\n    }\n\n    /**\n     * Find elements that have this class, including or under this element. Case-insensitive.\n     * <p>\n     * Elements can have multiple classes (e.g. {@code <div class=\"header round first\">}). This method\n     * checks each class, so you can find the above with {@code el.getElementsByClass(\"header\");}.\n     *\n     * @param className the name of the class to search for.\n     * @return elements with the supplied class name, empty if none\n     * @see #hasClass(String)\n     * @see #classNames()\n     */\n    public Elements getElementsByClass(String className) {\n        Validate.notEmpty(className);\n\n        return Collector.collect(new Evaluator.Class(className), this);\n    }\n\n    /**\n     * Find elements that have a named attribute set. Case-insensitive.\n     *\n     * @param key name of the attribute, e.g. {@code href}\n     * @return elements that have this attribute, empty if none\n     */\n    public Elements getElementsByAttribute(String key) {\n        Validate.notEmpty(key);\n        key = key.trim();\n\n        return Collector.collect(new Evaluator.Attribute(key), this);\n    }\n\n    /**\n     * Find elements that have an attribute name starting with the supplied prefix. Use {@code data-} to find elements\n     * that have HTML5 datasets.\n     * @param keyPrefix name prefix of the attribute e.g. {@code data-}\n     * @return elements that have attribute names that start with the prefix, empty if none.\n     */\n    public Elements getElementsByAttributeStarting(String keyPrefix) {\n        Validate.notEmpty(keyPrefix);\n        keyPrefix = keyPrefix.trim();\n\n        return Collector.collect(new Evaluator.AttributeStarting(keyPrefix), this);\n    }\n\n    /**\n     * Find elements that have an attribute with the specific value. Case-insensitive.\n     *\n     * @param key name of the attribute\n     * @param value value of the attribute\n     * @return elements that have this attribute with this value, empty if none\n     */\n    public Elements getElementsByAttributeValue(String key, String value) {\n        return Collector.collect(new Evaluator.AttributeWithValue(key, value), this);\n    }\n\n    /**\n     * Find elements that either do not have this attribute, or have it with a different value. Case-insensitive.\n     *\n     * @param key name of the attribute\n     * @param value value of the attribute\n     * @return elements that do not have a matching attribute\n     */\n    public Elements getElementsByAttributeValueNot(String key, String value) {\n        return Collector.collect(new Evaluator.AttributeWithValueNot(key, value), this);\n    }\n\n    /**\n     * Find elements that have attributes that start with the value prefix. Case-insensitive.\n     *\n     * @param key name of the attribute\n     * @param valuePrefix start of attribute value\n     * @return elements that have attributes that start with the value prefix\n     */\n    public Elements getElementsByAttributeValueStarting(String key, String valuePrefix) {\n        return Collector.collect(new Evaluator.AttributeWithValueStarting(key, valuePrefix), this);\n    }\n\n    /**\n     * Find elements that have attributes that end with the value suffix. Case-insensitive.\n     *\n     * @param key name of the attribute\n     * @param valueSuffix end of the attribute value\n     * @return elements that have attributes that end with the value suffix\n     */\n    public Elements getElementsByAttributeValueEnding(String key, String valueSuffix) {\n        return Collector.collect(new Evaluator.AttributeWithValueEnding(key, valueSuffix), this);\n    }\n\n    /**\n     * Find elements that have attributes whose value contains the match string. Case-insensitive.\n     *\n     * @param key name of the attribute\n     * @param match substring of value to search for\n     * @return elements that have attributes containing this text\n     */\n    public Elements getElementsByAttributeValueContaining(String key, String match) {\n        return Collector.collect(new Evaluator.AttributeWithValueContaining(key, match), this);\n    }\n\n    /**\n     * Find elements that have an attribute whose value matches the supplied regular expression.\n     * @param key name of the attribute\n     * @param pattern compiled regular expression to match against attribute values\n     * @return elements that have attributes matching this regular expression\n     */\n    public Elements getElementsByAttributeValueMatching(String key, Pattern pattern) {\n        return Collector.collect(new Evaluator.AttributeWithValueMatching(key, pattern), this);\n    }\n\n    /**\n     * Find elements that have attributes whose values match the supplied regular expression.\n     * @param key name of the attribute\n     * @param regex regular expression to match against attribute values. You can use <a href=\"http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded\">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options.\n     * @return elements that have attributes matching this regular expression\n     */\n    public Elements getElementsByAttributeValueMatching(String key, String regex) {\n        Regex pattern;\n        try {\n            pattern = Regex.compile(regex);\n        } catch (PatternSyntaxException e) {\n            throw new IllegalArgumentException(\"Pattern syntax error: \" + regex, e);\n        }\n        return Collector.collect(new Evaluator.AttributeWithValueMatching(key, pattern), this);\n    }\n\n    /**\n     * Find elements whose sibling index is less than the supplied index.\n     * @param index 0-based index\n     * @return elements less than index\n     */\n    public Elements getElementsByIndexLessThan(int index) {\n        return Collector.collect(new Evaluator.IndexLessThan(index), this);\n    }\n\n    /**\n     * Find elements whose sibling index is greater than the supplied index.\n     * @param index 0-based index\n     * @return elements greater than index\n     */\n    public Elements getElementsByIndexGreaterThan(int index) {\n        return Collector.collect(new Evaluator.IndexGreaterThan(index), this);\n    }\n\n    /**\n     * Find elements whose sibling index is equal to the supplied index.\n     * @param index 0-based index\n     * @return elements equal to index\n     */\n    public Elements getElementsByIndexEquals(int index) {\n        return Collector.collect(new Evaluator.IndexEquals(index), this);\n    }\n\n    /**\n     * Find elements that contain the specified string. The search is case-insensitive. The text may appear directly\n     * in the element, or in any of its descendants.\n     * @param searchText to look for in the element's text\n     * @return elements that contain the string, case-insensitive.\n     * @see Element#text()\n     */\n    public Elements getElementsContainingText(String searchText) {\n        return Collector.collect(new Evaluator.ContainsText(searchText), this);\n    }\n\n    /**\n     * Find elements that directly contain the specified string. The search is case-insensitive. The text must appear directly\n     * in the element, not in any of its descendants.\n     * @param searchText to look for in the element's own text\n     * @return elements that contain the string, case-insensitive.\n     * @see Element#ownText()\n     */\n    public Elements getElementsContainingOwnText(String searchText) {\n        return Collector.collect(new Evaluator.ContainsOwnText(searchText), this);\n    }\n\n    /**\n     * Find elements whose text matches the supplied regular expression.\n     * @param pattern regular expression to match text against\n     * @return elements matching the supplied regular expression.\n     * @see Element#text()\n     */\n    public Elements getElementsMatchingText(Pattern pattern) {\n        return Collector.collect(new Evaluator.Matches(pattern), this);\n    }\n\n    /**\n     * Find elements whose text matches the supplied regular expression.\n     * @param regex regular expression to match text against. You can use <a href=\"http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded\">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options.\n     * @return elements matching the supplied regular expression.\n     * @see Element#text()\n     */\n    public Elements getElementsMatchingText(String regex) {\n        Regex pattern;\n        try {\n            pattern = Regex.compile(regex);\n        } catch (PatternSyntaxException e) {\n            throw new IllegalArgumentException(\"Pattern syntax error: \" + regex, e);\n        }\n        return Collector.collect(new Evaluator.Matches(pattern), this);\n    }\n\n    /**\n     * Find elements whose own text matches the supplied regular expression.\n     * @param pattern regular expression to match text against\n     * @return elements matching the supplied regular expression.\n     * @see Element#ownText()\n     */\n    public Elements getElementsMatchingOwnText(Pattern pattern) {\n        return Collector.collect(new Evaluator.MatchesOwn(pattern), this);\n    }\n\n    /**\n     * Find elements whose own text matches the supplied regular expression.\n     * @param regex regular expression to match text against. You can use <a href=\"http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded\">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options.\n     * @return elements matching the supplied regular expression.\n     * @see Element#ownText()\n     */\n    public Elements getElementsMatchingOwnText(String regex) {\n        Regex pattern;\n        try {\n            pattern = Regex.compile(regex);\n        } catch (PatternSyntaxException e) {\n            throw new IllegalArgumentException(\"Pattern syntax error: \" + regex, e);\n        }\n        return Collector.collect(new Evaluator.MatchesOwn(pattern), this);\n    }\n\n    /**\n     * Find all elements under this element (including self, and children of children).\n     *\n     * @return all elements\n     */\n    public Elements getAllElements() {\n        return Collector.collect(new Evaluator.AllElements(), this);\n    }\n\n    /**\n     Gets the <b>normalized, combined text</b> of this element and all its children. Whitespace is normalized and\n     trimmed.\n     <p>For example, given HTML {@code <p>Hello  <b>there</b> now! </p>}, {@code p.text()} returns {@code \"Hello there\n    now!\"}\n     <p>If you do not want normalized text, use {@link #wholeText()}. If you want just the text of this node (and not\n     children), use {@link #ownText()}\n     <p>Note that this method returns the textual content that would be presented to a reader. The contents of data\n     nodes (such as {@code <script>} tags) are not considered text. Use {@link #data()} or {@link #html()} to retrieve\n     that content.\n\n     @return decoded, normalized text, or empty string if none.\n     @see #wholeText()\n     @see #ownText()\n     @see #textNodes()\n     */\n    public String text() {\n        final StringBuilder accum = StringUtil.borrowBuilder();\n        new TextAccumulator(accum).traverse(this);\n        return StringUtil.releaseBuilder(accum).trim();\n    }\n\n    private static class TextAccumulator implements NodeVisitor {\n        private final StringBuilder accum;\n\n        public TextAccumulator(StringBuilder accum) {\n            this.accum = accum;\n        }\n\n        @Override public void head(Node node, int depth) {\n            if (node instanceof TextNode) {\n                TextNode textNode = (TextNode) node;\n                appendNormalisedText(accum, textNode);\n            } else if (node instanceof Element) {\n                Element element = (Element) node;\n                if (accum.length() > 0 &&\n                    (element.isBlock() || element.nameIs(\"br\")) &&\n                    !lastCharIsWhitespace(accum))\n                    accum.append(' ');\n            }\n        }\n\n        @Override public void tail(Node node, int depth) {\n            // make sure there is a space between block tags and immediately following text nodes or inline elements <div>One</div>Two should be \"One Two\".\n            if (node instanceof Element) {\n                Element element = (Element) node;\n                Node next = node.nextSibling();\n                if (!element.tag.isInline() && (next instanceof TextNode || next instanceof Element && ((Element) next).tag.isInline()) && !lastCharIsWhitespace(accum))\n                    accum.append(' ');\n            }\n\n        }\n    }\n\n    /**\n     Get the non-normalized, decoded text of this element and its children, including only any newlines and spaces\n     present in the original source.\n     @return decoded, non-normalized text\n     @see #text()\n     @see #wholeOwnText()\n     */\n    public String wholeText() {\n        return wholeTextOf(nodeStream());\n    }\n\n    /**\n     An Element's nodeValue is its whole own text.\n     */\n    @Override\n    public String nodeValue() {\n        return wholeOwnText();\n    }\n\n    private static String wholeTextOf(Stream<Node> stream) {\n        return stream.map(node -> {\n            if (node instanceof TextNode) return ((TextNode) node).getWholeText();\n            if (node.nameIs(\"br\")) return \"\\n\";\n            return \"\";\n        }).collect(StringUtil.joining(\"\"));\n    }\n\n    /**\n     Get the non-normalized, decoded text of this element, <b>not including</b> any child elements, including any\n     newlines and spaces present in the original source.\n     @return decoded, non-normalized text that is a direct child of this Element\n     @see #text()\n     @see #wholeText()\n     @see #ownText()\n     @since 1.15.1\n     */\n    public String wholeOwnText() {\n        return wholeTextOf(childNodes.stream());\n    }\n\n    /**\n     * Gets the (normalized) text owned by this element only; does not get the combined text of all children.\n     * <p>\n     * For example, given HTML {@code <p>Hello <b>there</b> now!</p>}, {@code p.ownText()} returns {@code \"Hello now!\"},\n     * whereas {@code p.text()} returns {@code \"Hello there now!\"}.\n     * Note that the text within the {@code b} element is not returned, as it is not a direct child of the {@code p} element.\n     *\n     * @return decoded text, or empty string if none.\n     * @see #text()\n     * @see #textNodes()\n     */\n    public String ownText() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        ownText(sb);\n        return StringUtil.releaseBuilder(sb).trim();\n    }\n\n    private void ownText(StringBuilder accum) {\n        for (int i = 0; i < childNodeSize(); i++) {\n            Node child = childNodes.get(i);\n            if (child instanceof TextNode) {\n                TextNode textNode = (TextNode) child;\n                appendNormalisedText(accum, textNode);\n            } else if (child.nameIs(\"br\") && !lastCharIsWhitespace(accum)) {\n                accum.append(\" \");\n            }\n        }\n    }\n\n    private static void appendNormalisedText(StringBuilder accum, TextNode textNode) {\n        String text = textNode.getWholeText();\n        if (preserveWhitespace(textNode.parentNode) || textNode instanceof CDataNode)\n            accum.append(text);\n        else\n            StringUtil.appendNormalisedWhitespace(accum, text, lastCharIsWhitespace(accum));\n    }\n\n    static boolean preserveWhitespace(@Nullable Node node) {\n        // looks only at this element and five levels up, to prevent recursion & needless stack searches\n        if (node instanceof Element) {\n            Element el = (Element) node;\n            int i = 0;\n            do {\n                if (el.tag.preserveWhitespace())\n                    return true;\n                el = el.parent();\n                i++;\n            } while (i < 6 && el != null);\n        }\n        return false;\n    }\n\n    /**\n     * Set the text of this element. Any existing contents (text or elements) will be cleared.\n     * <p>As a special case, for {@code <script>} and {@code <style>} tags, the input text will be treated as data,\n     * not visible text.</p>\n     * @param text decoded text\n     * @return this element\n     */\n    public Element text(String text) {\n        Validate.notNull(text);\n        empty();\n        // special case for script/style in HTML (or customs): should be data node\n        if (tag().is(Tag.Data))\n            appendChild(new DataNode(text));\n        else\n            appendChild(new TextNode(text));\n\n        return this;\n    }\n\n    /**\n     Checks if the current element or any of its child elements contain non-whitespace text.\n     @return {@code true} if the element has non-blank text content, {@code false} otherwise.\n     */\n    public boolean hasText() {\n        AtomicBoolean hasText = new AtomicBoolean(false);\n        filter((node, depth) -> {\n            if (node instanceof TextNode) {\n                TextNode textNode = (TextNode) node;\n                if (!textNode.isBlank()) {\n                    hasText.set(true);\n                    return NodeFilter.FilterResult.STOP;\n                }\n            }\n            return NodeFilter.FilterResult.CONTINUE;\n        });\n        return hasText.get();\n    }\n\n    /**\n     * Get the combined data of this element. Data is e.g. the inside of a {@code <script>} tag. Note that data is NOT the\n     * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@code data()}\n     * for the contents of scripts, comments, CSS styles, etc.\n     *\n     * @return the data, or empty string if none\n     *\n     * @see #dataNodes()\n     */\n    public String data() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        traverse((childNode, depth) -> {\n            if (childNode instanceof DataNode) {\n                DataNode data = (DataNode) childNode;\n                sb.append(data.getWholeData());\n            } else if (childNode instanceof Comment) {\n                Comment comment = (Comment) childNode;\n                sb.append(comment.getData());\n            } else if (childNode instanceof CDataNode) {\n                // this shouldn't really happen because the html parser won't see the cdata as anything special when parsing script.\n                // but in case another type gets through.\n                CDataNode cDataNode = (CDataNode) childNode;\n                sb.append(cDataNode.getWholeText());\n            }\n        });\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    /**\n     * Gets the literal value of this element's \"class\" attribute, which may include multiple class names, space\n     * separated. (E.g. on <code>&lt;div class=\"header gray\"&gt;</code> returns, \"<code>header gray</code>\")\n     * @return The literal class attribute, or <b>empty string</b> if no class attribute set.\n     */\n    public String className() {\n        return attr(\"class\").trim();\n    }\n\n    /**\n     * Get each of the element's class names. E.g. on element {@code <div class=\"header gray\">},\n     * returns a set of two elements {@code \"header\", \"gray\"}. Note that modifications to this set are not pushed to\n     * the backing {@code class} attribute; use the {@link #classNames(java.util.Set)} method to persist them.\n     * @return set of classnames, empty if no class attribute\n     */\n    public Set<String> classNames() {\n    \tString[] names = ClassSplit.split(className());\n    \tSet<String> classNames = new LinkedHashSet<>(Arrays.asList(names));\n    \tclassNames.remove(\"\"); // if classNames() was empty, would include an empty class\n\n        return classNames;\n    }\n\n    /**\n     Set the element's {@code class} attribute to the supplied class names.\n     @param classNames set of classes\n     @return this element, for chaining\n     */\n    public Element classNames(Set<String> classNames) {\n        Validate.notNull(classNames);\n        if (classNames.isEmpty()) {\n            attributes().remove(\"class\");\n        } else {\n            attributes().put(\"class\", StringUtil.join(classNames, \" \"));\n        }\n        return this;\n    }\n\n    /**\n     * Tests if this element has a class. Case-insensitive.\n     * @param className name of class to check for\n     * @return true if it does, false if not\n     */\n    // performance sensitive\n    public boolean hasClass(String className) {\n        if (attributes == null)\n            return false;\n\n        final String classAttr = attributes.getIgnoreCase(\"class\");\n        final int len = classAttr.length();\n        final int wantLen = className.length();\n\n        if (len == 0 || len < wantLen) {\n            return false;\n        }\n\n        // if both lengths are equal, only need compare the className with the attribute\n        if (len == wantLen) {\n            return className.equalsIgnoreCase(classAttr);\n        }\n\n        // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations)\n        boolean inClass = false;\n        int start = 0;\n        for (int i = 0; i < len; i++) {\n            if (Character.isWhitespace(classAttr.charAt(i))) {\n                if (inClass) {\n                    // white space ends a class name, compare it with the requested one, ignore case\n                    if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) {\n                        return true;\n                    }\n                    inClass = false;\n                }\n            } else {\n                if (!inClass) {\n                    // we're in a class name : keep the start of the substring\n                    inClass = true;\n                    start = i;\n                }\n            }\n        }\n\n        // check the last entry\n        if (inClass && len - start == wantLen) {\n            return classAttr.regionMatches(true, start, className, 0, wantLen);\n        }\n\n        return false;\n    }\n\n    /**\n     Add a class name to this element's {@code class} attribute.\n     @param className class name to add\n     @return this element\n     */\n    public Element addClass(String className) {\n        Validate.notNull(className);\n\n        Set<String> classes = classNames();\n        classes.add(className);\n        classNames(classes);\n\n        return this;\n    }\n\n    /**\n     Remove a class name from this element's {@code class} attribute.\n     @param className class name to remove\n     @return this element\n     */\n    public Element removeClass(String className) {\n        Validate.notNull(className);\n\n        Set<String> classes = classNames();\n        classes.remove(className);\n        classNames(classes);\n\n        return this;\n    }\n\n    /**\n     Toggle a class name on this element's {@code class} attribute: if present, remove it; otherwise add it.\n     @param className class name to toggle\n     @return this element\n     */\n    public Element toggleClass(String className) {\n        Validate.notNull(className);\n\n        Set<String> classes = classNames();\n        if (classes.contains(className))\n            classes.remove(className);\n        else\n            classes.add(className);\n        classNames(classes);\n\n        return this;\n    }\n\n    /**\n     * Get the value of a form element (input, textarea, etc).\n     * @return the value of the form element, or empty string if not set.\n     */\n    public String val() {\n        if (elementIs(\"textarea\", NamespaceHtml))\n            return text();\n        else\n            return attr(\"value\");\n    }\n\n    /**\n     * Set the value of a form element (input, textarea, etc).\n     * @param value value to set\n     * @return this element (for chaining)\n     */\n    public Element val(String value) {\n        if (elementIs(\"textarea\", NamespaceHtml))\n            text(value);\n        else\n            attr(\"value\", value);\n        return this;\n    }\n\n    /**\n     Get the source range (start and end positions) of the end (closing) tag for this Element. Position tracking must be\n     enabled prior to parsing the content.\n     @return the range of the closing tag for this element, or {@code untracked} if its range was not tracked.\n     @see org.jsoup.parser.Parser#setTrackPosition(boolean)\n     @see Node#sourceRange()\n     @see Range#isImplicit()\n     @since 1.15.2\n     */\n    public Range endSourceRange() {\n        return Range.of(this, false);\n    }\n\n    @Override\n    void outerHtmlHead(final QuietAppendable accum, Document.OutputSettings out) {\n        String tagName = safeTagName(out.syntax());\n        accum.append('<').append(tagName);\n        if (attributes != null) attributes.html(accum, out);\n\n        if (childNodes.isEmpty()) {\n            boolean xmlMode = out.syntax() == xml || !tag.namespace().equals(NamespaceHtml);\n            if (xmlMode && (tag.is(Tag.SeenSelfClose) || (tag.isKnownTag() && (tag.isEmpty() || tag.isSelfClosing())))) {\n                accum.append(\" />\");\n            } else if (!xmlMode && tag.isEmpty()) { // html void element\n                accum.append('>');\n            } else {\n                accum.append(\"></\").append(tagName).append('>');\n            }\n        } else {\n            accum.append('>');\n        }\n    }\n\n    @Override\n    void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) {\n        if (!childNodes.isEmpty())\n            accum.append(\"</\").append(safeTagName(out.syntax())).append('>');\n        // if empty, we have already closed in htmlHead\n    }\n\n    /* If XML syntax, normalizes < to _ in tag name. */\n    @Nullable private String safeTagName(Document.OutputSettings.Syntax syntax) {\n        return syntax == xml ? Normalizer.xmlSafeTagName(tagName()) : tagName();\n    }\n\n    /**\n     * Retrieves the element's inner HTML. E.g. on a {@code <div>} with one empty {@code <p>}, would return\n     * {@code <p></p>}. (Whereas {@link #outerHtml()} would return {@code <div><p></p></div>}.)\n     *\n     * @return String of HTML.\n     * @see #outerHtml()\n     */\n    public String html() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        html(sb);\n        String html = StringUtil.releaseBuilder(sb);\n        return NodeUtils.outputSettings(this).prettyPrint() ? html.trim() : html;\n    }\n\n    @Override\n    public <T extends Appendable> T html(T accum) {\n        Node child = firstChild();\n        if (child != null) {\n            Printer printer = Printer.printerFor(child, QuietAppendable.wrap(accum));\n            while (child != null) {\n                printer.traverse(child);\n                child = child.nextSibling();\n            }\n        }\n        return accum;\n    }\n\n    /**\n     * Set this element's inner HTML. Clears the existing HTML first.\n     * @param html HTML to parse and set into this element\n     * @return this element\n     * @see #append(String)\n     */\n    public Element html(String html) {\n        empty();\n        append(html);\n        return this;\n    }\n\n    @Override\n    public Element clone() {\n        return (Element) super.clone();\n    }\n\n    @Override\n    public Element shallowClone() {\n        // simpler than implementing a clone version with no child copy\n        String baseUri = baseUri();\n        if (baseUri.isEmpty()) baseUri = null; // saves setting a blank internal attribute\n        return new Element(tag, baseUri, attributes == null ? null : attributes.clone());\n    }\n\n    @Override\n    protected Element doClone(@Nullable Node parent) {\n        Element clone = (Element) super.doClone(parent);\n        clone.childNodes = new NodeList(childNodes.size());\n        clone.childNodes.addAll(childNodes); // the children then get iterated and cloned in Node.clone\n        if (attributes != null) {\n            clone.attributes = attributes.clone();\n            // clear any cached children\n            clone.attributes.userData(childElsKey, null);\n        }\n\n        return clone;\n    }\n\n    // overrides of Node for call chaining\n    @Override\n    public Element clearAttributes() {\n        if (attributes != null) {\n            super.clearAttributes(); // keeps internal attributes via iterator\n            if (attributes.size == 0)\n                attributes = null; // only remove entirely if no internal attributes\n        }\n\n        return this;\n    }\n\n    @Override\n    public Element removeAttr(String attributeKey) {\n        return (Element) super.removeAttr(attributeKey);\n    }\n\n    @Override\n    public Element root() {\n        return (Element) super.root(); // probably a document, but always at least an element\n    }\n\n    @Override\n    public Element traverse(NodeVisitor nodeVisitor) {\n        return (Element) super.traverse(nodeVisitor);\n    }\n\n    @Override\n    public Element forEachNode(Consumer<? super Node> action) {\n        return (Element) super.forEachNode(action);\n    }\n\n    /**\n     Perform the supplied action on this Element and each of its descendant Elements, during a depth-first traversal.\n     Elements may be inspected, changed, added, replaced, or removed.\n     @param action the function to perform on the element\n     @see Node#forEachNode(Consumer)\n     */\n    @Override\n    public void forEach(Consumer<? super Element> action) {\n        stream().forEach(action);\n    }\n\n    /**\n     Returns an Iterator that iterates this Element and each of its descendant Elements, in document order.\n     @return an Iterator\n     */\n    @Override\n    public Iterator<Element> iterator() {\n        return new NodeIterator<>(this, Element.class);\n    }\n\n    @Override\n    public Element filter(NodeFilter nodeFilter) {\n        return  (Element) super.filter(nodeFilter);\n    }\n\n    static final class NodeList extends ArrayList<Node> {\n        /** Tracks if the children have valid sibling indices. We only need to reindex on siblingIndex() demand. */\n        boolean validChildren = true;\n\n        public NodeList(int size) {\n            super(size);\n        }\n\n        /** The modCount is used to invalidate the cached element children. */\n        int modCount() {\n            return this.modCount;\n        }\n\n        void incrementMod() {\n            this.modCount++;\n        }\n    }\n\n    void reindexChildren() {\n        final int size = childNodes.size();\n        for (int i = 0; i < size; i++) {\n            childNodes.get(i).setSiblingIndex(i);\n        }\n        childNodes.validChildren = true;\n    }\n\n    void invalidateChildren() {\n        childNodes.validChildren = false;\n    }\n\n    boolean hasValidChildren() {\n        return childNodes.validChildren;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Entities.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document.OutputSettings;\nimport org.jsoup.parser.CharacterReader;\nimport org.jsoup.parser.Parser;\n\nimport java.nio.charset.Charset;\nimport java.nio.charset.CharsetEncoder;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\n\nimport static org.jsoup.nodes.Entities.EscapeMode.base;\nimport static org.jsoup.nodes.Entities.EscapeMode.extended;\n\n/**\n * HTML entities, and escape routines. Source: <a href=\"http://www.w3.org/TR/html5/named-character-references.html#named-character-references\">W3C\n * HTML named character references</a>.\n */\npublic class Entities {\n    // constants for escape options:\n    static final int ForText = 0x1;\n    static final int ForAttribute = 0x2;\n    static final int Normalise = 0x4;\n    static final int TrimLeading = 0x8;\n    static final int TrimTrailing = 0x10;\n\n    private static final int empty = -1;\n    private static final String emptyName = \"\";\n    static final int codepointRadix = 36;\n    private static final char[] codeDelims = {',', ';'};\n    private static final HashMap<String, String> multipoints = new HashMap<>(); // name -> multiple character references\n\n    private static final int BaseCount = 106;\n    private static final ArrayList<String> baseSorted = new ArrayList<>(BaseCount); // names sorted longest first, for prefix matching\n\n    public enum EscapeMode {\n        /**\n         * Restricted entities suitable for XHTML output: lt, gt, amp, and quot only.\n         */\n        xhtml(EntitiesData.xmlPoints, 4),\n        /**\n         * Default HTML output entities.\n         */\n        base(EntitiesData.basePoints, 106),\n        /**\n         * Complete HTML entities.\n         */\n        extended(EntitiesData.fullPoints, 2125);\n\n        static {\n            // sort the base names by length, for prefix matching\n            Collections.addAll(baseSorted, base.nameKeys);\n            baseSorted.sort((a, b) -> b.length() - a.length());\n        }\n\n        // table of named references to their codepoints. sorted so we can binary search. built by BuildEntities.\n        private String[] nameKeys;\n        private int[] codeVals; // limitation is the few references with multiple characters; those go into multipoints.\n\n        // table of codepoints to named entities.\n        private int[] codeKeys; // we don't support multicodepoints to single named value currently\n        private String[] nameVals;\n\n        EscapeMode(String file, int size) {\n            load(this, file, size);\n        }\n\n        int codepointForName(final String name) {\n            int index = Arrays.binarySearch(nameKeys, name);\n            return index >= 0 ? codeVals[index] : empty;\n        }\n\n        String nameForCodepoint(final int codepoint) {\n            final int index = Arrays.binarySearch(codeKeys, codepoint);\n            if (index >= 0) {\n                // the results are ordered so lower case versions of same codepoint come after uppercase, and we prefer to emit lower\n                // (and binary search for same item with multi results is undefined\n                return (index < nameVals.length - 1 && codeKeys[index + 1] == codepoint) ?\n                    nameVals[index + 1] : nameVals[index];\n            }\n            return emptyName;\n        }\n    }\n\n    private Entities() {\n    }\n\n    /**\n     * Check if the input is a known named entity\n     *\n     * @param name the possible entity name (e.g. \"lt\" or \"amp\")\n     * @return true if a known named entity\n     */\n    public static boolean isNamedEntity(final String name) {\n        return extended.codepointForName(name) != empty;\n    }\n\n    /**\n     * Check if the input is a known named entity in the base entity set.\n     *\n     * @param name the possible entity name (e.g. \"lt\" or \"amp\")\n     * @return true if a known named entity in the base set\n     * @see #isNamedEntity(String)\n     */\n    public static boolean isBaseNamedEntity(final String name) {\n        return base.codepointForName(name) != empty;\n    }\n\n    /**\n     * Get the character(s) represented by the named entity\n     *\n     * @param name entity (e.g. \"lt\" or \"amp\")\n     * @return the string value of the character(s) represented by this entity, or \"\" if not defined\n     */\n    public static String getByName(String name) {\n        String val = multipoints.get(name);\n        if (val != null)\n            return val;\n        int codepoint = extended.codepointForName(name);\n        if (codepoint != empty)\n            return new String(new int[]{codepoint}, 0, 1);\n        return emptyName;\n    }\n\n    public static int codepointsForName(final String name, final int[] codepoints) {\n        String val = multipoints.get(name);\n        if (val != null) {\n            codepoints[0] = val.codePointAt(0);\n            codepoints[1] = val.codePointAt(1);\n            return 2;\n        }\n        int codepoint = extended.codepointForName(name);\n        if (codepoint != empty) {\n            codepoints[0] = codepoint;\n            return 1;\n        }\n        return 0;\n    }\n\n    /**\n     Finds the longest base named entity that is a prefix of the input. That is, input \"notit\" would return \"not\".\n\n     @return longest entity name that is a prefix of the input, or \"\" if no entity matches\n     */\n    public static String findPrefix(String input) {\n        for (String name : baseSorted) {\n            if (input.startsWith(name)) return name;\n        }\n        return emptyName;\n        // if perf critical, could look at using a Trie vs a scan\n    }\n\n    /**\n     HTML escape an input string. That is, {@code <} is returned as {@code &lt;}. The escaped string is suitable for use\n     both in attributes and in text data.\n     @param data the un-escaped string to escape\n     @param out the output settings to use. This configures the character set escaped against (that is, if a\n     character is supported in the output character set, it doesn't have to be escaped), and also HTML or XML\n     settings.\n     @return the escaped string\n     */\n    public static String escape(String data, OutputSettings out) {\n        return escapeString(data, out.escapeMode(), out.charset());\n    }\n\n    /**\n     HTML escape an input string, using the default settings (UTF-8, base entities). That is, {@code <} is\n     returned as {@code &lt;}. The escaped string is suitable for use both in attributes and in text data.\n     @param data the un-escaped string to escape\n     @return the escaped string\n     @see #escape(String, OutputSettings)\n     */\n    public static String escape(String data) {\n        return escapeString(data, base, DataUtil.UTF_8);\n    }\n\n    private static String escapeString(String data, EscapeMode escapeMode, Charset charset) {\n        if (data == null) return \"\";\n        StringBuilder sb = StringUtil.borrowBuilder();\n        doEscape(data, QuietAppendable.wrap(sb), escapeMode, charset, ForText | ForAttribute);\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    static void escape(QuietAppendable accum, String data, OutputSettings out, int options) {\n        doEscape(data, accum, out.escapeMode(), out.charset(), options);\n    }\n\n    private static void doEscape(String data, QuietAppendable accum, EscapeMode mode, Charset charset, int options) {\n        final CoreCharset coreCharset = CoreCharset.byName(charset.name());\n        final CharsetEncoder fallback = encoderFor(charset);\n        final int length = data.length();\n\n        int codePoint;\n        boolean lastWasWhite = false;\n        boolean reachedNonWhite = false;\n        boolean skipped = false;\n        for (int offset = 0; offset < length; offset += Character.charCount(codePoint)) {\n            codePoint = data.codePointAt(offset);\n\n            if ((options & Normalise) != 0) {\n                if (StringUtil.isWhitespace(codePoint)) {\n                    if ((options & TrimLeading) != 0 && !reachedNonWhite) continue;\n                    if (lastWasWhite) continue;\n                    if ((options & TrimTrailing) != 0) {\n                        skipped = true;\n                        continue;\n                    }\n                    accum.append(' ');\n                    lastWasWhite = true;\n                    continue;\n                } else {\n                    lastWasWhite = false;\n                    reachedNonWhite = true;\n                    if (skipped) {\n                        accum.append(' '); // wasn't the end, so need to place a normalized space\n                        skipped = false;\n                    }\n                }\n            }\n            appendEscaped(codePoint, accum, options, mode, coreCharset, fallback);\n        }\n    }\n\n    private static void appendEscaped(int codePoint, QuietAppendable accum, int options, EscapeMode escapeMode,\n        CoreCharset coreCharset, CharsetEncoder fallback) {\n        // specific character range for xml 1.0; drop (not encode) if so\n        if (EscapeMode.xhtml == escapeMode && !isValidXmlChar(codePoint)) {\n            return;\n        }\n\n        // surrogate pairs, split implementation for efficiency on single char common case (saves creating strings, char[]):\n        final char c = (char) codePoint;\n        if (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT) {\n            // html specific and required escapes:\n            switch (c) {\n                case '&':\n                    accum.append(\"&amp;\");\n                    break;\n                case 0xA0:\n                    appendNbsp(accum, escapeMode);\n                    break;\n                case '<':\n                    accum.append(\"&lt;\");\n                    break;\n                case '>':\n                    accum.append(\"&gt;\");\n                    break;\n                case '\"':\n                    if ((options & ForAttribute) != 0) accum.append(\"&quot;\");\n                    else accum.append(c);\n                    break;\n                case '\\'':\n                    // special case for the Entities.escape(string) method when we are maximally escaping. Otherwise, because we output attributes in \"\", there's no need to escape.\n                    appendApos(accum, options, escapeMode);\n                    break;\n                // we escape ascii control <x20 (other than tab, line-feed, carriage return) for XML compliance (required) and HTML ease of reading (not required) - https://www.w3.org/TR/xml/#charsets\n                case 0x9:\n                case 0xA:\n                case 0xD:\n                    accum.append(c);\n                    break;\n                default:\n                    if (c < 0x20 || !canEncode(coreCharset, c, fallback)) appendEncoded(accum, escapeMode, codePoint);\n                    else accum.append(c);\n            }\n        } else {\n            if (canEncode(coreCharset, c, fallback)) {\n                // reads into charBuf - we go through these steps to avoid GC objects as much as possible (would be a new String and a new char[2] for each character)\n                char[] chars = charBuf.get();\n                int len = Character.toChars(codePoint, chars, 0);\n                accum.append(chars, 0, len);\n            } else {\n                appendEncoded(accum, escapeMode, codePoint);\n            }\n        }\n    }\n\n    private static final ThreadLocal<char[]> charBuf = ThreadLocal.withInitial(() -> new char[2]);\n\n    private static void appendNbsp(QuietAppendable accum, EscapeMode escapeMode) {\n        if (escapeMode != EscapeMode.xhtml) accum.append(\"&nbsp;\");\n        else accum.append(\"&#xa0;\");\n    }\n\n    private static void appendApos(QuietAppendable accum, int options, EscapeMode escapeMode) {\n        if ((options & ForAttribute) != 0 && (options & ForText) != 0) {\n            if (escapeMode == EscapeMode.xhtml) accum.append(\"&#x27;\");\n            else accum.append(\"&apos;\");\n        } else {\n            accum.append('\\'');\n        }\n    }\n\n    private static void appendEncoded(QuietAppendable accum, EscapeMode escapeMode, int codePoint) {\n        final String name = escapeMode.nameForCodepoint(codePoint);\n        if (!emptyName.equals(name)) // ok for identity check\n            accum.append('&').append(name).append(';');\n        else\n            accum.append(\"&#x\").append(Integer.toHexString(codePoint)).append(';');\n    }\n\n    /**\n     * Un-escape an HTML escaped string. That is, {@code &lt;} is returned as {@code <}.\n     *\n     * @param string the HTML string to un-escape\n     * @return the unescaped string\n     */\n    public static String unescape(String string) {\n        return unescape(string, false);\n    }\n\n    /**\n     * Unescape the input string.\n     *\n     * @param string to un-HTML-escape\n     * @param strict if \"strict\" (that is, requires trailing ';' char, otherwise that's optional)\n     * @return unescaped string\n     */\n    static String unescape(String string, boolean strict) {\n        return Parser.unescapeEntities(string, strict);\n    }\n\n    /*\n     * Provides a fast-path for Encoder.canEncode, which drastically improves performance on Android post JellyBean.\n     * After KitKat, the implementation of canEncode degrades to the point of being useless. For non ASCII or UTF,\n     * performance may be bad. We can add more encoders for common character sets that are impacted by performance\n     * issues on Android if required.\n     *\n     * Benchmarks:     *\n     * OLD toHtml() impl v New (fastpath) in millis\n     * Wiki: 1895, 16\n     * CNN: 6378, 55\n     * Alterslash: 3013, 28\n     * Jsoup: 167, 2\n     */\n    private static boolean canEncode(final CoreCharset charset, final char c, final CharsetEncoder fallback) {\n        // todo add more charset tests if impacted by Android's bad perf in canEncode\n        switch (charset) {\n            case ascii:\n                return c < 0x80;\n            case utf:\n                return !(c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1)); // !Character.isSurrogate(c); but not in Android 10 desugar\n            default:\n                return fallback.canEncode(c);\n        }\n    }\n\n    private static boolean isValidXmlChar(int codePoint) {\n        // https://www.w3.org/TR/2006/REC-xml-20060816/Overview.html#charsets\n        // Char\t   ::=   \t#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]\tany Unicode character, excluding the surrogate blocks, FFFE, and FFFF.\n        return (codePoint == 0x9 || codePoint == 0xA || codePoint == 0xD || (codePoint >= 0x20 && codePoint <= 0xD7FF)\n            || (codePoint >= 0xE000 && codePoint <= 0xFFFD) || (codePoint >= 0x10000 && codePoint <= 0x10FFFF));\n    }\n\n    enum CoreCharset {\n        ascii, utf, fallback;\n\n        static CoreCharset byName(final String name) {\n            if (name.equals(\"US-ASCII\"))\n                return ascii;\n            if (name.startsWith(\"UTF-\")) // covers UTF-8, UTF-16, et al\n                return utf;\n            return fallback;\n        }\n    }\n\n    // cache the last used fallback encoder to save recreating on every use\n    private static final ThreadLocal<CharsetEncoder> LocalEncoder = new ThreadLocal<>();\n    private static CharsetEncoder encoderFor(Charset charset) {\n        CharsetEncoder encoder = LocalEncoder.get();\n        if (encoder == null || !encoder.charset().equals(charset)) {\n            encoder = charset.newEncoder();\n            LocalEncoder.set(encoder);\n        }\n        return encoder;\n    }\n\n    private static void load(EscapeMode e, String pointsData, int size) {\n        e.nameKeys = new String[size];\n        e.codeVals = new int[size];\n        e.codeKeys = new int[size];\n        e.nameVals = new String[size];\n\n        int i = 0;\n        try (CharacterReader reader = new CharacterReader(pointsData)) {\n            while (!reader.isEmpty()) {\n                // NotNestedLessLess=10913,824;1887&\n\n                final String name = reader.consumeTo('=');\n                reader.advance();\n                final int cp1 = Integer.parseInt(reader.consumeToAny(codeDelims), codepointRadix);\n                final char codeDelim = reader.current();\n                reader.advance();\n                final int cp2;\n                if (codeDelim == ',') {\n                    cp2 = Integer.parseInt(reader.consumeTo(';'), codepointRadix);\n                    reader.advance();\n                } else {\n                    cp2 = empty;\n                }\n                final String indexS = reader.consumeTo('&');\n                final int index = Integer.parseInt(indexS, codepointRadix);\n                reader.advance();\n\n                e.nameKeys[i] = name;\n                e.codeVals[i] = cp1;\n                e.codeKeys[index] = cp1;\n                e.nameVals[index] = name;\n\n                if (cp2 != empty) {\n                    multipoints.put(name, new String(new int[]{cp1, cp2}, 0, 2));\n                }\n                i++;\n            }\n\n            Validate.isTrue(i == size, \"Unexpected count of entities loaded\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/EntitiesData.java",
    "content": "package org.jsoup.nodes;\n\n/**\n * Holds packed data that represents Entity name=value pairs. Parsed by Entities, created by BuildEntities.\n */\nclass EntitiesData {\n    static final String xmlPoints;\n    static final String basePoints;\n    static final String fullPoints;\n\n    // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6447475\n    // allocated in static here so not inlined in users; saves 16K from .jar (!)\n    static {\n        xmlPoints = \"amp=12;1&gt=1q;3&lt=1o;2&quot=y;0&\";\n        basePoints = \"AElig=5i;1c&AMP=12;2&Aacute=5d;17&Acirc=5e;18&Agrave=5c;16&Aring=5h;1b&Atilde=5f;19&Auml=5g;1a&COPY=4p;h&Ccedil=5j;1d&ETH=5s;1m&Eacute=5l;1f&Ecirc=5m;1g&Egrave=5k;1e&Euml=5n;1h&GT=1q;6&Iacute=5p;1j&Icirc=5q;1k&Igrave=5o;1i&Iuml=5r;1l&LT=1o;4&Ntilde=5t;1n&Oacute=5v;1p&Ocirc=5w;1q&Ograve=5u;1o&Oslash=60;1u&Otilde=5x;1r&Ouml=5y;1s&QUOT=y;0&REG=4u;n&THORN=66;20&Uacute=62;1w&Ucirc=63;1x&Ugrave=61;1v&Uuml=64;1y&Yacute=65;1z&aacute=69;23&acirc=6a;24&acute=50;u&aelig=6e;28&agrave=68;22&amp=12;3&aring=6d;27&atilde=6b;25&auml=6c;26&brvbar=4m;e&ccedil=6f;29&cedil=54;y&cent=4i;a&copy=4p;i&curren=4k;c&deg=4w;q&divide=6v;2p&eacute=6h;2b&ecirc=6i;2c&egrave=6g;2a&eth=6o;2i&euml=6j;2d&frac12=59;13&frac14=58;12&frac34=5a;14&gt=1q;7&iacute=6l;2f&icirc=6m;2g&iexcl=4h;9&igrave=6k;2e&iquest=5b;15&iuml=6n;2h&laquo=4r;k&lt=1o;5&macr=4v;p&micro=51;v&middot=53;x&nbsp=4g;8&not=4s;l&ntilde=6p;2j&oacute=6r;2l&ocirc=6s;2m&ograve=6q;2k&ordf=4q;j&ordm=56;10&oslash=6w;2q&otilde=6t;2n&ouml=6u;2o&para=52;w&plusmn=4x;r&pound=4j;b&quot=y;1&raquo=57;11&reg=4u;o&sect=4n;f&shy=4t;m&sup1=55;z&sup2=4y;s&sup3=4z;t&szlig=67;21&thorn=72;2w&times=5z;1t&uacute=6y;2s&ucirc=6z;2t&ugrave=6x;2r&uml=4o;g&uuml=70;2u&yacute=71;2v&yen=4l;d&yuml=73;2x&\";\n        fullPoints = \"AElig=5i;2v&AMP=12;8&Aacute=5d;2p&Abreve=76;4k&Acirc=5e;2q&Acy=sw;av&Afr=2kn8;1kh&Agrave=5c;2o&Alpha=pd;8d&Amacr=74;4i&And=8cz;1e1&Aogon=78;4m&Aopf=2koo;1ls&ApplyFunction=6e9;ew&Aring=5h;2t&Ascr=2kkc;1jc&Assign=6s4;s6&Atilde=5f;2r&Auml=5g;2s&Backslash=6qe;o1&Barv=8h3;1it&Barwed=6x2;120&Bcy=sx;aw&Because=6r9;pw&Bernoullis=6jw;gn&Beta=pe;8e&Bfr=2kn9;1ki&Bopf=2kop;1lt&Breve=k8;82&Bscr=6jw;gp&Bumpeq=6ry;ro&CHcy=tj;bi&COPY=4p;1q&Cacute=7a;4o&Cap=6vm;zz&CapitalDifferentialD=6kl;h8&Cayleys=6jx;gq&Ccaron=7g;4u&Ccedil=5j;2w&Ccirc=7c;4q&Cconint=6r4;pn&Cdot=7e;4s&Cedilla=54;2e&CenterDot=53;2b&Cfr=6jx;gr&Chi=pz;8y&CircleDot=6u1;x8&CircleMinus=6ty;x3&CirclePlus=6tx;x1&CircleTimes=6tz;x5&ClockwiseContourIntegral=6r6;pp&CloseCurlyDoubleQuote=6cd;e0&CloseCurlyQuote=6c9;dt&Colon=6rb;q1&Colone=8dw;1en&Congruent=6sh;sn&Conint=6r3;pm&ContourIntegral=6r2;pi&Copf=6iq;f7&Coproduct=6q8;nq&CounterClockwiseContourIntegral=6r7;pr&Cross=8bz;1d8&Cscr=2kke;1jd&Cup=6vn;100&CupCap=6rx;rk&DD=6kl;h9&DDotrahd=841;184&DJcy=si;ai&DScy=sl;al&DZcy=sv;au&Dagger=6ch;e7&Darr=6n5;j5&Dashv=8h0;1ir&Dcaron=7i;4w&Dcy=t0;az&Del=6pz;n9&Delta=pg;8g&Dfr=2knb;1kj&DiacriticalAcute=50;27&DiacriticalDot=k9;84&DiacriticalDoubleAcute=kd;8a&DiacriticalGrave=2o;13&DiacriticalTilde=kc;88&Diamond=6v8;za&DifferentialD=6km;ha&Dopf=2kor;1lu&Dot=4o;1n&DotDot=6ho;f5&DotEqual=6s0;rw&DoubleContourIntegral=6r3;pl&DoubleDot=4o;1m&DoubleDownArrow=6oj;m0&DoubleLeftArrow=6og;lq&DoubleLeftRightArrow=6ok;m3&DoubleLeftTee=8h0;1iq&DoubleLongLeftArrow=7w8;17g&DoubleLongLeftRightArrow=7wa;17m&DoubleLongRightArrow=7w9;17j&DoubleRightArrow=6oi;lw&DoubleRightTee=6ug;xz&DoubleUpArrow=6oh;lt&DoubleUpDownArrow=6ol;m7&DoubleVerticalBar=6qt;ov&DownArrow=6mr;i8&DownArrowBar=843;186&DownArrowUpArrow=6ph;mn&DownBreve=lt;8c&DownLeftRightVector=85s;198&DownLeftTeeVector=866;19m&DownLeftVector=6nx;ke&DownLeftVectorBar=85y;19e&DownRightTeeVector=867;19n&DownRightVector=6o1;kq&DownRightVectorBar=85z;19f&DownTee=6uc;xs&DownTeeArrow=6nb;jh&Downarrow=6oj;m1&Dscr=2kkf;1je&Dstrok=7k;4y&ENG=96;6g&ETH=5s;35&Eacute=5l;2y&Ecaron=7u;56&Ecirc=5m;2z&Ecy=tp;bo&Edot=7q;52&Efr=2knc;1kk&Egrave=5k;2x&Element=6q0;na&Emacr=7m;50&EmptySmallSquare=7i3;15x&EmptyVerySmallSquare=7fv;150&Eogon=7s;54&Eopf=2kos;1lv&Epsilon=ph;8h&Equal=8dx;1eo&EqualTilde=6rm;qp&Equilibrium=6oc;li&Escr=6k0;gu&Esim=8dv;1em&Eta=pj;8j&Euml=5n;30&Exists=6pv;mz&ExponentialE=6kn;hc&Fcy=tg;bf&Ffr=2knd;1kl&FilledSmallSquare=7i4;15y&FilledVerySmallSquare=7fu;14w&Fopf=2kot;1lw&ForAll=6ps;ms&Fouriertrf=6k1;gv&Fscr=6k1;gw&GJcy=sj;aj&GT=1q;r&Gamma=pf;8f&Gammad=rg;a5&Gbreve=7y;5a&Gcedil=82;5e&Gcirc=7w;58&Gcy=sz;ay&Gdot=80;5c&Gfr=2kne;1km&Gg=6vt;10c&Gopf=2kou;1lx&GreaterEqual=6sl;sv&GreaterEqualLess=6vv;10i&GreaterFullEqual=6sn;t6&GreaterGreater=8f6;1gh&GreaterLess=6t3;ul&GreaterSlantEqual=8e6;1f5&GreaterTilde=6sz;ub&Gscr=2kki;1jf&Gt=6sr;tr&HARDcy=tm;bl&Hacek=jr;80&Hat=2m;10&Hcirc=84;5f&Hfr=6j0;fe&HilbertSpace=6iz;fa&Hopf=6j1;fg&HorizontalLine=7b4;13i&Hscr=6iz;fc&Hstrok=86;5h&HumpDownHump=6ry;rn&HumpEqual=6rz;rs&IEcy=t1;b0&IJlig=8i;5s&IOcy=sh;ah&Iacute=5p;32&Icirc=5q;33&Icy=t4;b3&Idot=8g;5p&Ifr=6j5;fq&Igrave=5o;31&Im=6j5;fr&Imacr=8a;5l&ImaginaryI=6ko;hf&Implies=6oi;ly&Int=6r0;pf&Integral=6qz;pd&Intersection=6v6;z4&InvisibleComma=6eb;f0&InvisibleTimes=6ea;ey&Iogon=8e;5n&Iopf=2kow;1ly&Iota=pl;8l&Iscr=6j4;fn&Itilde=88;5j&Iukcy=sm;am&Iuml=5r;34&Jcirc=8k;5u&Jcy=t5;b4&Jfr=2knh;1kn&Jopf=2kox;1lz&Jscr=2kkl;1jg&Jsercy=so;ao&Jukcy=sk;ak&KHcy=th;bg&KJcy=ss;as&Kappa=pm;8m&Kcedil=8m;5w&Kcy=t6;b5&Kfr=2kni;1ko&Kopf=2koy;1m0&Kscr=2kkm;1jh&LJcy=sp;ap&LT=1o;m&Lacute=8p;5z&Lambda=pn;8n&Lang=7vu;173&Laplacetrf=6j6;fs&Larr=6n2;j1&Lcaron=8t;63&Lcedil=8r;61&Lcy=t7;b6&LeftAngleBracket=7vs;16x&LeftArrow=6mo;hu&LeftArrowBar=6p0;mj&LeftArrowRightArrow=6o6;l3&LeftCeiling=6x4;121&LeftDoubleBracket=7vq;16t&LeftDownTeeVector=869;19p&LeftDownVector=6o3;kw&LeftDownVectorBar=861;19h&LeftFloor=6x6;125&LeftRightArrow=6ms;ib&LeftRightVector=85q;196&LeftTee=6ub;xq&LeftTeeArrow=6n8;ja&LeftTeeVector=862;19i&LeftTriangle=6uq;ya&LeftTriangleBar=89b;1c0&LeftTriangleEqual=6us;yg&LeftUpDownVector=85t;199&LeftUpTeeVector=868;19o&LeftUpVector=6nz;kk&LeftUpVectorBar=860;19g&LeftVector=6nw;kb&LeftVectorBar=85u;19a&Leftarrow=6og;lr&Leftrightarrow=6ok;m4&LessEqualGreater=6vu;10e&LessFullEqual=6sm;t0&LessGreater=6t2;ui&LessLess=8f5;1gf&LessSlantEqual=8e5;1ez&LessTilde=6sy;u8&Lfr=2knj;1kp&Ll=6vs;109&Lleftarrow=6oq;me&Lmidot=8v;65&LongLeftArrow=7w5;177&LongLeftRightArrow=7w7;17d&LongRightArrow=7w6;17a&Longleftarrow=7w8;17h&Longleftrightarrow=7wa;17n&Longrightarrow=7w9;17k&Lopf=2koz;1m1&LowerLeftArrow=6mx;iq&LowerRightArrow=6mw;in&Lscr=6j6;fu&Lsh=6nk;jv&Lstrok=8x;67&Lt=6sq;tl&Map=83p;17v&Mcy=t8;b7&MediumSpace=6e7;eu&Mellintrf=6k3;gx&Mfr=2knk;1kq&MinusPlus=6qb;nv&Mopf=2kp0;1m2&Mscr=6k3;gz&Mu=po;8o&NJcy=sq;aq&Nacute=8z;69&Ncaron=93;6d&Ncedil=91;6b&Ncy=t9;b8&NegativeMediumSpace=6bv;dc&NegativeThickSpace=6bv;dd&NegativeThinSpace=6bv;de&NegativeVeryThinSpace=6bv;db&NestedGreaterGreater=6sr;tq&NestedLessLess=6sq;tk&NewLine=a;1&Nfr=2knl;1kr&NoBreak=6e8;ev&NonBreakingSpace=4g;1d&Nopf=6j9;fx&Not=8h8;1ix&NotCongruent=6si;sp&NotCupCap=6st;tv&NotDoubleVerticalBar=6qu;p0&NotElement=6q1;ne&NotEqual=6sg;sk&NotEqualTilde=6rm,mw;qn&NotExists=6pw;n1&NotGreater=6sv;tz&NotGreaterEqual=6sx;u5&NotGreaterFullEqual=6sn,mw;t3&NotGreaterGreater=6sr,mw;tn&NotGreaterLess=6t5;uq&NotGreaterSlantEqual=8e6,mw;1f2&NotGreaterTilde=6t1;ug&NotHumpDownHump=6ry,mw;rl&NotHumpEqual=6rz,mw;rq&NotLeftTriangle=6wa;113&NotLeftTriangleBar=89b,mw;1bz&NotLeftTriangleEqual=6wc;119&NotLess=6su;tw&NotLessEqual=6sw;u2&NotLessGreater=6t4;uo&NotLessLess=6sq,mw;th&NotLessSlantEqual=8e5,mw;1ew&NotLessTilde=6t0;ue&NotNestedGreaterGreater=8f6,mw;1gg&NotNestedLessLess=8f5,mw;1ge&NotPrecedes=6tc;vb&NotPrecedesEqual=8fj,mw;1gv&NotPrecedesSlantEqual=6w0;10p&NotReverseElement=6q4;nl&NotRightTriangle=6wb;116&NotRightTriangleBar=89c,mw;1c1&NotRightTriangleEqual=6wd;11c&NotSquareSubset=6tr,mw;wh&NotSquareSubsetEqual=6w2;10t&NotSquareSuperset=6ts,mw;wl&NotSquareSupersetEqual=6w3;10v&NotSubset=6te,6he;vh&NotSubsetEqual=6tk;w0&NotSucceeds=6td;ve&NotSucceedsEqual=8fk,mw;1h1&NotSucceedsSlantEqual=6w1;10r&NotSucceedsTilde=6tb,mw;v7&NotSuperset=6tf,6he;vm&NotSupersetEqual=6tl;w3&NotTilde=6rl;ql&NotTildeEqual=6ro;qv&NotTildeFullEqual=6rr;r1&NotTildeTilde=6rt;r9&NotVerticalBar=6qs;or&Nscr=2kkp;1ji&Ntilde=5t;36&Nu=pp;8p&OElig=9e;6m&Oacute=5v;38&Ocirc=5w;39&Ocy=ta;b9&Odblac=9c;6k&Ofr=2knm;1ks&Ograve=5u;37&Omacr=98;6i&Omega=q1;90&Omicron=pr;8r&Oopf=2kp2;1m3&OpenCurlyDoubleQuote=6cc;dy&OpenCurlyQuote=6c8;dr&Or=8d0;1e2&Oscr=2kkq;1jj&Oslash=60;3d&Otilde=5x;3a&Otimes=8c7;1df&Ouml=5y;3b&OverBar=6da;em&OverBrace=732;13b&OverBracket=71w;134&OverParenthesis=730;139&PartialD=6pu;mx&Pcy=tb;ba&Pfr=2knn;1kt&Phi=py;8x&Pi=ps;8s&PlusMinus=4x;22&Poincareplane=6j0;fd&Popf=6jd;g3&Pr=8fv;1hl&Precedes=6t6;us&PrecedesEqual=8fj;1gy&PrecedesSlantEqual=6t8;uy&PrecedesTilde=6ta;v4&Prime=6cz;eg&Product=6q7;no&Proportion=6rb;q0&Proportional=6ql;oa&Pscr=2kkr;1jk&Psi=q0;8z&QUOT=y;3&Qfr=2kno;1ku&Qopf=6je;g5&Qscr=2kks;1jl&RBarr=840;183&REG=4u;1x&Racute=9g;6o&Rang=7vv;174&Rarr=6n4;j4&Rarrtl=846;187&Rcaron=9k;6s&Rcedil=9i;6q&Rcy=tc;bb&Re=6jg;gb&ReverseElement=6q3;nh&ReverseEquilibrium=6ob;le&ReverseUpEquilibrium=86n;1a4&Rfr=6jg;ga&Rho=pt;8t&RightAngleBracket=7vt;170&RightArrow=6mq;i3&RightArrowBar=6p1;ml&RightArrowLeftArrow=6o4;ky&RightCeiling=6x5;123&RightDoubleBracket=7vr;16v&RightDownTeeVector=865;19l&RightDownVector=6o2;kt&RightDownVectorBar=85x;19d&RightFloor=6x7;127&RightTee=6ua;xo&RightTeeArrow=6na;je&RightTeeVector=863;19j&RightTriangle=6ur;yd&RightTriangleBar=89c;1c2&RightTriangleEqual=6ut;yk&RightUpDownVector=85r;197&RightUpTeeVector=864;19k&RightUpVector=6ny;kh&RightUpVectorBar=85w;19c&RightVector=6o0;kn&RightVectorBar=85v;19b&Rightarrow=6oi;lx&Ropf=6jh;gd&RoundImplies=86o;1a6&Rrightarrow=6or;mg&Rscr=6jf;g7&Rsh=6nl;jx&RuleDelayed=8ac;1cb&SHCHcy=tl;bk&SHcy=tk;bj&SOFTcy=to;bn&Sacute=9m;6u&Sc=8fw;1hm&Scaron=9s;70&Scedil=9q;6y&Scirc=9o;6w&Scy=td;bc&Sfr=2knq;1kv&ShortDownArrow=6mr;i7&ShortLeftArrow=6mo;ht&ShortRightArrow=6mq;i2&ShortUpArrow=6mp;hy&Sigma=pv;8u&SmallCircle=6qg;o6&Sopf=2kp6;1m4&Sqrt=6qi;o9&Square=7fl;14t&SquareIntersection=6tv;ww&SquareSubset=6tr;wi&SquareSubsetEqual=6tt;wp&SquareSuperset=6ts;wm&SquareSupersetEqual=6tu;ws&SquareUnion=6tw;wz&Sscr=2kku;1jm&Star=6va;zf&Sub=6vk;zw&Subset=6vk;zv&SubsetEqual=6ti;vu&Succeeds=6t7;uv&SucceedsEqual=8fk;1h4&SucceedsSlantEqual=6t9;v1&SucceedsTilde=6tb;v8&SuchThat=6q3;ni&Sum=6q9;ns&Sup=6vl;zy&Superset=6tf;vp&SupersetEqual=6tj;vx&Supset=6vl;zx&THORN=66;3j&TRADE=6jm;gf&TSHcy=sr;ar&TScy=ti;bh&Tab=9;0&Tau=pw;8v&Tcaron=9w;74&Tcedil=9u;72&Tcy=te;bd&Tfr=2knr;1kw&Therefore=6r8;pt&Theta=pk;8k&ThickSpace=6e7,6bu;et&ThinSpace=6bt;d7&Tilde=6rg;q9&TildeEqual=6rn;qs&TildeFullEqual=6rp;qy&TildeTilde=6rs;r4&Topf=2kp7;1m5&TripleDot=6hn;f3&Tscr=2kkv;1jn&Tstrok=9y;76&Uacute=62;3f&Uarr=6n3;j2&Uarrocir=85l;193&Ubrcy=su;at&Ubreve=a4;7c&Ucirc=63;3g&Ucy=tf;be&Udblac=a8;7g&Ufr=2kns;1kx&Ugrave=61;3e&Umacr=a2;7a&UnderBar=2n;11&UnderBrace=733;13c&UnderBracket=71x;136&UnderParenthesis=731;13a&Union=6v7;z8&UnionPlus=6tq;wf&Uogon=aa;7i&Uopf=2kp8;1m6&UpArrow=6mp;hz&UpArrowBar=842;185&UpArrowDownArrow=6o5;l1&UpDownArrow=6mt;ie&UpEquilibrium=86m;1a2&UpTee=6ud;xv&UpTeeArrow=6n9;jc&Uparrow=6oh;lu&Updownarrow=6ol;m8&UpperLeftArrow=6mu;ih&UpperRightArrow=6mv;ik&Upsi=r6;9z&Upsilon=px;8w&Uring=a6;7e&Uscr=2kkw;1jo&Utilde=a0;78&Uuml=64;3h&VDash=6uj;y3&Vbar=8h7;1iw&Vcy=sy;ax&Vdash=6uh;y1&Vdashl=8h2;1is&Vee=6v5;z3&Verbar=6c6;dp&Vert=6c6;dq&VerticalBar=6qr;on&VerticalLine=3g;18&VerticalSeparator=7rs;16o&VerticalTilde=6rk;qi&VeryThinSpace=6bu;d9&Vfr=2knt;1ky&Vopf=2kp9;1m7&Vscr=2kkx;1jp&Vvdash=6ui;y2&Wcirc=ac;7k&Wedge=6v4;z0&Wfr=2knu;1kz&Wopf=2kpa;1m8&Wscr=2kky;1jq&Xfr=2knv;1l0&Xi=pq;8q&Xopf=2kpb;1m9&Xscr=2kkz;1jr&YAcy=tr;bq&YIcy=sn;an&YUcy=tq;bp&Yacute=65;3i&Ycirc=ae;7m&Ycy=tn;bm&Yfr=2knw;1l1&Yopf=2kpc;1ma&Yscr=2kl0;1js&Yuml=ag;7o&ZHcy=t2;b1&Zacute=ah;7p&Zcaron=al;7t&Zcy=t3;b2&Zdot=aj;7r&ZeroWidthSpace=6bv;df&Zeta=pi;8i&Zfr=6js;gl&Zopf=6jo;gi&Zscr=2kl1;1jt&aacute=69;3m&abreve=77;4l&ac=6ri;qg&acE=6ri,mr;qe&acd=6rj;qh&acirc=6a;3n&acute=50;28&acy=ts;br&aelig=6e;3r&af=6e9;ex&afr=2kny;1l2&agrave=68;3l&alefsym=6k5;h3&aleph=6k5;h4&alpha=q9;92&amacr=75;4j&amalg=8cf;1dm&amp=12;9&and=6qv;p6&andand=8d1;1e3&andd=8d8;1e9&andslope=8d4;1e6&andv=8d6;1e7&ang=6qo;oj&ange=884;1b1&angle=6qo;oi&angmsd=6qp;ol&angmsdaa=888;1b5&angmsdab=889;1b6&angmsdac=88a;1b7&angmsdad=88b;1b8&angmsdae=88c;1b9&angmsdaf=88d;1ba&angmsdag=88e;1bb&angmsdah=88f;1bc&angrt=6qn;og&angrtvb=6v2;yw&angrtvbd=87x;1b0&angsph=6qq;om&angst=5h;2u&angzarr=70c;12z&aogon=79;4n&aopf=2kpe;1mb&ap=6rs;r8&apE=8ds;1ej&apacir=8dr;1eh&ape=6ru;rd&apid=6rv;rf&apos=13;a&approx=6rs;r5&approxeq=6ru;rc&aring=6d;3q&ascr=2kl2;1ju&ast=16;e&asymp=6rs;r6&asympeq=6rx;rj&atilde=6b;3o&auml=6c;3p&awconint=6r7;ps&awint=8b5;1cr&bNot=8h9;1iy&backcong=6rw;rg&backepsilon=s6;af&backprime=6d1;ei&backsim=6rh;qc&backsimeq=6vh;zp&barvee=6v1;yv&barwed=6x1;11y&barwedge=6x1;11x&bbrk=71x;137&bbrktbrk=71y;138&bcong=6rw;rh&bcy=tt;bs&bdquo=6ce;e4&becaus=6r9;py&because=6r9;px&bemptyv=88g;1bd&bepsi=s6;ag&bernou=6jw;go&beta=qa;93&beth=6k6;h5&between=6ss;tt&bfr=2knz;1l3&bigcap=6v6;z5&bigcirc=7hr;15s&bigcup=6v7;z7&bigodot=8ao;1cd&bigoplus=8ap;1cf&bigotimes=8aq;1ch&bigsqcup=8au;1cl&bigstar=7id;15z&bigtriangledown=7gd;15e&bigtriangleup=7g3;154&biguplus=8as;1cj&bigvee=6v5;z1&bigwedge=6v4;yy&bkarow=83x;17x&blacklozenge=8a3;1c9&blacksquare=7fu;14x&blacktriangle=7g4;156&blacktriangledown=7ge;15g&blacktriangleleft=7gi;15k&blacktriangleright=7g8;15a&blank=74z;13f&blk12=7f6;14r&blk14=7f5;14q&blk34=7f7;14s&block=7ew;14p&bne=1p,6hx;o&bnequiv=6sh,6hx;sm&bnot=6xc;12d&bopf=2kpf;1mc&bot=6ud;xx&bottom=6ud;xu&bowtie=6vc;zi&boxDL=7dj;141&boxDR=7dg;13y&boxDl=7di;140&boxDr=7df;13x&boxH=7dc;13u&boxHD=7dy;14g&boxHU=7e1;14j&boxHd=7dw;14e&boxHu=7dz;14h&boxUL=7dp;147&boxUR=7dm;144&boxUl=7do;146&boxUr=7dl;143&boxV=7dd;13v&boxVH=7e4;14m&boxVL=7dv;14d&boxVR=7ds;14a&boxVh=7e3;14l&boxVl=7du;14c&boxVr=7dr;149&boxbox=895;1bw&boxdL=7dh;13z&boxdR=7de;13w&boxdl=7bk;13m&boxdr=7bg;13l&boxh=7b4;13j&boxhD=7dx;14f&boxhU=7e0;14i&boxhd=7cc;13r&boxhu=7ck;13s&boxminus=6u7;xi&boxplus=6u6;xg&boxtimes=6u8;xk&boxuL=7dn;145&boxuR=7dk;142&boxul=7bs;13o&boxur=7bo;13n&boxv=7b6;13k&boxvH=7e2;14k&boxvL=7dt;14b&boxvR=7dq;148&boxvh=7cs;13t&boxvl=7c4;13q&boxvr=7bw;13p&bprime=6d1;ej&breve=k8;83&brvbar=4m;1k&bscr=2kl3;1jv&bsemi=6dr;er&bsim=6rh;qd&bsime=6vh;zq&bsol=2k;x&bsolb=891;1bv&bsolhsub=7uw;16r&bull=6ci;e9&bullet=6ci;e8&bump=6ry;rp&bumpE=8fi;1gu&bumpe=6rz;ru&bumpeq=6rz;rt&cacute=7b;4p&cap=6qx;pa&capand=8ck;1dq&capbrcup=8cp;1dv&capcap=8cr;1dx&capcup=8cn;1dt&capdot=8cg;1dn&caps=6qx,1e68;p9&caret=6dd;eo&caron=jr;81&ccaps=8ct;1dz&ccaron=7h;4v&ccedil=6f;3s&ccirc=7d;4r&ccups=8cs;1dy&ccupssm=8cw;1e0&cdot=7f;4t&cedil=54;2f&cemptyv=88i;1bf&cent=4i;1g&centerdot=53;2c&cfr=2ko0;1l4&chcy=uf;ce&check=7pv;16j&checkmark=7pv;16i&chi=qv;9s&cir=7gr;15q&cirE=88z;1bt&circ=jq;7z&circeq=6s7;sc&circlearrowleft=6nu;k6&circlearrowright=6nv;k8&circledR=4u;1w&circledS=79k;13g&circledast=6u3;xc&circledcirc=6u2;xa&circleddash=6u5;xe&cire=6s7;sd&cirfnint=8b4;1cq&cirmid=8hb;1j0&cirscir=88y;1bs&clubs=7kz;168&clubsuit=7kz;167&colon=1m;j&colone=6s4;s7&coloneq=6s4;s5&comma=18;g&commat=1s;u&comp=6pt;mv&compfn=6qg;o7&complement=6pt;mu&complexes=6iq;f6&cong=6rp;qz&congdot=8dp;1ef&conint=6r2;pj&copf=2kpg;1md&coprod=6q8;nr&copy=4p;1r&copysr=6jb;fz&crarr=6np;k1&cross=7pz;16k&cscr=2kl4;1jw&csub=8gf;1id&csube=8gh;1if&csup=8gg;1ie&csupe=8gi;1ig&ctdot=6wf;11g&cudarrl=854;18x&cudarrr=851;18u&cuepr=6vy;10m&cuesc=6vz;10o&cularr=6nq;k3&cularrp=859;190&cup=6qy;pc&cupbrcap=8co;1du&cupcap=8cm;1ds&cupcup=8cq;1dw&cupdot=6tp;we&cupor=8cl;1dr&cups=6qy,1e68;pb&curarr=6nr;k5&curarrm=858;18z&curlyeqprec=6vy;10l&curlyeqsucc=6vz;10n&curlyvee=6vi;zr&curlywedge=6vj;zt&curren=4k;1i&curvearrowleft=6nq;k2&curvearrowright=6nr;k4&cuvee=6vi;zs&cuwed=6vj;zu&cwconint=6r6;pq&cwint=6r5;po&cylcty=6y5;12u&dArr=6oj;m2&dHar=86d;19t&dagger=6cg;e5&daleth=6k8;h7&darr=6mr;ia&dash=6c0;dl&dashv=6ub;xr&dbkarow=83z;180&dblac=kd;8b&dcaron=7j;4x&dcy=tw;bv&dd=6km;hb&ddagger=6ch;e6&ddarr=6oa;ld&ddotseq=8dz;1ep&deg=4w;21&delta=qc;95&demptyv=88h;1be&dfisht=873;1aj&dfr=2ko1;1l5&dharl=6o3;kx&dharr=6o2;ku&diam=6v8;zc&diamond=6v8;zb&diamondsuit=7l2;16b&diams=7l2;16c&die=4o;1o&digamma=rh;a6&disin=6wi;11j&div=6v;49&divide=6v;48&divideontimes=6vb;zg&divonx=6vb;zh&djcy=uq;co&dlcorn=6xq;12n&dlcrop=6x9;12a&dollar=10;6&dopf=2kph;1me&dot=k9;85&doteq=6s0;rx&doteqdot=6s1;rz&dotminus=6rc;q2&dotplus=6qc;ny&dotsquare=6u9;xm&doublebarwedge=6x2;11z&downarrow=6mr;i9&downdownarrows=6oa;lc&downharpoonleft=6o3;kv&downharpoonright=6o2;ks&drbkarow=840;182&drcorn=6xr;12p&drcrop=6x8;129&dscr=2kl5;1jx&dscy=ut;cr&dsol=8ae;1cc&dstrok=7l;4z&dtdot=6wh;11i&dtri=7gf;15j&dtrif=7ge;15h&duarr=6ph;mo&duhar=86n;1a5&dwangle=886;1b3&dzcy=v3;d0&dzigrarr=7wf;17r&eDDot=8dz;1eq&eDot=6s1;s0&eacute=6h;3u&easter=8dq;1eg&ecaron=7v;57&ecir=6s6;sb&ecirc=6i;3v&ecolon=6s5;s9&ecy=ul;ck&edot=7r;53&ee=6kn;he&efDot=6s2;s2&efr=2ko2;1l6&eg=8ey;1g9&egrave=6g;3t&egs=8eu;1g5&egsdot=8ew;1g7&el=8ex;1g8&elinters=73b;13e&ell=6j7;fv&els=8et;1g3&elsdot=8ev;1g6&emacr=7n;51&empty=6px;n7&emptyset=6px;n5&emptyv=6px;n6&emsp=6bn;d2&emsp13=6bo;d3&emsp14=6bp;d4&eng=97;6h&ensp=6bm;d1&eogon=7t;55&eopf=2kpi;1mf&epar=6vp;103&eparsl=89v;1c6&eplus=8dt;1ek&epsi=qd;97&epsilon=qd;96&epsiv=s5;ae&eqcirc=6s6;sa&eqcolon=6s5;s8&eqsim=6rm;qq&eqslantgtr=8eu;1g4&eqslantless=8et;1g2&equals=1p;p&equest=6sf;sj&equiv=6sh;so&equivDD=8e0;1er&eqvparsl=89x;1c8&erDot=6s3;s4&erarr=86p;1a7&escr=6jz;gs&esdot=6s0;ry&esim=6rm;qr&eta=qf;99&eth=6o;41&euml=6j;3w&euro=6gc;f2&excl=x;2&exist=6pv;n0&expectation=6k0;gt&exponentiale=6kn;hd&fallingdotseq=6s2;s1&fcy=uc;cb&female=7k0;163&ffilig=1dkz;1ja&fflig=1dkw;1j7&ffllig=1dl0;1jb&ffr=2ko3;1l7&filig=1dkx;1j8&fjlig=2u,2y;15&flat=7l9;16e&fllig=1dky;1j9&fltns=7g1;153&fnof=b6;7v&fopf=2kpj;1mg&forall=6ps;mt&fork=6vo;102&forkv=8gp;1in&fpartint=8b1;1cp&frac12=59;2k&frac13=6kz;hh&frac14=58;2j&frac15=6l1;hj&frac16=6l5;hn&frac18=6l7;hp&frac23=6l0;hi&frac25=6l2;hk&frac34=5a;2m&frac35=6l3;hl&frac38=6l8;hq&frac45=6l4;hm&frac56=6l6;ho&frac58=6l9;hr&frac78=6la;hs&frasl=6dg;eq&frown=6xu;12r&fscr=2kl7;1jy&gE=6sn;t8&gEl=8ek;1ft&gacute=dx;7x&gamma=qb;94&gammad=rh;a7&gap=8ee;1fh&gbreve=7z;5b&gcirc=7x;59&gcy=tv;bu&gdot=81;5d&ge=6sl;sx&gel=6vv;10k&geq=6sl;sw&geqq=6sn;t7&geqslant=8e6;1f6&ges=8e6;1f7&gescc=8fd;1gn&gesdot=8e8;1f9&gesdoto=8ea;1fb&gesdotol=8ec;1fd&gesl=6vv,1e68;10h&gesles=8es;1g1&gfr=2ko4;1l8&gg=6sr;ts&ggg=6vt;10b&gimel=6k7;h6&gjcy=ur;cp&gl=6t3;un&glE=8eq;1fz&gla=8f9;1gj&glj=8f8;1gi&gnE=6sp;tg&gnap=8ei;1fp&gnapprox=8ei;1fo&gne=8eg;1fl&gneq=8eg;1fk&gneqq=6sp;tf&gnsim=6w7;10y&gopf=2kpk;1mh&grave=2o;14&gscr=6iy;f9&gsim=6sz;ud&gsime=8em;1fv&gsiml=8eo;1fx&gt=1q;s&gtcc=8fb;1gl&gtcir=8e2;1et&gtdot=6vr;107&gtlPar=87p;1aw&gtquest=8e4;1ev&gtrapprox=8ee;1fg&gtrarr=86w;1ad&gtrdot=6vr;106&gtreqless=6vv;10j&gtreqqless=8ek;1fs&gtrless=6t3;um&gtrsim=6sz;uc&gvertneqq=6sp,1e68;td&gvnE=6sp,1e68;te&hArr=6ok;m5&hairsp=6bu;da&half=59;2l&hamilt=6iz;fb&hardcy=ui;ch&harr=6ms;id&harrcir=85k;192&harrw=6nh;js&hbar=6j3;fl&hcirc=85;5g&hearts=7l1;16a&heartsuit=7l1;169&hellip=6cm;eb&hercon=6ux;yr&hfr=2ko5;1l9&hksearow=84l;18i&hkswarow=84m;18k&hoarr=6pr;mr&homtht=6rf;q5&hookleftarrow=6nd;jj&hookrightarrow=6ne;jl&hopf=2kpl;1mi&horbar=6c5;do&hscr=2kl9;1jz&hslash=6j3;fi&hstrok=87;5i&hybull=6df;ep&hyphen=6c0;dk&iacute=6l;3y&ic=6eb;f1&icirc=6m;3z&icy=u0;bz&iecy=tx;bw&iexcl=4h;1f&iff=6ok;m6&ifr=2ko6;1la&igrave=6k;3x&ii=6ko;hg&iiiint=8b0;1cn&iiint=6r1;pg&iinfin=89o;1c3&iiota=6jt;gm&ijlig=8j;5t&imacr=8b;5m&image=6j5;fp&imagline=6j4;fm&imagpart=6j5;fo&imath=8h;5r&imof=6uv;yo&imped=c5;7w&in=6q0;nd&incare=6it;f8&infin=6qm;of&infintie=89p;1c4&inodot=8h;5q&int=6qz;pe&intcal=6uy;yt&integers=6jo;gh&intercal=6uy;ys&intlarhk=8bb;1cx&intprod=8cc;1dk&iocy=up;cn&iogon=8f;5o&iopf=2kpm;1mj&iota=qh;9b&iprod=8cc;1dl&iquest=5b;2n&iscr=2kla;1k0&isin=6q0;nc&isinE=6wp;11r&isindot=6wl;11n&isins=6wk;11l&isinsv=6wj;11k&isinv=6q0;nb&it=6ea;ez&itilde=89;5k&iukcy=uu;cs&iuml=6n;40&jcirc=8l;5v&jcy=u1;c0&jfr=2ko7;1lb&jmath=fr;7y&jopf=2kpn;1mk&jscr=2klb;1k1&jsercy=uw;cu&jukcy=us;cq&kappa=qi;9c&kappav=s0;a9&kcedil=8n;5x&kcy=u2;c1&kfr=2ko8;1lc&kgreen=8o;5y&khcy=ud;cc&kjcy=v0;cy&kopf=2kpo;1ml&kscr=2klc;1k2&lAarr=6oq;mf&lArr=6og;ls&lAtail=84b;18a&lBarr=83y;17z&lE=6sm;t2&lEg=8ej;1fr&lHar=86a;19q&lacute=8q;60&laemptyv=88k;1bh&lagran=6j6;ft&lambda=qj;9d&lang=7vs;16z&langd=87l;1as&langle=7vs;16y&lap=8ed;1ff&laquo=4r;1t&larr=6mo;hx&larrb=6p0;mk&larrbfs=84f;18e&larrfs=84d;18c&larrhk=6nd;jk&larrlp=6nf;jo&larrpl=855;18y&larrsim=86r;1a9&larrtl=6n6;j7&lat=8ff;1gp&latail=849;188&late=8fh;1gt&lates=8fh,1e68;1gs&lbarr=83w;17w&lbbrk=7si;16p&lbrace=3f;16&lbrack=2j;v&lbrke=87f;1am&lbrksld=87j;1aq&lbrkslu=87h;1ao&lcaron=8u;64&lcedil=8s;62&lceil=6x4;122&lcub=3f;17&lcy=u3;c2&ldca=852;18v&ldquo=6cc;dz&ldquor=6ce;e3&ldrdhar=86f;19v&ldrushar=85n;195&ldsh=6nm;jz&le=6sk;st&leftarrow=6mo;hv&leftarrowtail=6n6;j6&leftharpoondown=6nx;kd&leftharpoonup=6nw;ka&leftleftarrows=6o7;l6&leftrightarrow=6ms;ic&leftrightarrows=6o6;l4&leftrightharpoons=6ob;lf&leftrightsquigarrow=6nh;jr&leftthreetimes=6vf;zl&leg=6vu;10g&leq=6sk;ss&leqq=6sm;t1&leqslant=8e5;1f0&les=8e5;1f1&lescc=8fc;1gm&lesdot=8e7;1f8&lesdoto=8e9;1fa&lesdotor=8eb;1fc&lesg=6vu,1e68;10d&lesges=8er;1g0&lessapprox=8ed;1fe&lessdot=6vq;104&lesseqgtr=6vu;10f&lesseqqgtr=8ej;1fq&lessgtr=6t2;uj&lesssim=6sy;u9&lfisht=870;1ag&lfloor=6x6;126&lfr=2ko9;1ld&lg=6t2;uk&lgE=8ep;1fy&lhard=6nx;kf&lharu=6nw;kc&lharul=86i;19y&lhblk=7es;14o&ljcy=ux;cv&ll=6sq;tm&llarr=6o7;l7&llcorner=6xq;12m&llhard=86j;19z&lltri=7i2;15w&lmidot=8w;66&lmoust=71s;131&lmoustache=71s;130&lnE=6so;tc&lnap=8eh;1fn&lnapprox=8eh;1fm&lne=8ef;1fj&lneq=8ef;1fi&lneqq=6so;tb&lnsim=6w6;10x&loang=7vw;175&loarr=6pp;mp&lobrk=7vq;16u&longleftarrow=7w5;178&longleftrightarrow=7w7;17e&longmapsto=7wc;17p&longrightarrow=7w6;17b&looparrowleft=6nf;jn&looparrowright=6ng;jp&lopar=879;1ak&lopf=2kpp;1mm&loplus=8bx;1d6&lotimes=8c4;1dc&lowast=6qf;o5&lowbar=2n;12&loz=7gq;15p&lozenge=7gq;15o&lozf=8a3;1ca&lpar=14;b&lparlt=87n;1au&lrarr=6o6;l5&lrcorner=6xr;12o&lrhar=6ob;lg&lrhard=86l;1a1&lrm=6by;di&lrtri=6v3;yx&lsaquo=6d5;ek&lscr=2kld;1k3&lsh=6nk;jw&lsim=6sy;ua&lsime=8el;1fu&lsimg=8en;1fw&lsqb=2j;w&lsquo=6c8;ds&lsquor=6ca;dw&lstrok=8y;68&lt=1o;n&ltcc=8fa;1gk&ltcir=8e1;1es&ltdot=6vq;105&lthree=6vf;zm&ltimes=6vd;zj&ltlarr=86u;1ac&ltquest=8e3;1eu&ltrPar=87q;1ax&ltri=7gj;15n&ltrie=6us;yi&ltrif=7gi;15l&lurdshar=85m;194&luruhar=86e;19u&lvertneqq=6so,1e68;t9&lvnE=6so,1e68;ta&mDDot=6re;q4&macr=4v;20&male=7k2;164&malt=7q8;16m&maltese=7q8;16l&map=6na;jg&mapsto=6na;jf&mapstodown=6nb;ji&mapstoleft=6n8;jb&mapstoup=6n9;jd&marker=7fy;152&mcomma=8bt;1d4&mcy=u4;c3&mdash=6c4;dn&measuredangle=6qp;ok&mfr=2koa;1le&mho=6jr;gj&micro=51;29&mid=6qr;oq&midast=16;d&midcir=8hc;1j1&middot=53;2d&minus=6qa;nu&minusb=6u7;xj&minusd=6rc;q3&minusdu=8bu;1d5&mlcp=8gr;1ip&mldr=6cm;ec&mnplus=6qb;nw&models=6uf;xy&mopf=2kpq;1mn&mp=6qb;nx&mscr=2kle;1k4&mstpos=6ri;qf&mu=qk;9e&multimap=6uw;yp&mumap=6uw;yq&nGg=6vt,mw;10a&nGt=6sr,6he;tp&nGtv=6sr,mw;to&nLeftarrow=6od;lk&nLeftrightarrow=6oe;lm&nLl=6vs,mw;108&nLt=6sq,6he;tj&nLtv=6sq,mw;ti&nRightarrow=6of;lo&nVDash=6un;y7&nVdash=6um;y6&nabla=6pz;n8&nacute=90;6a&nang=6qo,6he;oh&nap=6rt;rb&napE=8ds,mw;1ei&napid=6rv,mw;re&napos=95;6f&napprox=6rt;ra&natur=7la;16g&natural=7la;16f&naturals=6j9;fw&nbsp=4g;1e&nbump=6ry,mw;rm&nbumpe=6rz,mw;rr&ncap=8cj;1dp&ncaron=94;6e&ncedil=92;6c&ncong=6rr;r2&ncongdot=8dp,mw;1ee&ncup=8ci;1do&ncy=u5;c4&ndash=6c3;dm&ne=6sg;sl&neArr=6on;mb&nearhk=84k;18h&nearr=6mv;im&nearrow=6mv;il&nedot=6s0,mw;rv&nequiv=6si;sq&nesear=84o;18n&nesim=6rm,mw;qo&nexist=6pw;n3&nexists=6pw;n2&nfr=2kob;1lf&ngE=6sn,mw;t4&nge=6sx;u7&ngeq=6sx;u6&ngeqq=6sn,mw;t5&ngeqslant=8e6,mw;1f3&nges=8e6,mw;1f4&ngsim=6t1;uh&ngt=6sv;u1&ngtr=6sv;u0&nhArr=6oe;ln&nharr=6ni;ju&nhpar=8he;1j3&ni=6q3;nk&nis=6ws;11u&nisd=6wq;11s&niv=6q3;nj&njcy=uy;cw&nlArr=6od;ll&nlE=6sm,mw;sy&nlarr=6my;iu&nldr=6cl;ea&nle=6sw;u4&nleftarrow=6my;it&nleftrightarrow=6ni;jt&nleq=6sw;u3&nleqq=6sm,mw;sz&nleqslant=8e5,mw;1ex&nles=8e5,mw;1ey&nless=6su;tx&nlsim=6t0;uf&nlt=6su;ty&nltri=6wa;115&nltrie=6wc;11b&nmid=6qs;ou&nopf=2kpr;1mo&not=4s;1u&notin=6q1;ng&notinE=6wp,mw;11q&notindot=6wl,mw;11m&notinva=6q1;nf&notinvb=6wn;11p&notinvc=6wm;11o&notni=6q4;nn&notniva=6q4;nm&notnivb=6wu;11w&notnivc=6wt;11v&npar=6qu;p4&nparallel=6qu;p2&nparsl=8hp,6hx;1j5&npart=6pu,mw;mw&npolint=8b8;1cu&npr=6tc;vd&nprcue=6w0;10q&npre=8fj,mw;1gw&nprec=6tc;vc&npreceq=8fj,mw;1gx&nrArr=6of;lp&nrarr=6mz;iw&nrarrc=84z,mw;18s&nrarrw=6n1,mw;ix&nrightarrow=6mz;iv&nrtri=6wb;118&nrtrie=6wd;11e&nsc=6td;vg&nsccue=6w1;10s&nsce=8fk,mw;1h2&nscr=2klf;1k5&nshortmid=6qs;os&nshortparallel=6qu;p1&nsim=6rl;qm&nsime=6ro;qx&nsimeq=6ro;qw&nsmid=6qs;ot&nspar=6qu;p3&nsqsube=6w2;10u&nsqsupe=6w3;10w&nsub=6tg;vs&nsubE=8g5,mw;1hv&nsube=6tk;w2&nsubset=6te,6he;vi&nsubseteq=6tk;w1&nsubseteqq=8g5,mw;1hw&nsucc=6td;vf&nsucceq=8fk,mw;1h3&nsup=6th;vt&nsupE=8g6,mw;1hz&nsupe=6tl;w5&nsupset=6tf,6he;vn&nsupseteq=6tl;w4&nsupseteqq=8g6,mw;1i0&ntgl=6t5;ur&ntilde=6p;42&ntlg=6t4;up&ntriangleleft=6wa;114&ntrianglelefteq=6wc;11a&ntriangleright=6wb;117&ntrianglerighteq=6wd;11d&nu=ql;9f&num=z;5&numero=6ja;fy&numsp=6br;d5&nvDash=6ul;y5&nvHarr=83o;17u&nvap=6rx,6he;ri&nvdash=6uk;y4&nvge=6sl,6he;su&nvgt=1q,6he;q&nvinfin=89q;1c5&nvlArr=83m;17s&nvle=6sk,6he;sr&nvlt=1o,6he;l&nvltrie=6us,6he;yf&nvrArr=83n;17t&nvrtrie=6ut,6he;yj&nvsim=6rg,6he;q6&nwArr=6om;ma&nwarhk=84j;18g&nwarr=6mu;ij&nwarrow=6mu;ii&nwnear=84n;18m&oS=79k;13h&oacute=6r;44&oast=6u3;xd&ocir=6u2;xb&ocirc=6s;45&ocy=u6;c5&odash=6u5;xf&odblac=9d;6l&odiv=8c8;1dg&odot=6u1;x9&odsold=88s;1bn&oelig=9f;6n&ofcir=88v;1bp&ofr=2koc;1lg&ogon=kb;87&ograve=6q;43&ogt=88x;1br&ohbar=88l;1bi&ohm=q1;91&oint=6r2;pk&olarr=6nu;k7&olcir=88u;1bo&olcross=88r;1bm&oline=6da;en&olt=88w;1bq&omacr=99;6j&omega=qx;9u&omicron=qn;9h&omid=88m;1bj&ominus=6ty;x4&oopf=2kps;1mp&opar=88n;1bk&operp=88p;1bl&oplus=6tx;x2&or=6qw;p8&orarr=6nv;k9&ord=8d9;1ea&order=6k4;h1&orderof=6k4;h0&ordf=4q;1s&ordm=56;2h&origof=6uu;yn&oror=8d2;1e4&orslope=8d3;1e5&orv=8d7;1e8&oscr=6k4;h2&oslash=6w;4a&osol=6u0;x7&otilde=6t;46&otimes=6tz;x6&otimesas=8c6;1de&ouml=6u;47&ovbar=6yl;12x&par=6qt;oz&para=52;2a&parallel=6qt;ox&parsim=8hf;1j4&parsl=8hp;1j6&part=6pu;my&pcy=u7;c6&percnt=11;7&period=1a;h&permil=6cw;ed&perp=6ud;xw&pertenk=6cx;ee&pfr=2kod;1lh&phi=qu;9r&phiv=r9;a2&phmmat=6k3;gy&phone=7im;162&pi=qo;9i&pitchfork=6vo;101&piv=ra;a4&planck=6j3;fj&planckh=6j2;fh&plankv=6j3;fk&plus=17;f&plusacir=8bn;1cz&plusb=6u6;xh&pluscir=8bm;1cy&plusdo=6qc;nz&plusdu=8bp;1d1&pluse=8du;1el&plusmn=4x;23&plussim=8bq;1d2&plustwo=8br;1d3&pm=4x;24&pointint=8b9;1cv&popf=2kpt;1mq&pound=4j;1h&pr=6t6;uu&prE=8fn;1h7&prap=8fr;1he&prcue=6t8;v0&pre=8fj;1h0&prec=6t6;ut&precapprox=8fr;1hd&preccurlyeq=6t8;uz&preceq=8fj;1gz&precnapprox=8ft;1hh&precneqq=8fp;1h9&precnsim=6w8;10z&precsim=6ta;v5&prime=6cy;ef&primes=6jd;g2&prnE=8fp;1ha&prnap=8ft;1hi&prnsim=6w8;110&prod=6q7;np&profalar=6y6;12v&profline=6xe;12e&profsurf=6xf;12f&prop=6ql;oe&propto=6ql;oc&prsim=6ta;v6&prurel=6uo;y8&pscr=2klh;1k6&psi=qw;9t&puncsp=6bs;d6&qfr=2koe;1li&qint=8b0;1co&qopf=2kpu;1mr&qprime=6dz;es&qscr=2kli;1k7&quaternions=6j1;ff&quatint=8ba;1cw&quest=1r;t&questeq=6sf;si&quot=y;4&rAarr=6or;mh&rArr=6oi;lz&rAtail=84c;18b&rBarr=83z;181&rHar=86c;19s&race=6rh,mp;qb&racute=9h;6p&radic=6qi;o8&raemptyv=88j;1bg&rang=7vt;172&rangd=87m;1at&range=885;1b2&rangle=7vt;171&raquo=57;2i&rarr=6mq;i6&rarrap=86t;1ab&rarrb=6p1;mm&rarrbfs=84g;18f&rarrc=84z;18t&rarrfs=84e;18d&rarrhk=6ne;jm&rarrlp=6ng;jq&rarrpl=85h;191&rarrsim=86s;1aa&rarrtl=6n7;j9&rarrw=6n1;iz&ratail=84a;189&ratio=6ra;pz&rationals=6je;g4&rbarr=83x;17y&rbbrk=7sj;16q&rbrace=3h;1b&rbrack=2l;y&rbrke=87g;1an&rbrksld=87i;1ap&rbrkslu=87k;1ar&rcaron=9l;6t&rcedil=9j;6r&rceil=6x5;124&rcub=3h;1c&rcy=u8;c7&rdca=853;18w&rdldhar=86h;19x&rdquo=6cd;e2&rdquor=6cd;e1&rdsh=6nn;k0&real=6jg;g9&realine=6jf;g6&realpart=6jg;g8&reals=6jh;gc&rect=7fx;151&reg=4u;1y&rfisht=871;1ah&rfloor=6x7;128&rfr=2kof;1lj&rhard=6o1;kr&rharu=6o0;ko&rharul=86k;1a0&rho=qp;9j&rhov=s1;ab&rightarrow=6mq;i4&rightarrowtail=6n7;j8&rightharpoondown=6o1;kp&rightharpoonup=6o0;km&rightleftarrows=6o4;kz&rightleftharpoons=6oc;lh&rightrightarrows=6o9;la&rightsquigarrow=6n1;iy&rightthreetimes=6vg;zn&ring=ka;86&risingdotseq=6s3;s3&rlarr=6o4;l0&rlhar=6oc;lj&rlm=6bz;dj&rmoust=71t;133&rmoustache=71t;132&rnmid=8ha;1iz&roang=7vx;176&roarr=6pq;mq&robrk=7vr;16w&ropar=87a;1al&ropf=2kpv;1ms&roplus=8by;1d7&rotimes=8c5;1dd&rpar=15;c&rpargt=87o;1av&rppolint=8b6;1cs&rrarr=6o9;lb&rsaquo=6d6;el&rscr=2klj;1k8&rsh=6nl;jy&rsqb=2l;z&rsquo=6c9;dv&rsquor=6c9;du&rthree=6vg;zo&rtimes=6ve;zk&rtri=7g9;15d&rtrie=6ut;ym&rtrif=7g8;15b&rtriltri=89a;1by&ruluhar=86g;19w&rx=6ji;ge&sacute=9n;6v&sbquo=6ca;dx&sc=6t7;ux&scE=8fo;1h8&scap=8fs;1hg&scaron=9t;71&sccue=6t9;v3&sce=8fk;1h6&scedil=9r;6z&scirc=9p;6x&scnE=8fq;1hc&scnap=8fu;1hk&scnsim=6w9;112&scpolint=8b7;1ct&scsim=6tb;va&scy=u9;c8&sdot=6v9;zd&sdotb=6u9;xn&sdote=8di;1ec&seArr=6oo;mc&searhk=84l;18j&searr=6mw;ip&searrow=6mw;io&sect=4n;1l&semi=1n;k&seswar=84p;18p&setminus=6qe;o2&setmn=6qe;o4&sext=7qu;16n&sfr=2kog;1lk&sfrown=6xu;12q&sharp=7lb;16h&shchcy=uh;cg&shcy=ug;cf&shortmid=6qr;oo&shortparallel=6qt;ow&shy=4t;1v&sigma=qr;9n&sigmaf=qq;9l&sigmav=qq;9m&sim=6rg;qa&simdot=8dm;1ed&sime=6rn;qu&simeq=6rn;qt&simg=8f2;1gb&simgE=8f4;1gd&siml=8f1;1ga&simlE=8f3;1gc&simne=6rq;r0&simplus=8bo;1d0&simrarr=86q;1a8&slarr=6mo;hw&smallsetminus=6qe;o0&smashp=8c3;1db&smeparsl=89w;1c7&smid=6qr;op&smile=6xv;12t&smt=8fe;1go&smte=8fg;1gr&smtes=8fg,1e68;1gq&softcy=uk;cj&sol=1b;i&solb=890;1bu&solbar=6yn;12y&sopf=2kpw;1mt&spades=7kw;166&spadesuit=7kw;165&spar=6qt;oy&sqcap=6tv;wx&sqcaps=6tv,1e68;wv&sqcup=6tw;x0&sqcups=6tw,1e68;wy&sqsub=6tr;wk&sqsube=6tt;wr&sqsubset=6tr;wj&sqsubseteq=6tt;wq&sqsup=6ts;wo&sqsupe=6tu;wu&sqsupset=6ts;wn&sqsupseteq=6tu;wt&squ=7fl;14v&square=7fl;14u&squarf=7fu;14y&squf=7fu;14z&srarr=6mq;i5&sscr=2klk;1k9&ssetmn=6qe;o3&ssmile=6xv;12s&sstarf=6va;ze&star=7ie;161&starf=7id;160&straightepsilon=s5;ac&straightphi=r9;a0&strns=4v;1z&sub=6te;vl&subE=8g5;1hy&subdot=8fx;1hn&sube=6ti;vw&subedot=8g3;1ht&submult=8g1;1hr&subnE=8gb;1i8&subne=6tm;w9&subplus=8fz;1hp&subrarr=86x;1ae&subset=6te;vk&subseteq=6ti;vv&subseteqq=8g5;1hx&subsetneq=6tm;w8&subsetneqq=8gb;1i7&subsim=8g7;1i3&subsub=8gl;1ij&subsup=8gj;1ih&succ=6t7;uw&succapprox=8fs;1hf&succcurlyeq=6t9;v2&succeq=8fk;1h5&succnapprox=8fu;1hj&succneqq=8fq;1hb&succnsim=6w9;111&succsim=6tb;v9&sum=6q9;nt&sung=7l6;16d&sup=6tf;vr&sup1=55;2g&sup2=4y;25&sup3=4z;26&supE=8g6;1i2&supdot=8fy;1ho&supdsub=8go;1im&supe=6tj;vz&supedot=8g4;1hu&suphsol=7ux;16s&suphsub=8gn;1il&suplarr=86z;1af&supmult=8g2;1hs&supnE=8gc;1ic&supne=6tn;wd&supplus=8g0;1hq&supset=6tf;vq&supseteq=6tj;vy&supseteqq=8g6;1i1&supsetneq=6tn;wc&supsetneqq=8gc;1ib&supsim=8g8;1i4&supsub=8gk;1ii&supsup=8gm;1ik&swArr=6op;md&swarhk=84m;18l&swarr=6mx;is&swarrow=6mx;ir&swnwar=84q;18r&szlig=67;3k&target=6xi;12h&tau=qs;9o&tbrk=71w;135&tcaron=9x;75&tcedil=9v;73&tcy=ua;c9&tdot=6hn;f4&telrec=6xh;12g&tfr=2koh;1ll&there4=6r8;pv&therefore=6r8;pu&theta=qg;9a&thetasym=r5;9v&thetav=r5;9x&thickapprox=6rs;r3&thicksim=6rg;q7&thinsp=6bt;d8&thkap=6rs;r7&thksim=6rg;q8&thorn=72;4g&tilde=kc;89&times=5z;3c&timesb=6u8;xl&timesbar=8c1;1da&timesd=8c0;1d9&tint=6r1;ph&toea=84o;18o&top=6uc;xt&topbot=6ye;12w&topcir=8hd;1j2&topf=2kpx;1mu&topfork=8gq;1io&tosa=84p;18q&tprime=6d0;eh&trade=6jm;gg&triangle=7g5;158&triangledown=7gf;15i&triangleleft=7gj;15m&trianglelefteq=6us;yh&triangleq=6sc;sg&triangleright=7g9;15c&trianglerighteq=6ut;yl&tridot=7ho;15r&trie=6sc;sh&triminus=8ca;1di&triplus=8c9;1dh&trisb=899;1bx&tritime=8cb;1dj&trpezium=736;13d&tscr=2kll;1ka&tscy=ue;cd&tshcy=uz;cx&tstrok=9z;77&twixt=6ss;tu&twoheadleftarrow=6n2;j0&twoheadrightarrow=6n4;j3&uArr=6oh;lv&uHar=86b;19r&uacute=6y;4c&uarr=6mp;i1&ubrcy=v2;cz&ubreve=a5;7d&ucirc=6z;4d&ucy=ub;ca&udarr=6o5;l2&udblac=a9;7h&udhar=86m;1a3&ufisht=872;1ai&ufr=2koi;1lm&ugrave=6x;4b&uharl=6nz;kl&uharr=6ny;ki&uhblk=7eo;14n&ulcorn=6xo;12j&ulcorner=6xo;12i&ulcrop=6xb;12c&ultri=7i0;15u&umacr=a3;7b&uml=4o;1p&uogon=ab;7j&uopf=2kpy;1mv&uparrow=6mp;i0&updownarrow=6mt;if&upharpoonleft=6nz;kj&upharpoonright=6ny;kg&uplus=6tq;wg&upsi=qt;9q&upsih=r6;9y&upsilon=qt;9p&upuparrows=6o8;l8&urcorn=6xp;12l&urcorner=6xp;12k&urcrop=6xa;12b&uring=a7;7f&urtri=7i1;15v&uscr=2klm;1kb&utdot=6wg;11h&utilde=a1;79&utri=7g5;159&utrif=7g4;157&uuarr=6o8;l9&uuml=70;4e&uwangle=887;1b4&vArr=6ol;m9&vBar=8h4;1iu&vBarv=8h5;1iv&vDash=6ug;y0&vangrt=87w;1az&varepsilon=s5;ad&varkappa=s0;a8&varnothing=6px;n4&varphi=r9;a1&varpi=ra;a3&varpropto=6ql;ob&varr=6mt;ig&varrho=s1;aa&varsigma=qq;9k&varsubsetneq=6tm,1e68;w6&varsubsetneqq=8gb,1e68;1i5&varsupsetneq=6tn,1e68;wa&varsupsetneqq=8gc,1e68;1i9&vartheta=r5;9w&vartriangleleft=6uq;y9&vartriangleright=6ur;yc&vcy=tu;bt&vdash=6ua;xp&vee=6qw;p7&veebar=6uz;yu&veeeq=6sa;sf&vellip=6we;11f&verbar=3g;19&vert=3g;1a&vfr=2koj;1ln&vltri=6uq;yb&vnsub=6te,6he;vj&vnsup=6tf,6he;vo&vopf=2kpz;1mw&vprop=6ql;od&vrtri=6ur;ye&vscr=2kln;1kc&vsubnE=8gb,1e68;1i6&vsubne=6tm,1e68;w7&vsupnE=8gc,1e68;1ia&vsupne=6tn,1e68;wb&vzigzag=87u;1ay&wcirc=ad;7l&wedbar=8db;1eb&wedge=6qv;p5&wedgeq=6s9;se&weierp=6jc;g0&wfr=2kok;1lo&wopf=2kq0;1mx&wp=6jc;g1&wr=6rk;qk&wreath=6rk;qj&wscr=2klo;1kd&xcap=6v6;z6&xcirc=7hr;15t&xcup=6v7;z9&xdtri=7gd;15f&xfr=2kol;1lp&xhArr=7wa;17o&xharr=7w7;17f&xi=qm;9g&xlArr=7w8;17i&xlarr=7w5;179&xmap=7wc;17q&xnis=6wr;11t&xodot=8ao;1ce&xopf=2kq1;1my&xoplus=8ap;1cg&xotime=8aq;1ci&xrArr=7w9;17l&xrarr=7w6;17c&xscr=2klp;1ke&xsqcup=8au;1cm&xuplus=8as;1ck&xutri=7g3;155&xvee=6v5;z2&xwedge=6v4;yz&yacute=71;4f&yacy=un;cm&ycirc=af;7n&ycy=uj;ci&yen=4l;1j&yfr=2kom;1lq&yicy=uv;ct&yopf=2kq2;1mz&yscr=2klq;1kf&yucy=um;cl&yuml=73;4h&zacute=ai;7q&zcaron=am;7u&zcy=tz;by&zdot=ak;7s&zeetrf=6js;gk&zeta=qe;98&zfr=2kon;1lr&zhcy=ty;bx&zigrarr=6ot;mi&zopf=2kq3;1n0&zscr=2klr;1kg&zwj=6bx;dh&zwnj=6bw;dg&\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/FormElement.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.HttpConnection;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.Selector;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * An HTML Form Element provides ready access to the form fields/controls that are associated with it. It also allows a\n * form to easily be submitted.\n */\npublic class FormElement extends Element {\n    private final Elements linkedEls = new Elements();\n    // contains form submittable elements that were linked during the parse (and due to parse rules, may no longer be a child of this form)\n    private static final Evaluator submittable = Selector.evaluatorOf(StringUtil.join(SharedConstants.FormSubmitTags, \", \"));\n\n    /**\n     * Create a new, standalone form element.\n     *\n     * @param tag        tag of this element\n     * @param baseUri    the base URI\n     * @param attributes initial attributes\n     */\n    public FormElement(Tag tag, @Nullable String baseUri, @Nullable Attributes attributes) {\n        super(tag, baseUri, attributes);\n    }\n\n    /**\n     * Get the list of form control elements associated with this form.\n     * @return form controls associated with this element.\n     */\n    public Elements elements() {\n        // As elements may have been added or removed from the DOM after parse, prepare a new list that unions them:\n        Elements els = select(submittable); // current form children\n        for (Element linkedEl : linkedEls) {\n            if (linkedEl.ownerDocument() != null && !els.contains(linkedEl)) {\n                els.add(linkedEl); // adds previously linked elements, that weren't previously removed from the DOM\n            }\n        }\n\n        return els;\n    }\n\n    /**\n     * Add a form control element to this form.\n     * @param element form control to add\n     * @return this form element, for chaining\n     */\n    public FormElement addElement(Element element) {\n        linkedEls.add(element);\n        return this;\n    }\n\n    @Override\n    protected void removeChild(Node out) {\n        super.removeChild(out);\n        linkedEls.remove(out);\n    }\n\n    /**\n     Prepare to submit this form. A Connection object is created with the request set up from the form values. This\n     Connection will inherit the settings and the cookies (etc) of the connection/session used to request this Document\n     (if any), as available in {@link Document#connection()}\n     <p>You can then set up other options (like user-agent, timeout, cookies), then execute it.</p>\n\n     @return a connection prepared from the values of this form, in the same session as the one used to request it\n     @throws IllegalArgumentException if the form's absolute action URL cannot be determined. Make sure you pass the\n     document's base URI when parsing.\n     */\n    public Connection submit() {\n        String action = hasAttr(\"action\") ? absUrl(\"action\") : baseUri();\n        Validate.notEmpty(action, \"Could not determine a form action URL for submit. Ensure you set a base URI when parsing.\");\n        Connection.Method method = attr(\"method\").equalsIgnoreCase(\"POST\") ?\n                Connection.Method.POST : Connection.Method.GET;\n\n        Document owner = ownerDocument();\n        Connection connection = owner != null? owner.connection().newRequest() : Jsoup.newSession();\n        return connection.url(action)\n                .data(formData())\n                .method(method);\n    }\n\n    /**\n     * Get the data that this form submits. The returned list is a copy of the data, and changes to the contents of the\n     * list will not be reflected in the DOM.\n     * @return a list of key vals\n     */\n    public List<Connection.KeyVal> formData() {\n        ArrayList<Connection.KeyVal> data = new ArrayList<>();\n\n        // iterate the form control elements and accumulate their values\n        Elements formEls = elements();\n        for (Element el: formEls) {\n            if (!el.tag().isFormSubmittable()) continue; // contents are form listable, superset of submitable\n            if (el.hasAttr(\"disabled\")) continue; // skip disabled form inputs\n            String name = el.attr(\"name\");\n            if (name.length() == 0) continue;\n            String type = el.attr(\"type\");\n\n            if (type.equalsIgnoreCase(\"button\") || type.equalsIgnoreCase(\"image\")) continue; // browsers don't submit these\n\n            if (el.nameIs(\"select\")) {\n                Elements options = el.select(\"option[selected]\");\n                boolean set = false;\n                for (Element option: options) {\n                    data.add(HttpConnection.KeyVal.create(name, option.val()));\n                    set = true;\n                }\n                if (!set) {\n                    Element option = el.selectFirst(\"option\");\n                    if (option != null)\n                        data.add(HttpConnection.KeyVal.create(name, option.val()));\n                }\n            } else if (\"checkbox\".equalsIgnoreCase(type) || \"radio\".equalsIgnoreCase(type)) {\n                // only add checkbox or radio if they have the checked attribute\n                if (el.hasAttr(\"checked\")) {\n                    final String val = el.val().length() >  0 ? el.val() : \"on\";\n                    data.add(HttpConnection.KeyVal.create(name, val));\n                }\n            } else {\n                data.add(HttpConnection.KeyVal.create(name, el.val()));\n            }\n        }\n        return data;\n    }\n\n    @Override\n    public FormElement clone() {\n        return (FormElement) super.clone();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/LeafNode.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.List;\n\n/**\n A node that does not hold any children. E.g.: {@link TextNode}, {@link DataNode}, {@link Comment}.\n */\npublic abstract class LeafNode extends Node {\n    Object value; // either a string value, or an attribute map (in the rare case multiple attributes are set)\n\n    public LeafNode() {\n        value = \"\";\n    }\n\n    protected LeafNode(String coreValue) {\n        Validate.notNull(coreValue);\n        value = coreValue;\n    }\n\n    @Override protected final boolean hasAttributes() {\n        return value instanceof Attributes;\n    }\n\n    @Override\n    public final Attributes attributes() {\n        ensureAttributes();\n        return (Attributes) value;\n    }\n\n    private void ensureAttributes() {\n        if (!hasAttributes()) { // then value is String coreValue\n            String coreValue = (String) value;\n            Attributes attributes = new Attributes();\n            value = attributes;\n            attributes.put(nodeName(), coreValue);\n        }\n    }\n\n    String coreValue() {\n        return attr(nodeName());\n    }\n\n    @Override @Nullable\n    public Element parent() {\n        return parentNode;\n    }\n\n    @Override\n    public String nodeValue() {\n        return coreValue();\n    }\n\n    void coreValue(String value) {\n        attr(nodeName(), value);\n    }\n\n    @Override\n    public String attr(String key) {\n        if (!hasAttributes()) {\n            return nodeName().equals(key) ? (String) value : EmptyString;\n        }\n        return super.attr(key);\n    }\n\n    @Override\n    public Node attr(String key, String value) {\n        if (!hasAttributes() && key.equals(nodeName())) {\n            this.value = value;\n        } else {\n            ensureAttributes();\n            super.attr(key, value);\n        }\n        return this;\n    }\n\n    @Override\n    public boolean hasAttr(String key) {\n        ensureAttributes();\n        return super.hasAttr(key);\n    }\n\n    @Override\n    public Node removeAttr(String key) {\n        ensureAttributes();\n        return super.removeAttr(key);\n    }\n\n    @Override\n    public String absUrl(String key) {\n        ensureAttributes();\n        return super.absUrl(key);\n    }\n\n    @Override\n    public String baseUri() {\n        return parentNode != null ? parentNode.baseUri() : \"\";\n    }\n\n    @Override\n    protected void doSetBaseUri(String baseUri) {\n        // noop\n    }\n\n    @Override\n    public int childNodeSize() {\n        return 0;\n    }\n\n    @Override\n    public Node empty() {\n        return this;\n    }\n\n    @Override\n    protected List<Node> ensureChildNodes() {\n        return EmptyNodes;\n    }\n\n    @Override\n    void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) {}\n\n    @Override\n    protected LeafNode doClone(Node parent) {\n        LeafNode clone = (LeafNode) super.doClone(parent);\n\n        // Object value could be plain string or attributes - need to clone\n        if (hasAttributes())\n            clone.value = ((Attributes) value).clone();\n\n        return clone;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Node.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.select.NodeFilter;\nimport org.jsoup.select.NodeVisitor;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.function.Consumer;\nimport java.util.stream.Stream;\n\n/**\n The base, abstract Node model. {@link Element}, {@link Document}, {@link Comment}, {@link TextNode}, et al.,\n are instances of Node.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic abstract class Node implements Cloneable {\n    static final List<Node> EmptyNodes = Collections.emptyList();\n    static final String EmptyString = \"\";\n    @Nullable Element parentNode; // Nodes don't always have parents\n    int siblingIndex;\n\n    /**\n     * Default constructor. Doesn't set up base uri, children, or attributes; use with caution.\n     */\n    protected Node() {\n    }\n\n    /**\n     Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof).\n     @return node name\n     */\n    public abstract String nodeName();\n\n    /**\n     Get the normalized name of this node. For node types other than Element, this is the same as {@link #nodeName()}.\n     For an Element, will be the lower-cased tag name.\n     @return normalized node name\n     @since 1.15.4.\n     */\n    public String normalName() {\n        return nodeName();\n    }\n\n    /**\n     Get the node's value. For a TextNode, the whole text; for a Comment, the comment data; for an Element,\n     wholeOwnText. Returns \"\" if there is no value.\n     @return the node's value\n     */\n    public String nodeValue() {\n        return \"\";\n    }\n\n    /**\n     Test if this node has the specified normalized name, in any namespace.\n     * @param normalName a normalized element name (e.g. {@code div}).\n     * @return true if the element's normal name matches exactly\n     * @since 1.17.2\n     */\n    public boolean nameIs(String normalName) {\n        return normalName().equals(normalName);\n    }\n\n    /**\n     Test if this node's parent has the specified normalized name.\n     * @param normalName a normalized name (e.g. {@code div}).\n     * @return true if the parent element's normal name matches exactly\n     * @since 1.17.2\n     */\n    public boolean parentNameIs(String normalName) {\n        return parentNode != null && parentNode.normalName().equals(normalName);\n    }\n\n    /**\n     Test if this node's parent is an Element with the specified normalized name and namespace.\n     * @param normalName a normalized element name (e.g. {@code div}).\n     * @param namespace the namespace\n     * @return true if the parent element's normal name matches exactly, and that element is in the specified namespace\n     * @since 1.17.2\n     */\n    public boolean parentElementIs(String normalName, String namespace) {\n        return parentNode != null && parentNode instanceof Element\n            && ((Element) parentNode).elementIs(normalName, namespace);\n    }\n\n    /**\n     * Check if this Node has an actual Attributes object.\n     */\n    protected abstract boolean hasAttributes();\n\n    /**\n     Checks if this node has a parent. Nodes won't have parents if (e.g.) they are newly created and not added as a child\n     to an existing node, or if they are a {@link #shallowClone()}. In such cases, {@link #parent()} will return {@code null}.\n     @return if this node has a parent.\n     */\n    public boolean hasParent() {\n        return parentNode != null;\n    }\n\n    /**\n     * Get an attribute's value by its key. <b>Case insensitive</b>\n     * <p>\n     * To get an absolute URL from an attribute that may be a relative URL, prefix the key with <code><b>abs:</b></code>,\n     * which is a shortcut to the {@link #absUrl} method.\n     * </p>\n     * E.g.:\n     * <blockquote><code>String url = a.attr(\"abs:href\");</code></blockquote>\n     *\n     * @param attributeKey The attribute key.\n     * @return The attribute, or empty string if not present (to avoid nulls).\n     * @see #attributes()\n     * @see #hasAttr(String)\n     * @see #absUrl(String)\n     */\n    public String attr(String attributeKey) {\n        Validate.notNull(attributeKey);\n        if (!hasAttributes())\n            return EmptyString;\n\n        String val = attributes().getIgnoreCase(attributeKey);\n        if (val.length() > 0)\n            return val;\n        else if (attributeKey.startsWith(\"abs:\"))\n            return absUrl(attributeKey.substring(\"abs:\".length()));\n        else return \"\";\n    }\n\n    /**\n     * Get each of the Element's attributes.\n     * @return attributes (which implements Iterable, with the same order as presented in the original HTML).\n     */\n    public abstract Attributes attributes();\n\n    /**\n     Get the number of attributes that this Node has.\n     @return the number of attributes\n     @since 1.14.2\n     */\n    public int attributesSize() {\n        // added so that we can test how many attributes exist without implicitly creating the Attributes object\n        return hasAttributes() ? attributes().size() : 0;\n    }\n\n    /**\n     * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is\n     * <b>case insensitive</b>. The key will be set with case sensitivity as set in the parser settings.\n     * @param attributeKey The attribute key.\n     * @param attributeValue The attribute value.\n     * @return this (for chaining)\n     */\n    public Node attr(String attributeKey, String attributeValue) {\n        Document doc = ownerDocument();\n        ParseSettings settings = doc != null ? doc.parser().settings() : ParseSettings.htmlDefault;\n        attributeKey = settings.normalizeAttribute(attributeKey);\n        attributes().putIgnoreCase(attributeKey, attributeValue);\n        return this;\n    }\n\n    /**\n     * Test if this Node has an attribute. <b>Case insensitive</b>.\n     * @param attributeKey The attribute key to check.\n     * @return true if the attribute exists, false if not.\n     */\n    public boolean hasAttr(String attributeKey) {\n        Validate.notNull(attributeKey);\n        if (!hasAttributes())\n            return false;\n\n        if (attributeKey.startsWith(\"abs:\")) {\n            String key = attributeKey.substring(\"abs:\".length());\n            if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).isEmpty())\n                return true;\n        }\n        return attributes().hasKeyIgnoreCase(attributeKey);\n    }\n\n    /**\n     * Remove an attribute from this node.\n     * @param attributeKey The attribute to remove.\n     * @return this (for chaining)\n     */\n    public Node removeAttr(String attributeKey) {\n        Validate.notNull(attributeKey);\n        if (hasAttributes())\n            attributes().removeIgnoreCase(attributeKey);\n        return this;\n    }\n\n    /**\n     * Clear (remove) each of the attributes in this node.\n     * @return this, for chaining\n     */\n    public Node clearAttributes() {\n        if (hasAttributes()) {\n            Iterator<Attribute> it = attributes().iterator();\n            while (it.hasNext()) {\n                it.next();\n                it.remove();\n            }\n        }\n        return this;\n    }\n\n    /**\n     Get the base URI that applies to this node. Will return an empty string if not defined. Used to make relative links\n     absolute.\n\n     @return base URI\n     @see #absUrl\n     */\n    public abstract String baseUri();\n\n    /**\n     * Set the baseUri for just this node (not its descendants), if this Node tracks base URIs.\n     * @param baseUri new URI\n     */\n    protected abstract void doSetBaseUri(String baseUri);\n\n    /**\n     Update the base URI of this node and all of its descendants.\n     @param baseUri base URI to set\n     */\n    public void setBaseUri(final String baseUri) {\n        Validate.notNull(baseUri);\n        doSetBaseUri(baseUri);\n    }\n\n    /**\n     * Get an absolute URL from a URL attribute that may be relative (such as an <code>&lt;a href&gt;</code> or\n     * <code>&lt;img src&gt;</code>).\n     * <p>\n     * E.g.: <code>String absUrl = linkEl.absUrl(\"href\");</code>\n     * </p>\n     * <p>\n     * If the attribute value is already absolute (i.e. it starts with a protocol, like\n     * <code>http://</code> or <code>https://</code> etc), and it successfully parses as a URL, the attribute is\n     * returned directly. Otherwise, it is treated as a URL relative to the element's {@link #baseUri}, and made\n     * absolute using that.\n     * </p>\n     * <p>\n     * As an alternate, you can use the {@link #attr} method with the <code>abs:</code> prefix, e.g.:\n     * <code>String absUrl = linkEl.attr(\"abs:href\");</code>\n     * </p>\n     *\n     * @param attributeKey The attribute key\n     * @return An absolute URL if one could be made, or an empty string (not null) if the attribute was missing or\n     * could not be made successfully into a URL.\n     * @see #attr\n     * @see java.net.URL#URL(java.net.URL, String)\n     */\n    public String absUrl(String attributeKey) {\n        Validate.notEmpty(attributeKey);\n        if (!(hasAttributes() && attributes().hasKeyIgnoreCase(attributeKey))) // not using hasAttr, so that we don't recurse down hasAttr->absUrl\n            return \"\";\n\n        return StringUtil.resolve(baseUri(), attributes().getIgnoreCase(attributeKey));\n    }\n\n    protected abstract List<Node> ensureChildNodes();\n\n    /**\n     Get a child node by its 0-based index.\n     @param index index of child node\n     @return the child node at this index.\n     @throws IndexOutOfBoundsException if the index is out of bounds.\n     */\n    public Node childNode(int index) {\n        return ensureChildNodes().get(index);\n    }\n\n    /**\n     Get this node's children. Presented as an unmodifiable list: new children can not be added, but the child nodes\n     themselves can be manipulated.\n     @return list of children. If no children, returns an empty list.\n     */\n    public List<Node> childNodes() {\n        if (childNodeSize() == 0)\n            return EmptyNodes;\n\n        List<Node> children = ensureChildNodes();\n        List<Node> rewrap = new ArrayList<>(children.size()); // wrapped so that looping and moving will not throw a CME as the source changes\n        rewrap.addAll(children);\n        return Collections.unmodifiableList(rewrap);\n    }\n\n    /**\n     * Returns a deep copy of this node's children. Changes made to these nodes will not be reflected in the original\n     * nodes\n     * @return a deep copy of this node's children\n     */\n    public List<Node> childNodesCopy() {\n        final List<Node> nodes = ensureChildNodes();\n        final ArrayList<Node> children = new ArrayList<>(nodes.size());\n        for (Node node : nodes) {\n            children.add(node.clone());\n        }\n        return children;\n    }\n\n    /**\n     * Get the number of child nodes that this node holds.\n     * @return the number of child nodes that this node holds.\n     */\n    public abstract int childNodeSize();\n\n    protected Node[] childNodesAsArray() {\n        return ensureChildNodes().toArray(new Node[0]);\n    }\n\n    /**\n     * Delete all this node's children.\n     * @return this node, for chaining\n     */\n    public abstract Node empty();\n\n    /**\n     Gets this node's parent node. This is always an Element.\n     @return parent node; or null if no parent.\n     @see #hasParent()\n     @see #parentElement();\n     */\n    public @Nullable Node parent() {\n        return parentNode;\n    }\n\n    /**\n     Gets this node's parent Element.\n     @return parent element; or null if this node has no parent.\n     @see #hasParent()\n     @since 1.21.1\n     */\n    public @Nullable Element parentElement() {\n        return parentNode;\n    }\n\n    /**\n     Gets this node's parent node. Not overridable by extending classes, so useful if you really just need the Node type.\n     @return parent node; or null if no parent.\n     */\n    public @Nullable final Node parentNode() {\n        return parentNode;\n    }\n\n    /**\n     * Get this node's root node; that is, its topmost ancestor. If this node is the top ancestor, returns {@code this}.\n     * @return topmost ancestor.\n     */\n    public Node root() {\n        Node node = this;\n        while (node.parentNode != null)\n            node = node.parentNode;\n        return node;\n    }\n\n    /**\n     * Gets the Document associated with this Node.\n     * @return the Document associated with this Node, or null if there is no such Document.\n     */\n    public @Nullable Document ownerDocument() {\n        Node node = this;\n        while (node != null) {\n            if (node instanceof Document) return (Document) node;\n            node = node.parentNode;\n        }\n        return null;\n    }\n\n    /**\n     * Remove (delete) this node from the DOM tree. If this node has children, they are also removed. If this node is\n     * an orphan, nothing happens.\n     */\n    public void remove() {\n        if (parentNode != null)\n            parentNode.removeChild(this);\n    }\n\n    /**\n     * Insert the specified HTML into the DOM before this node (as a preceding sibling).\n     * @param html HTML to add before this node\n     * @return this node, for chaining\n     * @see #after(String)\n     */\n    public Node before(String html) {\n        addSiblingHtml(siblingIndex(), html);\n        return this;\n    }\n\n    /**\n     * Insert the specified node into the DOM before this node (as a preceding sibling).\n     * @param node to add before this node\n     * @return this node, for chaining\n     * @see #after(Node)\n     */\n    public Node before(Node node) {\n        Validate.notNull(node);\n        Validate.notNull(parentNode);\n\n        // if the incoming node is a sibling of this, remove it first so siblingIndex is correct on add\n        if (node.parentNode == parentNode) node.remove();\n\n        parentNode.addChildren(siblingIndex(), node);\n        return this;\n    }\n\n    /**\n     * Insert the specified HTML into the DOM after this node (as a following sibling).\n     * @param html HTML to add after this node\n     * @return this node, for chaining\n     * @see #before(String)\n     */\n    public Node after(String html) {\n        addSiblingHtml(siblingIndex() + 1, html);\n        return this;\n    }\n\n    /**\n     * Insert the specified node into the DOM after this node (as a following sibling).\n     * @param node to add after this node\n     * @return this node, for chaining\n     * @see #before(Node)\n     */\n    public Node after(Node node) {\n        Validate.notNull(node);\n        Validate.notNull(parentNode);\n\n        // if the incoming node is a sibling of this, remove it first so siblingIndex is correct on add\n        if (node.parentNode == parentNode) node.remove();\n\n        parentNode.addChildren(siblingIndex() + 1, node);\n        return this;\n    }\n\n    private void addSiblingHtml(int index, String html) {\n        Validate.notNull(html);\n        Validate.notNull(parentNode);\n\n        Element context = parentNode instanceof Element ? (Element) parentNode : null;\n        List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri());\n        parentNode.addChildren(index, nodes.toArray(new Node[0]));\n    }\n\n    /**\n     Wrap the supplied HTML around this node.\n\n     @param html HTML to wrap around this node, e.g. {@code <div class=\"head\"></div>}. Can be arbitrarily deep. If\n     the input HTML does not parse to a result starting with an Element, this will be a no-op.\n     @return this node, for chaining.\n     */\n    public Node wrap(String html) {\n        Validate.notEmpty(html);\n\n        // Parse context - parent (because wrapping), this, or null\n        Element context =\n            parentNode != null && parentNode instanceof Element ? (Element) parentNode :\n                this instanceof Element ? (Element) this :\n                    null;\n        List<Node> wrapChildren = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri());\n        Node wrapNode = wrapChildren.get(0);\n        if (!(wrapNode instanceof Element)) // nothing to wrap with; noop\n            return this;\n\n        Element wrap = (Element) wrapNode;\n        Element deepest = getDeepChild(wrap);\n        if (parentNode != null)\n            parentNode.replaceChild(this, wrap);\n        deepest.addChildren(this); // side effect of tricking wrapChildren to lose first\n\n        // remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder\n        if (wrapChildren.size() > 0) {\n            //noinspection ForLoopReplaceableByForEach (beacause it allocates an Iterator which is wasteful here)\n            for (int i = 0; i < wrapChildren.size(); i++) {\n                Node remainder = wrapChildren.get(i);\n                // if no parent, this could be the wrap node, so skip\n                if (wrap == remainder)\n                    continue;\n\n                if (remainder.parentNode != null)\n                    remainder.parentNode.removeChild(remainder);\n                wrap.after(remainder);\n            }\n        }\n        return this;\n    }\n\n    /**\n     * Removes this node from the DOM, and moves its children up into the node's parent. This has the effect of dropping\n     * the node but keeping its children.\n     * <p>\n     * For example, with the input html:\n     * </p>\n     * <p>{@code <div>One <span>Two <b>Three</b></span></div>}</p>\n     * Calling {@code element.unwrap()} on the {@code span} element will result in the html:\n     * <p>{@code <div>One Two <b>Three</b></div>}</p>\n     * and the {@code \"Two \"} {@link TextNode} being returned.\n     *\n     * @return the first child of this node, after the node has been unwrapped. @{code Null} if the node had no children.\n     * @see #remove()\n     * @see #wrap(String)\n     */\n    public @Nullable Node unwrap() {\n        Validate.notNull(parentNode);\n        Node firstChild = firstChild();\n        parentNode.addChildren(siblingIndex(), this.childNodesAsArray());\n        this.remove();\n\n        return firstChild;\n    }\n\n    private static Element getDeepChild(Element el) {\n        Element child = el.firstElementChild();\n        while (child != null) {\n            el = child;\n            child = child.firstElementChild();\n        }\n        return el;\n    }\n\n    /**\n     * Replace this node in the DOM with the supplied node.\n     * @param in the node that will replace the existing node.\n     */\n    public void replaceWith(Node in) {\n        Validate.notNull(in);\n        if (parentNode == null) parentNode = in.parentNode; // allows old to have been temp removed before replacing\n        Validate.notNull(parentNode);\n        parentNode.replaceChild(this, in);\n    }\n\n    protected void setParentNode(Node parentNode) {\n        Validate.notNull(parentNode);\n        if (this.parentNode != null)\n            this.parentNode.removeChild(this);\n        assert parentNode instanceof Element;\n        this.parentNode = (Element) parentNode;\n    }\n\n    protected void replaceChild(Node out, Node in) {\n        Validate.isTrue(out.parentNode == this);\n        Validate.notNull(in);\n        if (out == in) return; // no-op self replacement\n\n        if (in.parentNode != null)\n            in.parentNode.removeChild(in);\n\n        final int index = out.siblingIndex();\n        ensureChildNodes().set(index, in);\n        in.parentNode = (Element) this;\n        in.setSiblingIndex(index);\n        out.parentNode = null;\n\n        ((Element) this).childNodes.incrementMod(); // as mod count not changed in set(), requires explicit update, to invalidate the child element cache\n    }\n\n    protected void removeChild(Node out) {\n        Validate.isTrue(out.parentNode == this);\n        Element el = (Element) this;\n        if (el.hasValidChildren()) // can remove by index\n            ensureChildNodes().remove(out.siblingIndex);\n        else\n            ensureChildNodes().remove(out); // iterates, but potentially not every one\n\n        el.invalidateChildren();\n        out.parentNode = null;\n    }\n\n    protected void addChildren(Node... children) {\n        //most used. short circuit addChildren(int), which hits reindex children and array copy\n        final List<Node> nodes = ensureChildNodes();\n\n        for (Node child: children) {\n            reparentChild(child);\n            nodes.add(child);\n            child.setSiblingIndex(nodes.size()-1);\n        }\n    }\n\n    protected void addChildren(int index, Node... children) {\n        // todo clean up all these and use the list, not the var array. just need to be careful when iterating the incoming (as we are removing as we go)\n        Validate.notNull(children);\n        if (children.length == 0) return;\n        final List<Node> nodes = ensureChildNodes();\n\n        // fast path - if used as a wrap (index=0, children = child[0].parent.children - do inplace\n        final Node firstParent = children[0].parent();\n        if (firstParent != null && firstParent.childNodeSize() == children.length) {\n            boolean sameList = true;\n            final List<Node> firstParentNodes = firstParent.ensureChildNodes();\n            // identity check contents to see if same\n            int i = children.length;\n            while (i-- > 0) {\n                if (children[i] != firstParentNodes.get(i)) {\n                    sameList = false;\n                    break;\n                }\n            }\n            if (sameList) { // moving, so OK to empty firstParent and short-circuit\n                firstParent.empty();\n                nodes.addAll(index, Arrays.asList(children));\n                i = children.length;\n                assert this instanceof Element;\n                while (i-- > 0) {\n                    children[i].parentNode = (Element) this;\n                }\n                ((Element) this).invalidateChildren();\n                return;\n            }\n        }\n\n        Validate.noNullElements(children);\n        for (Node child : children) {\n            reparentChild(child);\n        }\n        nodes.addAll(index, Arrays.asList(children));\n        ((Element) this).invalidateChildren();\n    }\n    \n    protected void reparentChild(Node child) {\n        child.setParentNode(this);\n    }\n\n    /**\n     Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not\n     include this node (a node is not a sibling of itself).\n     @return node siblings. If the node has no parent, returns an empty list.\n     */\n    public List<Node> siblingNodes() {\n        if (parentNode == null)\n            return Collections.emptyList();\n\n        List<Node> nodes = parentNode.ensureChildNodes();\n        List<Node> siblings = new ArrayList<>(nodes.size() - 1);\n        for (Node node: nodes)\n            if (node != this)\n                siblings.add(node);\n        return siblings;\n    }\n\n    /**\n     Get this node's next sibling.\n     @return next sibling, or {@code null} if this is the last sibling\n     */\n    public @Nullable Node nextSibling() {\n        if (parentNode == null)\n            return null; // root\n\n        final List<Node> siblings = parentNode.ensureChildNodes();\n        final int index = siblingIndex() + 1;\n        if (siblings.size() > index) {\n            Node node = siblings.get(index);\n            assert (node.siblingIndex == index); // sanity test that invalidations haven't missed\n            return node;\n        } else\n            return null;\n    }\n\n    /**\n     Get this node's previous sibling.\n     @return the previous sibling, or @{code null} if this is the first sibling\n     */\n    public @Nullable Node previousSibling() {\n        if (parentNode == null)\n            return null; // root\n\n        if (siblingIndex() > 0)\n            return parentNode.ensureChildNodes().get(siblingIndex-1);\n        else\n            return null;\n    }\n\n    /**\n     * Get the list index of this node in its node sibling list. E.g. if this is the first node\n     * sibling, returns 0.\n     * @return position in node sibling list\n     * @see org.jsoup.nodes.Element#elementSiblingIndex()\n     */\n    public int siblingIndex() {\n        if (parentNode != null && !parentNode.childNodes.validChildren)\n            parentNode.reindexChildren();\n\n        return siblingIndex;\n    }\n\n    protected void setSiblingIndex(int siblingIndex) {\n        this.siblingIndex = siblingIndex;\n    }\n\n    /**\n     Gets the first child node of this node, or {@code null} if there is none. This could be any Node type, such as an\n     Element, TextNode, Comment, etc. Use {@link Element#firstElementChild()} to get the first Element child.\n     @return the first child node, or null if there are no children.\n     @see Element#firstElementChild()\n     @see #lastChild()\n     @since 1.15.2\n     */\n    public @Nullable Node firstChild() {\n        if (childNodeSize() == 0) return null;\n        return ensureChildNodes().get(0);\n    }\n\n    /**\n     Gets the last child node of this node, or {@code null} if there is none.\n     @return the last child node, or null if there are no children.\n     @see Element#lastElementChild()\n     @see #firstChild()\n     @since 1.15.2\n     */\n    public @Nullable Node lastChild() {\n        final int size = childNodeSize();\n        if (size == 0) return null;\n        List<Node> children = ensureChildNodes();\n        return children.get(size - 1);\n    }\n\n    /**\n     Gets the first sibling of this node. That may be this node.\n\n     @return the first sibling node\n     @since 1.21.1\n     */\n    public Node firstSibling() {\n        if (parentNode != null) {\n            //noinspection DataFlowIssue\n            return parentNode.firstChild();\n        } else\n            return this; // orphan is its own first sibling\n    }\n\n    /**\n     Gets the last sibling of this node. That may be this node.\n\n     @return the last sibling (aka the parent's last child)\n     @since 1.21.1\n     */\n    public Node lastSibling() {\n        if (parentNode != null) {\n            //noinspection DataFlowIssue (not nullable, would be this if no other sibs)\n            return parentNode.lastChild();\n        } else\n            return this;\n    }\n\n    /**\n     Gets the next sibling Element of this node. E.g., if a {@code div} contains two {@code p}s, the\n     {@code nextElementSibling} of the first {@code p} is the second {@code p}.\n     <p>This is similar to {@link #nextSibling()}, but specifically finds only Elements.</p>\n\n     @return the next element, or null if there is no next element\n     @see #previousElementSibling()\n     */\n    public @Nullable Element nextElementSibling() {\n        Node next = this;\n        while ((next = next.nextSibling()) != null) {\n            if (next instanceof Element) return (Element) next;\n        }\n        return null;\n    }\n\n    /**\n     Gets the previous Element sibling of this node.\n\n     @return the previous element, or null if there is no previous element\n     @see #nextElementSibling()\n     */\n    public @Nullable Element previousElementSibling() {\n        Node prev = this;\n        while ((prev = prev.previousSibling()) != null) {\n            if (prev instanceof Element) return (Element) prev;\n        }\n        return null;\n    }\n\n    /**\n     * Perform a depth-first traversal through this node and its descendants.\n     * @param nodeVisitor the visitor callbacks to perform on each node\n     * @return this node, for chaining\n     */\n    public Node traverse(NodeVisitor nodeVisitor) {\n        Validate.notNull(nodeVisitor);\n        nodeVisitor.traverse(this);\n        return this;\n    }\n\n    /**\n     Perform the supplied action on this Node and each of its descendants, during a depth-first traversal. Nodes may be\n     inspected, changed, added, replaced, or removed.\n     @param action the function to perform on the node\n     @return this Node, for chaining\n     @see Element#forEach(Consumer)\n     */\n    public Node forEachNode(Consumer<? super Node> action) {\n        Validate.notNull(action);\n        nodeStream().forEach(action);\n        return this;\n    }\n\n    /**\n     * Perform a depth-first controllable traversal through this node and its descendants.\n     * @param nodeFilter the filter callbacks to perform on each node\n     * @return this node, for chaining\n     */\n    public Node filter(NodeFilter nodeFilter) {\n        Validate.notNull(nodeFilter);\n        nodeFilter.traverse(this);\n        return this;\n    }\n\n    /**\n     Returns a Stream of this Node and all of its descendant Nodes. The stream has document order.\n     @return a stream of all nodes.\n     @see Element#stream()\n     @since 1.17.1\n     */\n    public Stream<Node> nodeStream() {\n        return NodeUtils.stream(this, Node.class);\n    }\n\n    /**\n     Returns a Stream of this and descendant nodes, containing only nodes of the specified type. The stream has document\n     order.\n     @return a stream of nodes filtered by type.\n     @see Element#stream()\n     @since 1.17.1\n     */\n    public <T extends Node> Stream<T> nodeStream(Class<T> type) {\n        return NodeUtils.stream(this, type);\n    }\n\n    /**\n     Get the outer HTML of this node. For example, on a {@code p} element, may return {@code <p>Para</p>}.\n     @return outer HTML\n     @see Element#html()\n     @see Element#text()\n     */\n    public String outerHtml() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        outerHtml(QuietAppendable.wrap(sb));\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    protected void outerHtml(Appendable accum) {\n        outerHtml(QuietAppendable.wrap(accum));\n    }\n\n    protected void outerHtml(QuietAppendable accum) {\n        Printer printer = Printer.printerFor(this, accum);\n        printer.traverse(this);\n    }\n\n    /**\n     Get the outer HTML of this node.\n\n     @param accum accumulator to place HTML into\n     @param out\n     */\n    abstract void outerHtmlHead(final QuietAppendable accum, final Document.OutputSettings out);\n\n    abstract void outerHtmlTail(final QuietAppendable accum, final Document.OutputSettings out);\n\n    /**\n     Write this node and its children to the given {@link Appendable}.\n\n     @param appendable the {@link Appendable} to write to.\n     @return the supplied {@link Appendable}, for chaining.\n     @throws org.jsoup.SerializationException if the appendable throws an IOException.\n     */\n    public <T extends Appendable> T html(T appendable) {\n        outerHtml(appendable);\n        return appendable;\n    }\n\n    /**\n     Get the source range (start and end positions) in the original input source from which this node was parsed.\n     Position tracking must be enabled prior to parsing the content. For an Element, this will be the positions of the\n     start tag.\n     @return the range for the start of the node, or {@code untracked} if its range was not tracked.\n     @see org.jsoup.parser.Parser#setTrackPosition(boolean)\n     @see Range#isImplicit()\n     @see Element#endSourceRange()\n     @see Attributes#sourceRange(String name)\n     @since 1.15.2\n     */\n    public Range sourceRange() {\n        return Range.of(this, true);\n    }\n\n    /**\n     * Gets this node's outer HTML.\n     * @return outer HTML.\n     * @see #outerHtml()\n     */\n    @Override\n    public String toString() {\n        return outerHtml();\n    }\n\n    /** @deprecated internal method moved into Printer; will be removed in jsoup 1.24.1. */\n    @Deprecated\n    protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException {\n        accum.append('\\n').append(StringUtil.padding(depth * out.indentAmount(), out.maxPaddingWidth()));\n    }\n\n    /**\n     * Check if this node is the same instance of another (object identity test).\n     * <p>For a node value equality check, see {@link #hasSameValue(Object)}</p>\n     * @param o other object to compare to\n     * @return true if the content of this node is the same as the other\n     * @see Node#hasSameValue(Object)\n     */\n    @Override\n    public boolean equals(@Nullable Object o) {\n        // implemented just so that javadoc is clear this is an identity test\n        return this == o;\n    }\n\n    /**\n     Provides a hashCode for this Node, based on its object identity. Changes to the Node's content will not impact the\n     result.\n     @return an object identity based hashcode for this Node\n     */\n    @Override\n    public int hashCode() {\n        // implemented so that javadoc and scanners are clear this is an identity test\n        return super.hashCode();\n    }\n\n    /**\n     * Check if this node has the same content as another node. A node is considered the same if its name, attributes and content match the\n     * other node; particularly its position in the tree does not influence its similarity.\n     * @param o other object to compare to\n     * @return true if the content of this node is the same as the other\n     */\n    public boolean hasSameValue(@Nullable Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        return this.outerHtml().equals(((Node) o).outerHtml());\n    }\n\n    /**\n     Create a stand-alone, deep copy of this node, and all of its children. The cloned node will have no siblings.\n     <p><ul>\n     <li>If this node is a {@link LeafNode}, the clone will have no parent.</li>\n     <li>If this node is an {@link Element}, the clone will have a simple owning {@link Document} to retain the\n     configured output settings and parser.</li>\n     </ul></p>\n     <p>The cloned node may be adopted into another Document or node structure using\n     {@link Element#appendChild(Node)}.</p>\n\n     @return a stand-alone cloned node, including clones of any children\n     @see #shallowClone()\n     */\n    @SuppressWarnings(\"MethodDoesntCallSuperMethod\")\n    // because it does call super.clone in doClone - analysis just isn't following\n    @Override\n    public Node clone() {\n        Node thisClone = doClone(null); // splits for orphan\n\n        // Queue up nodes that need their children cloned (BFS).\n        final LinkedList<Node> nodesToProcess = new LinkedList<>();\n        nodesToProcess.add(thisClone);\n\n        while (!nodesToProcess.isEmpty()) {\n            Node currParent = nodesToProcess.remove();\n\n            final int size = currParent.childNodeSize();\n            for (int i = 0; i < size; i++) {\n                final List<Node> childNodes = currParent.ensureChildNodes();\n                Node childClone = childNodes.get(i).doClone(currParent);\n                childNodes.set(i, childClone);\n                nodesToProcess.add(childClone);\n            }\n        }\n\n        return thisClone;\n    }\n\n    /**\n     * Create a stand-alone, shallow copy of this node. None of its children (if any) will be cloned, and it will have\n     * no parent or sibling nodes.\n     * @return a single independent copy of this node\n     * @see #clone()\n     */\n    public Node shallowClone() {\n        return doClone(null);\n    }\n\n    /*\n     * Return a clone of the node using the given parent (which can be null).\n     * Not a deep copy of children.\n     */\n    protected Node doClone(@Nullable Node parent) {\n        assert parent == null || parent instanceof Element;\n        Node clone;\n\n        try {\n            clone = (Node) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new RuntimeException(e);\n        }\n\n        clone.parentNode = (Element) parent; // can be null, to create an orphan split\n        clone.siblingIndex = parent == null ? 0 : siblingIndex();\n        // if not keeping the parent, shallowClone the ownerDocument to preserve its settings\n        if (parent == null && !(this instanceof Document)) {\n            Document doc = ownerDocument();\n            if (doc != null) {\n                Document docClone = doc.shallowClone();\n                clone.parentNode = docClone;\n                docClone.ensureChildNodes().add(clone);\n            }\n        }\n\n        return clone;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/NodeIterator.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\n/**\n Iterate through a Node and its tree of descendants, in document order, and returns nodes of the specified type. This\n iterator supports structural changes to the tree during the traversal, such as {@link Node#remove()},\n {@link Node#replaceWith(Node)}, {@link Node#wrap(String)}, etc.\n <p>See also the {@link org.jsoup.select.NodeTraversor NodeTraversor} if {@code head} and {@code tail} callbacks are\n desired for each node.</p>\n @since 1.17.1\n */\npublic class NodeIterator<T extends Node> implements Iterator<T> {\n    private Node root;                      // root / starting node\n    private @Nullable T next;               // the next node to return\n    private Node current;                   // the current (last emitted) node\n    private Node previous;                  // the previously emitted node; used to recover from structural changes\n    private @Nullable Node currentParent;   // the current node's parent; used to detect structural changes\n    private final Class<T> type;            // the desired node class type\n\n    /**\n     Create a NoteIterator that will iterate the supplied node, and all of its descendants. The returned {@link #next}\n     type will be filtered to the input type.\n     * @param start initial node\n     * @param type node type to filter for\n     */\n    public NodeIterator(Node start, Class<T> type) {\n        Validate.notNull(start);\n        Validate.notNull(type);\n        this.type = type;\n\n        restart(start);\n    }\n\n    /**\n     Create a NoteIterator that will iterate the supplied node, and all of its descendants. All node types will be\n     returned.\n     * @param start initial node\n     */\n    public static NodeIterator<Node> from(Node start) {\n        return new NodeIterator<>(start, Node.class);\n    }\n\n    /**\n     Restart this Iterator from the specified start node. Will act as if it were newly constructed. Useful for e.g. to\n     save some GC if the iterator is used in a tight loop.\n     * @param start the new start node.\n     */\n    public void restart(Node start) {\n        if (type.isInstance(start))\n            //noinspection unchecked\n            next = (T) start; // first next() will be the start node\n\n        root = previous = current = start;\n        currentParent = current.parent();\n    }\n\n    @Override public boolean hasNext() {\n        maybeFindNext();\n        return next != null;\n    }\n\n    @Override public T next() {\n        maybeFindNext();\n        if (next == null) throw new NoSuchElementException();\n\n        T result = next;\n        previous = current;\n        current = next;\n        currentParent = current.parent();\n        next = null;\n        return result;\n    }\n\n    /**\n     If next is not null, looks for and sets next. If next is null after this, we have reached the end.\n     */\n    private void maybeFindNext() {\n        if (next != null) return;\n\n        //  change detected (removed or replaced), redo from previous\n        if (currentParent != null && !current.hasParent())\n            current = previous;\n\n        next = findNextNode();\n    }\n\n    private @Nullable T findNextNode() {\n        Node node = current;\n        while (true) {\n            if (node.childNodeSize() > 0)\n                node = node.childNode(0);                   // descend children\n            else if (root.equals(node))\n                node = null;                                // complete when all children of root are fully visited\n            else if (node.nextSibling() != null)\n                node = node.nextSibling();                  // in a descendant with no more children; traverse\n            else {\n                while (true) {\n                    node = node.parent();                   // pop out of descendants\n                    if (node == null || root.equals(node))\n                        return null;                        // got back to root; complete\n                    if (node.nextSibling() != null) {\n                        node = node.nextSibling();          // traverse\n                        break;\n                    }\n                }\n            }\n            if (node == null)\n                return null;                                // reached the end\n\n            if (type.isInstance(node))\n                //noinspection unchecked\n                return (T) node;\n        }\n    }\n\n    @Override public void remove() {\n        current.remove();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/NodeUtils.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.helper.W3CDom;\nimport org.jsoup.parser.HtmlTreeBuilder;\nimport org.jsoup.parser.Parser;\nimport org.w3c.dom.NodeList;\n\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Spliterator;\nimport java.util.Spliterators;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\n/**\n * Internal helpers for Nodes, to keep the actual node APIs relatively clean. A jsoup internal class, so don't use it as\n * there is no contract API.\n */\nfinal class NodeUtils {\n    /**\n     * Get the output setting for this node,  or if this node has no document (or parent), retrieve the default output\n     * settings\n     */\n    static Document.OutputSettings outputSettings(Node node) {\n        Document owner = node.ownerDocument();\n        return owner != null ? owner.outputSettings() : (new Document(\"\")).outputSettings();\n    }\n\n    /**\n     * Get the parser that was used to make this node, or the default HTML parser if it has no parent.\n     */\n    static Parser parser(Node node) {\n        Document doc = node.ownerDocument();\n        return doc != null ? doc.parser() : new Parser(new HtmlTreeBuilder());\n    }\n\n    /**\n     This impl works by compiling the input xpath expression, and then evaluating it against a W3C Document converted\n     from the original jsoup element. The original jsoup elements are then fetched from the w3c doc user data (where we\n     stashed them during conversion). This process could potentially be optimized by transpiling the compiled xpath\n     expression to a jsoup Evaluator when there's 1:1 support, thus saving the W3C document conversion stage.\n     */\n    static <T extends Node> List<T> selectXpath(String xpath, Element el, Class<T> nodeType) {\n        Validate.notEmpty(xpath);\n        Validate.notNull(el);\n        Validate.notNull(nodeType);\n\n        W3CDom w3c = new W3CDom().namespaceAware(false);\n        org.w3c.dom.Document wDoc = w3c.fromJsoup(el);\n        org.w3c.dom.Node contextNode = w3c.contextNode(wDoc);\n        NodeList nodeList = w3c.selectXpath(xpath, contextNode);\n        return w3c.sourceNodes(nodeList, nodeType);\n    }\n\n    /** Creates a Stream, starting with the supplied node. */\n    static <T extends Node> Stream<T> stream(Node start, Class<T> type) {\n        NodeIterator<T> iterator = new NodeIterator<>(start, type);\n        Spliterator<T> spliterator = spliterator(iterator);\n\n        return StreamSupport.stream(spliterator, false);\n    }\n\n    static <T extends Node> Spliterator<T> spliterator(Iterator<T> iterator) {\n        return Spliterators.spliteratorUnknownSize(\n                iterator,\n                Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Printer.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document.OutputSettings;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.NodeVisitor;\nimport org.jspecify.annotations.Nullable;\n\n/** Base Printer */\nclass Printer implements NodeVisitor {\n    final Node root;\n    final QuietAppendable accum;\n    final OutputSettings settings;\n\n    Printer(Node root, QuietAppendable accum, OutputSettings settings) {\n        this.root = root;\n        this.accum = accum;\n        this.settings = settings;\n    }\n\n    void addHead(Element el, int depth) {\n        el.outerHtmlHead(accum, settings);\n    }\n\n    void addTail(Element el, int depth) {\n        el.outerHtmlTail(accum, settings);\n    }\n\n    void addText(TextNode textNode, int textOptions, int depth) {\n        int options = Entities.ForText | textOptions;\n        Entities.escape(accum, textNode.coreValue(), settings, options);\n    }\n\n    void addNode(LeafNode node, int depth) {\n        node.outerHtmlHead(accum, settings);\n    }\n\n    void indent(int depth) {\n        accum.append('\\n').append(StringUtil.padding(depth * settings.indentAmount(), settings.maxPaddingWidth()));\n    }\n\n    @Override\n    public void head(Node node, int depth) {\n        if (node.getClass() == TextNode.class)  addText((TextNode) node, 0, depth); // Excludes CData; falls to addNode\n        else if (node instanceof Element)       addHead((Element) node, depth);\n        else                                    addNode((LeafNode) node, depth);\n    }\n\n    @Override\n    public void tail(Node node, int depth) {\n        if (node instanceof Element) { // otherwise a LeafNode\n            addTail((Element) node, depth);\n        }\n    }\n\n    /** Pretty Printer */\n    static class Pretty extends Printer {\n        boolean preserveWhitespace = false;\n\n        Pretty(Node root, QuietAppendable accum, OutputSettings settings) {\n            super(root, accum, settings);\n\n            // check if there is a pre on stack\n            for (Node node = root; node != null; node = node.parentNode()) {\n                if (tagIs(Tag.PreserveWhitespace, node)) {\n                    preserveWhitespace = true;\n                    break;\n                }\n            }\n        }\n\n        @Override\n        void addHead(Element el, int depth) {\n            if (shouldIndent(el))\n                indent(depth);\n            super.addHead(el, depth);\n            if (tagIs(Tag.PreserveWhitespace, el)) preserveWhitespace = true;\n        }\n\n        @Override\n        void addTail(Element el, int depth) {\n            if (shouldIndent(nextNonBlank(el.firstChild()))) {\n                indent(depth);\n            }\n            super.addTail(el, depth);\n\n            // clear the preserveWhitespace if this element is not, and there are none on the stack above\n            if (preserveWhitespace && el.tag.is(Tag.PreserveWhitespace)) {\n                for (Element parent = el.parent(); parent != null; parent = parent.parent()) {\n                    if (parent.tag().preserveWhitespace()) return; // keep\n                }\n                preserveWhitespace = false;\n            }\n        }\n\n        @Override\n        void addNode(LeafNode node, int depth) {\n            if (shouldIndent(node))\n                indent(depth);\n            super.addNode(node, depth);\n        }\n\n        @Override\n        void addText(TextNode node, int textOptions, int depth) {\n            if (!preserveWhitespace) {\n                textOptions |= Entities.Normalise;\n                textOptions = textTrim(node, textOptions);\n\n                if (!node.isBlank() && isBlockEl(node.parentNode) && shouldIndent(node))\n                    indent(depth);\n            }\n\n            super.addText(node, textOptions, depth);\n        }\n\n        int textTrim(TextNode node, int options) {\n            if (!isBlockEl(node.parentNode)) return options; // don't trim inline, whitespace significant\n            Node prev = node.previousSibling();\n            Node next = node.nextSibling();\n\n            // if previous is not an inline element\n            if (!(prev instanceof Element && !isBlockEl(prev))) {\n                // if there is no previous sib; or not a text node and should be indented\n                if (prev == null || !(prev instanceof TextNode) && shouldIndent(prev))\n                    options |= Entities.TrimLeading;\n            }\n\n            if (next == null || !(next instanceof TextNode) && shouldIndent(next)) {\n                options |= Entities.TrimTrailing;\n            } else { // trim trailing whitespace if the next non-empty TextNode has leading whitespace\n                next = nextNonBlank(next);\n                if (next instanceof TextNode && StringUtil.isWhitespace(next.nodeValue().codePointAt(0)))\n                    options |= Entities.TrimTrailing;\n            }\n\n            return options;\n        }\n\n        boolean shouldIndent(@Nullable Node node) {\n            if (node == null || node == root || preserveWhitespace || isBlankText(node))\n                return false;\n            if (isBlockEl(node))\n                return true;\n\n            Node prevSib = previousNonblank(node);\n            if (isBlockEl(prevSib)) return true;\n\n            Element parent = node.parentNode;\n            if (!isBlockEl(parent) || parent.tag().is(Tag.InlineContainer) || !hasNonTextNodes(parent))\n                return false;\n\n            return prevSib == null ||\n                (!(prevSib instanceof TextNode) &&\n                    (isBlockEl(prevSib) || !(prevSib instanceof Element)));\n        }\n\n        boolean isBlockEl(@Nullable Node node) {\n            if (node == null) return false;\n            if (node instanceof Element) {\n                Element el = (Element) node;\n                if (el.nameIs(\"br\")) return true; // give <br> a newline; actually an inline tag\n                return el.isBlock() ||\n                    (!el.tag.isKnownTag() && (el.parentNode instanceof Document || hasChildBlocks(el)));\n            }\n\n            return false;\n        }\n\n        /**\n         Returns true if any of the Element's child nodes should indent. Checks the last 5 nodes only (to minimize\n         scans).\n         */\n        static boolean hasChildBlocks(Element el) {\n            Element child = el.firstElementChild();\n            for (int i = 0; i < maxScan && child != null; i++) {\n                if (child.isBlock() || !child.tag.isKnownTag()) return true;\n                child = child.nextElementSibling();\n            }\n            return false;\n        }\n        static private final int maxScan = 5;\n\n        static boolean hasNonTextNodes(Element el) {\n            Node child = el.firstChild();\n            for (int i = 0; i < maxScan && child != null; i++) {\n                if (!(child instanceof TextNode)) return true;\n                child = child.nextSibling();\n            }\n            return false;\n        }\n\n        static @Nullable Node previousNonblank(Node node) {\n            Node prev = node.previousSibling();\n            while (isBlankText(prev)) prev = prev.previousSibling();\n            return prev;\n        }\n\n        static @Nullable Node nextNonBlank(@Nullable Node node) {\n            while (isBlankText(node)) node = node.nextSibling();\n            return node;\n        }\n\n        static boolean isBlankText(@Nullable Node node) {\n            return node instanceof TextNode && ((TextNode) node).isBlank();\n        }\n\n        static boolean tagIs(int option, @Nullable Node node) {\n            return node instanceof Element && ((Element) node).tag.is(option);\n        }\n    }\n\n    /** Outline Printer */\n    static class Outline extends Pretty {\n        Outline(Node root, QuietAppendable accum, OutputSettings settings) {\n            super(root, accum, settings);\n        }\n\n        @Override\n        boolean isBlockEl(@Nullable Node node) {\n            return node != null;\n        }\n\n        @Override\n        boolean shouldIndent(@Nullable Node node) {\n            if (node == null || node == root || preserveWhitespace || isBlankText(node))\n                return false;\n            if (node instanceof TextNode) {\n                return node.previousSibling() != null || node.nextSibling() != null;\n            }\n            return true;\n        }\n    }\n\n    static Printer printerFor(Node root, QuietAppendable accum) {\n        OutputSettings settings = NodeUtils.outputSettings(root);\n        if (settings.outline())     return new Printer.Outline(root, accum, settings);\n        if (settings.prettyPrint()) return new Printer.Pretty(root, accum, settings);\n        return new Printer(root, accum, settings);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/PseudoTextElement.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.parser.Tag;\n\n/**\n * Represents a {@link TextNode} as an {@link Element}, to enable text nodes to be selected with\n * the {@link org.jsoup.select.Selector} {@code :matchText} syntax.\n * @deprecated use {@link Element#selectNodes(String, Class)} instead, with selector of <code>::textnode</code> and class <code>TextNode</code>;\n * will be removed in jsoup 1.24.1.\n */\n@Deprecated\npublic class PseudoTextElement extends Element {\n\n    public PseudoTextElement(Tag tag, String baseUri, Attributes attributes) {\n        super(tag, baseUri, attributes);\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n    }\n\n    @Override\n    void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) {\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/Range.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.StringUtil;\n\nimport java.util.Objects;\n\nimport static org.jsoup.internal.SharedConstants.*;\n\n/**\n A Range object tracks the character positions in the original input source where a Node starts or ends. If you want to\n track these positions, tracking must be enabled in the Parser with\n {@link org.jsoup.parser.Parser#setTrackPosition(boolean)}.\n @see Node#sourceRange()\n @since 1.15.2\n */\npublic class Range {\n    private static final Position UntrackedPos = new Position(-1, -1, -1);\n    private final Position start, end;\n\n    /** An untracked source range. */\n    static final Range Untracked = new Range(UntrackedPos, UntrackedPos);\n\n    /**\n     Creates a new Range with start and end Positions. Called by TreeBuilder when position tracking is on.\n     * @param start the start position\n     * @param end the end position\n     */\n    public Range(Position start, Position end) {\n        this.start = start;\n        this.end = end;\n    }\n\n    /**\n     Get the start position of this node.\n     * @return the start position\n     */\n    public Position start() {\n        return start;\n    }\n\n    /**\n     Get the starting cursor position of this range.\n     @return the 0-based start cursor position.\n     @since 1.17.1\n     */\n    public int startPos() {\n        return start.pos;\n    }\n\n    /**\n     Get the end position of this node.\n     * @return the end position\n     */\n    public Position end() {\n        return end;\n    }\n\n    /**\n     Get the ending cursor position of this range.\n     @return the 0-based ending cursor position.\n     @since 1.17.1\n     */\n    public int endPos() {\n        return end.pos;\n    }\n\n    /**\n     Test if this source range was tracked during parsing.\n     * @return true if this was tracked during parsing, false otherwise (and all fields will be {@code -1}).\n     */\n    public boolean isTracked() {\n        return this != Untracked;\n    }\n\n    /**\n     Checks if the range represents a node that was implicitly created / closed.\n     <p>For example, with HTML of {@code <p>One<p>Two}, both {@code p} elements will have an explicit\n     {@link Element#sourceRange()} but an implicit {@link Element#endSourceRange()} marking the end position, as neither\n     have closing {@code </p>} tags. The TextNodes will have explicit sourceRanges.\n     <p>A range is considered implicit if its start and end positions are the same.\n     @return true if the range is tracked and its start and end positions are the same, false otherwise.\n     @since 1.17.1\n     */\n    public boolean isImplicit() {\n        if (!isTracked()) return false;\n        return start.equals(end);\n    }\n\n    /**\n     Retrieves the source range for a given Node.\n     * @param node the node to retrieve the position for\n     * @param start if this is the starting range. {@code false} for Element end tags.\n     * @return the Range, or the Untracked (-1) position if tracking is disabled.\n     */\n    static Range of(Node node, boolean start) {\n        final String key = start ? RangeKey : EndRangeKey;\n        if (!node.hasAttributes()) return Untracked;\n        Object range = node.attributes().userData(key);\n        return range != null ? (Range) range : Untracked;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        Range range = (Range) o;\n\n        if (!start.equals(range.start)) return false;\n        return end.equals(range.end);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(start, end);\n    }\n\n    /**\n     Gets a String presentation of this Range, in the format {@code line,column:pos-line,column:pos}.\n     * @return a String\n     */\n    @Override\n    public String toString() {\n        return start + \"-\" + end;\n    }\n\n    /**\n     A Position object tracks the character position in the original input source where a Node starts or ends. If you want to\n     track these positions, tracking must be enabled in the Parser with\n     {@link org.jsoup.parser.Parser#setTrackPosition(boolean)}.\n     @see Node#sourceRange()\n     */\n    public static class Position {\n        private final int pos, lineNumber, columnNumber;\n\n        /**\n         Create a new Position object. Called by the TreeBuilder if source position tracking is on.\n         * @param pos position index\n         * @param lineNumber line number\n         * @param columnNumber column number\n         */\n        public Position(int pos, int lineNumber, int columnNumber) {\n            this.pos = pos;\n            this.lineNumber = lineNumber;\n            this.columnNumber = columnNumber;\n        }\n\n        /**\n         Gets the position index (0-based) of the original input source that this Position was read at. This tracks the\n         total number of characters read into the source at this position, regardless of the number of preceding lines.\n         * @return the position, or {@code -1} if untracked.\n         */\n        public int pos() {\n            return pos;\n        }\n\n        /**\n         Gets the line number (1-based) of the original input source that this Position was read at.\n         * @return the line number, or {@code -1} if untracked.\n         */\n        public int lineNumber() {\n            return lineNumber;\n        }\n\n        /**\n         Gets the cursor number (1-based) of the original input source that this Position was read at. The cursor number\n         resets to 1 on every new line.\n         * @return the cursor number, or {@code -1} if untracked.\n         */\n        public int columnNumber() {\n            return columnNumber;\n        }\n\n        /**\n         Test if this position was tracked during parsing.\n         * @return true if this was tracked during parsing, false otherwise (and all fields will be {@code -1}).\n         */\n        public boolean isTracked() {\n            return this != UntrackedPos;\n        }\n\n        /**\n         Gets a String presentation of this Position, in the format {@code line,column:pos}.\n         * @return a String\n         */\n        @Override\n        public String toString() {\n            return lineNumber + \",\" + columnNumber + \":\" + pos;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n            Position position = (Position) o;\n            if (pos != position.pos) return false;\n            if (lineNumber != position.lineNumber) return false;\n            return columnNumber == position.columnNumber;\n        }\n\n        @Override\n        public int hashCode() {\n            return Objects.hash(pos, lineNumber, columnNumber);\n        }\n    }\n\n    public static class AttributeRange {\n        static final AttributeRange UntrackedAttr = new AttributeRange(Range.Untracked, Range.Untracked);\n\n        private final Range nameRange;\n        private final Range valueRange;\n\n        /** Creates a new AttributeRange. Called during parsing by Token.StartTag. */\n        public AttributeRange(Range nameRange, Range valueRange) {\n            this.nameRange = nameRange;\n            this.valueRange = valueRange;\n        }\n\n        /** Get the source range for the attribute's name. */\n        public Range nameRange() {\n            return nameRange;\n        }\n\n        /** Get the source range for the attribute's value. */\n        public Range valueRange() {\n            return valueRange;\n        }\n\n        /** Get a String presentation of this Attribute range, in the form\n         {@code line,column:pos-line,column:pos=line,column:pos-line,column:pos} (name start - name end = val start - val end).\n         . */\n        @Override public String toString() {\n            StringBuilder sb = StringUtil.borrowBuilder()\n                .append(nameRange)\n                .append('=')\n                .append(valueRange);\n            return StringUtil.releaseBuilder(sb);\n        }\n\n        @Override public boolean equals(Object o) {\n            if (this == o) return true;\n            if (o == null || getClass() != o.getClass()) return false;\n\n            AttributeRange that = (AttributeRange) o;\n\n            if (!nameRange.equals(that.nameRange)) return false;\n            return valueRange.equals(that.valueRange);\n        }\n\n        @Override public int hashCode() {\n            return Objects.hash(nameRange, valueRange);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/TextNode.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\n\n\n/**\n A text node.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class TextNode extends LeafNode {\n    /**\n     Create a new TextNode representing the supplied (unencoded) text).\n\n     @param text raw text\n     @see #createFromEncoded(String)\n     */\n    public TextNode(String text) {\n        super(text);\n    }\n\n\t@Override public String nodeName() {\n        return \"#text\";\n    }\n    \n    /**\n     * Get the text content of this text node.\n     * @return Unencoded, normalised text.\n     * @see TextNode#getWholeText()\n     */\n    public String text() {\n        return StringUtil.normaliseWhitespace(getWholeText());\n    }\n    \n    /**\n     * Set the text content of this text node.\n     * @param text unencoded text\n     * @return this, for chaining\n     */\n    public TextNode text(String text) {\n        coreValue(text);\n        return this;\n    }\n\n    /**\n     Get the (unencoded) text of this text node, including any newlines and spaces present in the original.\n     @return text\n     */\n    public String getWholeText() {\n        return coreValue();\n    }\n\n    /**\n     Test if this text node is blank -- that is, empty or only whitespace (including newlines).\n     @return true if this document is empty or only whitespace, false if it contains any text content.\n     */\n    public boolean isBlank() {\n        return StringUtil.isBlank(coreValue());\n    }\n\n    /**\n     * Split this text node into two nodes at the specified string offset. After splitting, this node will contain the\n     * original text up to the offset, and will have a new text node sibling containing the text after the offset.\n     * @param offset string offset point to split node at.\n     * @return the newly created text node containing the text after the offset.\n     */\n    public TextNode splitText(int offset) {\n        final String text = coreValue();\n        Validate.isTrue(offset >= 0, \"Split offset must be not be negative\");\n        Validate.isTrue(offset < text.length(), \"Split offset must not be greater than current text length\");\n\n        String head = text.substring(0, offset);\n        String tail = text.substring(offset);\n        text(head);\n        TextNode tailNode = new TextNode(tail);\n        if (parentNode != null)\n            parentNode.addChildren(siblingIndex()+1, tailNode);\n\n        return tailNode;\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        Entities.escape(accum, coreValue(), out, Entities.ForText);\n    }\n\n    @Override\n    public String toString() {\n        return outerHtml();\n    }\n\n    @Override\n    public TextNode clone() {\n        return (TextNode) super.clone();\n    }\n\n    /**\n     * Create a new TextNode from HTML encoded (aka escaped) data.\n     * @param encodedText Text containing encoded HTML (e.g. {@code &lt;})\n     * @return TextNode containing unencoded data (e.g. {@code <})\n     */\n    public static TextNode createFromEncoded(String encodedText) {\n        String text = Entities.unescape(encodedText);\n        return new TextNode(text);\n    }\n\n    static String normaliseWhitespace(String text) {\n        text = StringUtil.normaliseWhitespace(text);\n        return text;\n    }\n\n    static String stripLeadingWhitespace(String text) {\n        return text.replaceFirst(\"^\\\\s+\", \"\");\n    }\n\n    static boolean lastCharIsWhitespace(StringBuilder sb) {\n        return sb.length() != 0 && sb.charAt(sb.length() - 1) == ' ';\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/XmlDeclaration.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.jsoup.internal.StringUtil;\n\n\n/**\n * An XML Declaration. Includes support for treating the declaration contents as pseudo attributes.\n */\npublic class XmlDeclaration extends LeafNode {\n\n    /**\n     First char is `!` if isDeclaration, like in {@code  <!ENTITY ...>}.\n     Otherwise, is `?`, a processing instruction, like {@code <?xml .... ?>} (and note trailing `?`).\n     */\n    private final boolean isDeclaration;\n\n    /**\n     * Create a new XML declaration\n     * @param name of declaration\n     * @param isDeclaration {@code true} if a declaration (first char is `!`), otherwise a processing instruction (first char is `?`).\n     */\n    public XmlDeclaration(String name, boolean isDeclaration) {\n        super(name);\n        this.isDeclaration = isDeclaration;\n    }\n\n    @Override public String nodeName() {\n        return \"#declaration\";\n    }\n\n    /**\n     * Get the name of this declaration.\n     * @return name of this declaration.\n     */\n    public String name() {\n        return coreValue();\n    }\n\n    /**\n     * Get the unencoded XML declaration.\n     * @return XML declaration\n     */\n    public String getWholeDeclaration() {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        getWholeDeclaration(QuietAppendable.wrap(sb), new Document.OutputSettings());\n        return StringUtil.releaseBuilder(sb).trim();\n    }\n\n    private void getWholeDeclaration(QuietAppendable accum, Document.OutputSettings out) {\n        for (Attribute attribute : attributes()) {\n            String key = attribute.getKey();\n            String val = attribute.getValue();\n            if (!key.equals(nodeName())) { // skips coreValue (name)\n                accum.append(' ');\n                // basically like Attribute, but skip empty vals in XML\n                accum.append(key);\n                if (!val.isEmpty()) {\n                    accum.append(\"=\\\"\");\n                    Entities.escape(accum, val, out, Entities.ForAttribute);\n                    accum.append('\"');\n                }\n            }\n        }\n    }\n\n    @Override\n    void outerHtmlHead(QuietAppendable accum, Document.OutputSettings out) {\n        accum\n            .append(\"<\")\n            .append(isDeclaration ? \"!\" : \"?\")\n            .append(coreValue());\n        getWholeDeclaration(accum, out);\n        accum\n            .append(isDeclaration ? \"\" : \"?\")\n            .append(\">\");\n    }\n\n    @Override\n    void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) {\n    }\n\n    @Override\n    public String toString() {\n        return outerHtml();\n    }\n\n    @Override\n    public XmlDeclaration clone() {\n        return (XmlDeclaration) super.clone();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/nodes/package-info.java",
    "content": "/**\n HTML document structure nodes.\n */\n@NullMarked\npackage org.jsoup.nodes;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java/org/jsoup/package-info.java",
    "content": "/**\n Contains the main {@link org.jsoup.Jsoup} class, which provides convenient static access to the jsoup functionality.\n */\n@NullMarked\npackage org.jsoup;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/CharacterReader.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.SoftPool;\nimport org.jsoup.internal.StringUtil;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Locale;\n\n/**\n CharacterReader consumes tokens off a string. Used internally by jsoup. API subject to changes.\n <p>If the underlying reader throws an IOException during any operation, the CharacterReader will throw an\n {@link UncheckedIOException}. That won't happen with String / StringReader inputs.</p>\n */\npublic final class CharacterReader implements AutoCloseable {\n    static final char EOF = (char) -1;\n    private static final int MaxStringCacheLen = 12;\n    private static final int StringCacheSize = 512;\n    private String[] stringCache; // holds reused strings in this doc, to lessen garbage\n    private static final SoftPool<String[]> StringPool = new SoftPool<>(() -> new String[StringCacheSize]); // reuse cache between iterations\n\n    static final int BufferSize = 1024 * 2;         // visible for testing\n    static final int RefillPoint = BufferSize / 2;  // when bufPos characters read, refill; visible for testing\n    private static final int RewindLimit = 1024;    // the maximum we can rewind. No HTML entities can be larger than this.\n\n    private Reader reader;      // underlying Reader, will be backed by a buffered+controlled input stream, or StringReader\n    private char[] charBuf;     // character buffer we consume from; filled from Reader\n    private int bufPos;         // position in charBuf that's been consumed to\n    private int bufLength;      // the num of characters actually buffered in charBuf, <= charBuf.length\n    private int fillPoint = 0;  // how far into the charBuf we read before re-filling. 0.5 of charBuf.length after bufferUp\n    private int consumed;       // how many characters total have been consumed from this CharacterReader (less the current bufPos)\n    private int bufMark = -1;   // if not -1, the marked rewind position\n    private boolean readFully;  // if the underlying stream has been completely read, no value in further buffering\n\n    private static final SoftPool<char[]> BufferPool = new SoftPool<>(() -> new char[BufferSize]); // recycled char buffer\n\n    @Nullable private ArrayList<Integer> newlinePositions = null; // optionally track the pos() position of newlines - scans during bufferUp()\n    private int lineNumberOffset = 1; // line numbers start at 1; += newlinePosition[indexof(pos)]\n\n    public CharacterReader(Reader input, int sz) {\n        this(input); // sz is no longer used\n    }\n\n    public CharacterReader(Reader input) {\n        Validate.notNull(input);\n        reader = input;\n        charBuf = BufferPool.borrow();\n        stringCache = StringPool.borrow();\n        bufferUp();\n    }\n\n    public CharacterReader(String input) {\n        this(new StringReader(input));\n    }\n\n    @Override\n    public void close() {\n        if (reader == null)\n            return;\n        try {\n            reader.close();\n        } catch (IOException ignored) {\n        } finally {\n            reader = null;\n            Arrays.fill(charBuf, (char) 0); // before release, clear the buffer. Not required, but acts as a safety net, and makes debug view clearer\n            BufferPool.release(charBuf);\n            charBuf = null;\n            StringPool.release(stringCache); // conversely, we don't clear the string cache, so we can reuse the contents\n            stringCache = null;\n        }\n    }\n\n    private void bufferUp() {\n        if (readFully || bufPos < fillPoint || bufMark != -1)\n            return;\n        doBufferUp(); // structured so bufferUp may become an intrinsic candidate\n    }\n\n    /**\n     Reads into the buffer. Will throw an UncheckedIOException if the underling reader throws an IOException.\n     @throws UncheckedIOException if the underlying reader throws an IOException\n     */\n    private void doBufferUp() {\n        /*\n        The flow:\n        - if read fully, or if bufPos < fillPoint, or if marked - do not fill.\n        - update readerPos (total amount consumed from this CharacterReader) += bufPos\n        - shift charBuf contents such that bufPos = 0; set next read offset (bufLength) -= shift amount\n        - loop read the Reader until we fill charBuf. bufLength += read.\n        - readFully = true when read = -1\n         */\n        consumed += bufPos;\n        bufLength -= bufPos;\n        if (bufLength > 0)\n            System.arraycopy(charBuf, bufPos, charBuf, 0, bufLength);\n        bufPos = 0;\n        while (bufLength < BufferSize) {\n            try {\n                int read = reader.read(charBuf, bufLength, charBuf.length - bufLength);\n                if (read == -1) {\n                    readFully = true;\n                    break;\n                }\n                if (read == 0) {\n                    break; // if we have a surrogate on the buffer boundary and trying to read 1; will have enough in our buffer to proceed\n                }\n                bufLength += read;\n            } catch (IOException e) {\n                throw new UncheckedIOException(e);\n            }\n        }\n        fillPoint = Math.min(bufLength, RefillPoint);\n\n        scanBufferForNewlines(); // if enabled, we index newline positions for line number tracking\n        lastIcSeq = null; // cache for last containsIgnoreCase(seq)\n    }\n\n    void mark() {\n        // make sure there is enough look ahead capacity\n        if (bufLength - bufPos < RewindLimit)\n            fillPoint = 0;\n\n        bufferUp();\n        bufMark = bufPos;\n    }\n\n    void unmark() {\n        bufMark = -1;\n    }\n\n    void rewindToMark() {\n        if (bufMark == -1)\n            throw new UncheckedIOException(new IOException(\"Mark invalid\"));\n\n        bufPos = bufMark;\n        unmark();\n    }\n\n    /**\n     * Gets the position currently read to in the content. Starts at 0.\n     * @return current position\n     */\n    public int pos() {\n        return consumed + bufPos;\n    }\n\n    /** Tests if the buffer has been fully read. */\n    boolean readFully() {\n        return readFully;\n    }\n\n    /**\n     Enables or disables line number tracking. By default, will be <b>off</b>.Tracking line numbers improves the\n     legibility of parser error messages, for example. Tracking should be enabled before any content is read to be of\n     use.\n\n     @param track set tracking on|off\n     @since 1.14.3\n     */\n    public void trackNewlines(boolean track) {\n        if (track && newlinePositions == null) {\n            newlinePositions = new ArrayList<>(BufferSize / 80); // rough guess of likely count\n            scanBufferForNewlines(); // first pass when enabled; subsequently called during bufferUp\n        }\n        else if (!track)\n            newlinePositions = null;\n    }\n\n    /**\n     Check if the tracking of newlines is enabled.\n     @return the current newline tracking state\n     @since 1.14.3\n     */\n    public boolean isTrackNewlines() {\n        return newlinePositions != null;\n    }\n\n    /**\n     Get the current line number (that the reader has consumed to). Starts at line #1.\n     @return the current line number, or 1 if line tracking is not enabled.\n     @since 1.14.3\n     @see #trackNewlines(boolean)\n     */\n    public int lineNumber() {\n        return lineNumber(pos());\n    }\n\n    int lineNumber(int pos) {\n        // note that this impl needs to be called before the next buffer up or line numberoffset will be wrong. if that\n        // causes issues, can remove the reset of newlinepositions during buffer, at the cost of a larger tracking array\n        if (!isTrackNewlines())\n            return 1;\n\n        int i = lineNumIndex(pos);\n        if (i == -1)\n            return lineNumberOffset; // first line\n        return i + lineNumberOffset + 1;\n    }\n\n    /**\n     Get the current column number (that the reader has consumed to). Starts at column #1.\n     @return the current column number\n     @since 1.14.3\n     @see #trackNewlines(boolean)\n     */\n    public int columnNumber() {\n        return columnNumber(pos());\n    }\n\n    int columnNumber(int pos) {\n        if (!isTrackNewlines())\n            return pos + 1;\n\n        int i = lineNumIndex(pos);\n        if (i == -1)\n          return pos + 1;\n        return pos - newlinePositions.get(i) + 1;\n    }\n\n    /**\n     Get a formatted string representing the current line and column positions. E.g. <code>5:10</code> indicating line\n     number 5 and column number 10.\n     @return line:col position\n     @since 1.14.3\n     @see #trackNewlines(boolean)\n     */\n    String posLineCol() {\n        return lineNumber() + \":\" + columnNumber();\n    }\n\n    private int lineNumIndex(int pos) {\n        if (!isTrackNewlines()) return 0;\n        int i = Collections.binarySearch(newlinePositions, pos);\n        if (i < -1) i = Math.abs(i) - 2;\n        return i;\n    }\n\n    /**\n     Scans the buffer for newline position, and tracks their location in newlinePositions.\n     */\n    private void scanBufferForNewlines() {\n        if (!isTrackNewlines())\n            return;\n\n        if (newlinePositions.size() > 0) {\n            // work out the line number that we have read up to (as we have likely scanned past this point)\n            int index = lineNumIndex(consumed);\n            if (index == -1) index = 0; // first line\n            int linePos = newlinePositions.get(index);\n            lineNumberOffset += index; // the num lines we've read up to\n            newlinePositions.clear();\n            newlinePositions.add(linePos); // roll the last read pos to first, for cursor num after buffer\n        }\n\n        for (int i = bufPos; i < bufLength; i++) {\n            if (charBuf[i] == '\\n')\n                newlinePositions.add(1 + consumed + i);\n        }\n    }\n\n    /**\n     * Tests if all the content has been read.\n     * @return true if nothing left to read.\n     */\n    public boolean isEmpty() {\n        bufferUp();\n        return bufPos >= bufLength;\n    }\n\n    private boolean isEmptyNoBufferUp() {\n        return bufPos >= bufLength;\n    }\n\n    /**\n     * Get the char at the current position.\n     * @return char\n     */\n    public char current() {\n        bufferUp();\n        return isEmptyNoBufferUp() ? EOF : charBuf[bufPos];\n    }\n\n    /**\n     Consume one character off the queue.\n     @return first character on queue, or EOF if the queue is empty.\n     */\n    public char consume() {\n        bufferUp();\n        char val = isEmptyNoBufferUp() ? EOF : charBuf[bufPos];\n        bufPos++;\n        return val;\n    }\n\n    /**\n     Unconsume one character (bufPos--). MUST only be called directly after a consume(), and no chance of a bufferUp.\n     */\n    void unconsume() {\n        if (bufPos < 1)\n            throw new UncheckedIOException(new IOException(\"WTF: No buffer left to unconsume.\")); // a bug if this fires, need to trace it.\n\n        bufPos--;\n    }\n\n    /**\n     * Moves the current position by one.\n     */\n    public void advance() {\n        bufPos++;\n    }\n\n    /**\n     * Returns the number of characters between the current position and the next instance of the input char\n     * @param c scan target\n     * @return offset between current position and next instance of target. -1 if not found.\n     */\n    int nextIndexOf(char c) {\n        // doesn't handle scanning for surrogates\n        bufferUp();\n        for (int i = bufPos; i < bufLength; i++) {\n            if (c == charBuf[i])\n                return i - bufPos;\n        }\n        return -1;\n    }\n\n    /**\n     * Returns the number of characters between the current position and the next instance of the input sequence\n     *\n     * @param seq scan target\n     * @return offset between current position and next instance of target. -1 if not found.\n     */\n    int nextIndexOf(CharSequence seq) {\n        bufferUp();\n        // doesn't handle scanning for surrogates\n        char startChar = seq.charAt(0);\n        for (int offset = bufPos; offset < bufLength; offset++) {\n            // scan to first instance of startchar:\n            if (startChar != charBuf[offset])\n                while(++offset < bufLength && startChar != charBuf[offset]) { /* empty */ }\n            int i = offset + 1;\n            int last = i + seq.length()-1;\n            if (offset < bufLength && last <= bufLength) {\n                for (int j = 1; i < last && seq.charAt(j) == charBuf[i]; i++, j++) { /* empty */ }\n                if (i == last) // found full sequence\n                    return offset - bufPos;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Reads characters up to the specific char.\n     * @param c the delimiter\n     * @return the chars read\n     */\n    public String consumeTo(char c) {\n        int offset = nextIndexOf(c);\n        if (offset != -1) {\n            String consumed = cacheString(charBuf, stringCache, bufPos, offset);\n            bufPos += offset;\n            return consumed;\n        } else {\n            return consumeToEnd();\n        }\n    }\n\n    /**\n     Reads the characters up to (but not including) the specified case-sensitive string.\n     <p>If the sequence is not found in the buffer, will return the remainder of the current buffered amount, less the\n     length of the sequence, such that this call may be repeated.\n     @param seq the delimiter\n     @return the chars read\n     */\n    public String consumeTo(String seq) {\n        int offset = nextIndexOf(seq);\n        if (offset != -1) {\n            String consumed = cacheString(charBuf, stringCache, bufPos, offset);\n            bufPos += offset;\n            return consumed;\n        } else if (bufLength - bufPos < seq.length()) {\n            // nextIndexOf() did a bufferUp(), so if the buffer is shorter than the search string, we must be at EOF\n            return consumeToEnd();\n        } else {\n            // the string we're looking for may be straddling a buffer boundary, so keep (length - 1) characters\n            // unread in case they contain the beginning of the search string\n            int endPos = bufLength - seq.length() + 1;\n            String consumed = cacheString(charBuf, stringCache, bufPos, endPos - bufPos);\n            bufPos = endPos;\n            return consumed;\n        }\n    }\n\n    /**\n     Read characters while the input predicate returns true.\n     @return characters read\n     */\n    String consumeMatching(CharPredicate func) {\n        return consumeMatching(func, -1);\n    }\n\n    /**\n     Read characters while the input predicate returns true, up to a maximum length.\n     @param func predicate to test\n     @param maxLength maximum length to read. -1 indicates no maximum\n     @return characters read\n     */\n    String consumeMatching(CharPredicate func, int maxLength) {\n        bufferUp();\n        int pos = bufPos;\n        final int start = pos;\n        final int remaining = bufLength;\n        final char[] val = charBuf;\n\n        while (pos < remaining && (maxLength == -1 || pos - start < maxLength) && func.test(val[pos])) {\n            pos++;\n        }\n\n        bufPos = pos;\n        return pos > start ? cacheString(charBuf, stringCache, start, pos -start) : \"\";\n    }\n\n    /**\n     * Read characters until the first of any delimiters is found.\n     * @param chars delimiters to scan for\n     * @return characters read up to the matched delimiter.\n     */\n    public String consumeToAny(final char... chars) {\n        return consumeMatching(c -> { // seeks until we see one of the terminating chars\n            for (char seek : chars)\n                if (c == seek) return false;\n            return true;\n        });\n    }\n\n    String consumeToAnySorted(final char... chars) {\n        return consumeMatching(c -> Arrays.binarySearch(chars, c) < 0); // matches until a hit\n    }\n\n    String consumeData() {\n        // consumes until &, <, null\n        return consumeMatching(c -> c != '&' && c != '<' && c != TokeniserState.nullChar);\n    }\n\n    String consumeAttributeQuoted(final boolean single) {\n        // null, \" or ', &\n        return consumeMatching(c -> c != TokeniserState.nullChar && c != '&' && (single ? c != '\\'' : c != '\"'));\n    }\n\n    String consumeRawData() {\n        // <, null\n        return consumeMatching(c -> c != '<' && c != TokeniserState.nullChar);\n    }\n\n    String consumeTagName() {\n        // '\\t', '\\n', '\\r', '\\f', ' ', '/', '>'\n        // NOTE: out of spec; does not stop and append on nullChar but eats\n        return consumeMatching(c -> {\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                case '/':\n                case '>':\n                    return false;\n            }\n            return true;\n        });\n    }\n\n    String consumeToEnd() {\n        bufferUp();\n        String data = cacheString(charBuf, stringCache, bufPos, bufLength - bufPos);\n        bufPos = bufLength;\n        return data;\n    }\n\n    String consumeLetterSequence() {\n        return consumeMatching(Character::isLetter);\n    }\n\n    String consumeLetterThenDigitSequence() {\n        bufferUp();\n        int start = bufPos;\n        while (bufPos < bufLength) {\n            if (StringUtil.isAsciiLetter(charBuf[bufPos])) bufPos++;\n            else break;\n        }\n        while (!isEmptyNoBufferUp()) {\n            if (StringUtil.isDigit(charBuf[bufPos])) bufPos++;\n            else break;\n        }\n\n        return cacheString(charBuf, stringCache, start, bufPos - start);\n    }\n\n    String consumeHexSequence() {\n        return consumeMatching(StringUtil::isHexDigit);\n    }\n\n    String consumeDigitSequence() {\n        return consumeMatching(c -> c >= '0' && c <= '9');\n    }\n\n    boolean matches(char c) {\n        return !isEmpty() && charBuf[bufPos] == c;\n    }\n\n    boolean matches(String seq) {\n        bufferUp();\n        int scanLength = seq.length();\n        if (scanLength > bufLength - bufPos)\n            return false;\n\n        for (int offset = 0; offset < scanLength; offset++)\n            if (seq.charAt(offset) != charBuf[bufPos +offset])\n                return false;\n        return true;\n    }\n\n    boolean matchesIgnoreCase(String seq) {\n        bufferUp();\n        int scanLength = seq.length();\n        if (scanLength > bufLength - bufPos)\n            return false;\n\n        for (int offset = 0; offset < scanLength; offset++) {\n            char scan = seq.charAt(offset);\n            char target = charBuf[bufPos + offset];\n            if (scan == target) continue;\n\n            scan = Character.toUpperCase(scan);\n            target = Character.toUpperCase(target);\n            if (scan != target) return false;\n        }\n        return true;\n    }\n\n    /**\n     Tests if the next character in the queue matches any of the characters in the sequence, case sensitively.\n     @param seq list of characters to check for\n     @return true if any matched, false if none did\n     */\n    boolean matchesAny(char... seq) {\n        if (isEmpty())\n            return false;\n\n        bufferUp();\n        char c = charBuf[bufPos];\n        for (char seek : seq) {\n            if (seek == c)\n                return true;\n        }\n        return false;\n    }\n\n    boolean matchesAnySorted(char[] seq) {\n        bufferUp();\n        return !isEmpty() && Arrays.binarySearch(seq, charBuf[bufPos]) >= 0;\n    }\n\n    /**\n     Checks if the current pos matches an ascii alpha (A-Z a-z) per https://infra.spec.whatwg.org/#ascii-alpha\n     @return if it matches or not\n     */\n    boolean matchesAsciiAlpha() {\n        if (isEmpty()) return false;\n        return StringUtil.isAsciiLetter(charBuf[bufPos]);\n    }\n\n    boolean matchesDigit() {\n        if (isEmpty()) return false;\n        return StringUtil.isDigit(charBuf[bufPos]);\n    }\n\n    boolean matchConsume(String seq) {\n        bufferUp();\n        if (matches(seq)) {\n            bufPos += seq.length();\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    boolean matchConsumeIgnoreCase(String seq) {\n        if (matchesIgnoreCase(seq)) {\n            bufPos += seq.length();\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    // we maintain a cache of the previously scanned sequence, and return that if applicable on repeated scans.\n    // that improves the situation where there is a sequence of <p<p<p<p<p<p<p...</title> and we're bashing on the <p\n    // looking for the </title>. Resets in bufferUp()\n    @Nullable private String lastIcSeq; // scan cache\n    private int lastIcIndex; // nearest found indexOf\n\n    /** Used to check presence of </title>, </style> when we're in RCData and see a <xxx. Only finds consistent case. */\n    boolean containsIgnoreCase(String seq) {\n        if (seq.equals(lastIcSeq)) {\n            if (lastIcIndex == -1) return false;\n            if (lastIcIndex >= bufPos) return true;\n        }\n        lastIcSeq = seq;\n\n        String loScan = seq.toLowerCase(Locale.ENGLISH);\n        int lo = nextIndexOf(loScan);\n        if (lo > -1) {\n            lastIcIndex = bufPos + lo; return true;\n        }\n\n        String hiScan = seq.toUpperCase(Locale.ENGLISH);\n        int hi = nextIndexOf(hiScan);\n        boolean found = hi > -1;\n        lastIcIndex = found ? bufPos + hi : -1; // we don't care about finding the nearest, just that buf contains\n        return found;\n    }\n\n    @Override\n    public String toString() {\n        if (bufLength - bufPos < 0) return \"\";\n        return new String(charBuf, bufPos, bufLength - bufPos);\n    }\n\n    /**\n     * Caches short strings, as a flyweight pattern, to reduce GC load. Just for this doc, to prevent leaks.\n     * <p />\n     * Simplistic, and on hash collisions just falls back to creating a new string, vs a full HashMap with Entry list.\n     * That saves both having to create objects as hash keys, and running through the entry list, at the expense of\n     * some more duplicates.\n     */\n    private static String cacheString(final char[] charBuf, final String[] stringCache, final int start, final int count) {\n        if (count > MaxStringCacheLen) // don't cache strings that are too big\n            return new String(charBuf, start, count);\n        if (count < 1)\n            return \"\";\n\n        // calculate hash:\n        int hash = 0;\n        int end = count + start;\n        for (int i = start; i < end; i++) {\n            hash = 31 * hash + charBuf[i];\n        }\n\n        // get from cache\n        final int index = hash & StringCacheSize - 1;\n        String cached = stringCache[index];\n\n        if (cached != null && rangeEquals(charBuf, start, count, cached)) // positive hit\n            return cached;\n        else {\n            cached = new String(charBuf, start, count);\n            stringCache[index] = cached; // add or replace, assuming most recently used are most likely to recur next\n        }\n\n        return cached;\n    }\n\n    /**\n     * Check if the value of the provided range equals the string.\n     */\n    static boolean rangeEquals(final char[] charBuf, final int start, int count, final String cached) {\n        if (count == cached.length()) {\n            int i = start;\n            int j = 0;\n            while (count-- != 0) {\n                if (charBuf[i++] != cached.charAt(j++))\n                    return false;\n            }\n            return true;\n        }\n        return false;\n    }\n\n    // just used for testing\n    boolean rangeEquals(final int start, final int count, final String cached) {\n        return rangeEquals(charBuf, start, count, cached);\n    }\n\n    @FunctionalInterface\n    interface CharPredicate {\n        boolean test(char c);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/HtmlTreeBuilder.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.CDataNode;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.FormElement;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.Reader;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jsoup.internal.StringUtil.inSorted;\nimport static org.jsoup.parser.HtmlTreeBuilderState.Constants.InTableFoster;\nimport static org.jsoup.parser.HtmlTreeBuilderState.ForeignContent;\nimport static org.jsoup.parser.Parser.*;\n\n/**\n * HTML Tree Builder; creates a DOM from Tokens.\n */\npublic class HtmlTreeBuilder extends TreeBuilder {\n    // tag searches. must be sorted, used in inSorted. HtmlTreeBuilderTest validates they're sorted.\n    static final String[] TagsSearchInScope = new String[]{ // a particular element in scope\n        \"applet\", \"caption\", \"html\", \"marquee\", \"object\", \"table\", \"td\", \"template\", \"th\"\n    };\n    // math and svg namespaces for particular element in scope\n    static final String[]TagSearchInScopeMath = new String[] {\n        \"annotation-xml\",  \"mi\", \"mn\", \"mo\", \"ms\", \"mtext\"\n    };\n    static final String[]TagSearchInScopeSvg = new String[] {\n        \"desc\", \"foreignobject\", \"title\" // note normalized to lowercase to match other scope searches; will preserve input case as appropriate\n    };\n\n    static final String[] TagSearchList = new String[]{\"ol\", \"ul\"};\n    static final String[] TagSearchButton = new String[]{\"button\"};\n    static final String[] TagSearchTableScope = new String[]{\"html\", \"table\"};\n    static final String[] TagSearchSelectScope = new String[]{\"optgroup\", \"option\"};\n    static final String[] TagSearchEndTags = new String[]{\"dd\", \"dt\", \"li\", \"optgroup\", \"option\", \"p\", \"rb\", \"rp\", \"rt\", \"rtc\"};\n    static final String[] TagThoroughSearchEndTags = new String[]{\"caption\", \"colgroup\", \"dd\", \"dt\", \"li\", \"optgroup\", \"option\", \"p\", \"rb\", \"rp\", \"rt\", \"rtc\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n    static final String[] TagSearchSpecial = new String[]{\n        \"address\", \"applet\", \"area\", \"article\", \"aside\", \"base\", \"basefont\", \"bgsound\", \"blockquote\", \"body\", \"br\",\n        \"button\", \"caption\", \"center\", \"col\", \"colgroup\", \"dd\", \"details\", \"dir\", \"div\", \"dl\", \"dt\", \"embed\",\n        \"fieldset\", \"figcaption\", \"figure\", \"footer\", \"form\", \"frame\", \"frameset\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n        \"head\", \"header\", \"hgroup\", \"hr\", \"html\", \"iframe\", \"img\", \"input\", \"keygen\", \"li\", \"link\", \"listing\", \"main\",\n        \"marquee\", \"menu\", \"meta\", \"nav\", \"noembed\", \"noframes\", \"noscript\", \"object\", \"ol\", \"p\", \"param\", \"plaintext\",\n        \"pre\", \"script\", \"search\", \"section\", \"select\", \"source\", \"style\", \"summary\", \"table\", \"tbody\", \"td\",\n        \"template\", \"textarea\", \"tfoot\", \"th\", \"thead\", \"title\", \"tr\", \"track\", \"ul\", \"wbr\", \"xmp\"};\n    static String[] TagSearchSpecialMath = {\"annotation-xml\", \"mi\", \"mn\", \"mo\", \"ms\", \"mtext\"}; // differs to MathML text integration point; adds annotation-xml\n    static final String[] TagMathMlTextIntegration = new String[]{\"mi\", \"mn\", \"mo\", \"ms\", \"mtext\"};\n    static final String[] TagSvgHtmlIntegration = new String[]{\"desc\", \"foreignObject\", \"title\"};\n    static final String[] TagFormListed = {\n        \"button\", \"fieldset\", \"input\", \"keygen\", \"object\", \"output\", \"select\", \"textarea\"\n    };\n\n    /** @deprecated Not used anymore; configure parser depth via {@link Parser#setMaxDepth(int)}. Will be removed in jsoup 1.24.1. */\n    @Deprecated\n    public static final int MaxScopeSearchDepth = 100;\n\n    private HtmlTreeBuilderState state; // the current state\n    private HtmlTreeBuilderState originalState; // original / marked state\n\n    private boolean baseUriSetFromDoc;\n    private @Nullable Element headElement; // the current head element\n    private @Nullable FormElement formElement; // the current form element\n    private @Nullable Element contextElement; // fragment parse root; name only copy of context. could be null even if fragment parsing\n    ArrayList<Element> formattingElements; // active (open) formatting elements\n    private ArrayList<HtmlTreeBuilderState> tmplInsertMode; // stack of Template Insertion modes\n    private List<Token.Character> pendingTableCharacters; // chars in table to be shifted out\n    private Token.EndTag emptyEnd; // reused empty end tag\n\n    private boolean framesetOk; // if ok to go into frameset\n    private boolean fosterInserts; // if next inserts should be fostered\n    private boolean fragmentParsing; // if parsing a fragment of html\n\n    @Override ParseSettings defaultSettings() {\n        return ParseSettings.htmlDefault;\n    }\n\n    @Override\n    HtmlTreeBuilder newInstance() {\n        return new HtmlTreeBuilder();\n    }\n\n    @Override\n    protected void initialiseParse(Reader input, String baseUri, Parser parser) {\n        super.initialiseParse(input, baseUri, parser);\n\n        // this is a bit mucky. todo - probably just create new parser objects to ensure all reset.\n        state = HtmlTreeBuilderState.Initial;\n        originalState = null;\n        baseUriSetFromDoc = false;\n        headElement = null;\n        formElement = null;\n        contextElement = null;\n        formattingElements = new ArrayList<>();\n        tmplInsertMode = new ArrayList<>();\n        pendingTableCharacters = new ArrayList<>();\n        emptyEnd = new Token.EndTag(this);\n        framesetOk = true;\n        fosterInserts = false;\n        fragmentParsing = false;\n    }\n\n    @Override void initialiseParseFragment(@Nullable Element context) {\n        // context may be null\n        state = HtmlTreeBuilderState.Initial;\n        fragmentParsing = true;\n\n        if (context != null) {\n            final String contextName = context.normalName();\n            contextElement = new Element(tagFor(contextName, contextName, defaultNamespace(), settings), baseUri);\n            if (context.ownerDocument() != null) // quirks setup:\n                doc.quirksMode(context.ownerDocument().quirksMode());\n\n            // initialise the tokeniser state:\n            switch (contextName) {\n                case \"script\":\n                    tokeniser.transition(TokeniserState.ScriptData);\n                    break;\n                case \"plaintext\":\n                    tokeniser.transition(TokeniserState.PLAINTEXT);\n                    break;\n                case \"template\":\n                    tokeniser.transition(TokeniserState.Data);\n                    pushTemplateMode(HtmlTreeBuilderState.InTemplate);\n                    break;\n                default:\n                    Tag tag = contextElement.tag();\n                    TokeniserState textState = tag.textState();\n                    if (textState != null)\n                        tokeniser.transition(textState); // style, xmp, title, textarea, etc; or custom\n                    else\n                        tokeniser.transition(TokeniserState.Data);\n            }\n            doc.appendChild(contextElement);\n            push(contextElement);\n            resetInsertionMode();\n\n            // setup form element to nearest form on context (up ancestor chain). ensures form controls are associated\n            // with form correctly\n            Element formSearch = context;\n            while (formSearch != null) {\n                if (formSearch instanceof FormElement) {\n                    formElement = (FormElement) formSearch;\n                    break;\n                }\n                formSearch = formSearch.parent();\n            }\n        }\n    }\n\n    @Override List<Node> completeParseFragment() {\n        if (contextElement != null) {\n            // depending on context and the input html, content may have been added outside of the root el\n            // e.g. context=p, input=div, the div will have been pushed out.\n            List<Node> nodes = contextElement.siblingNodes();\n            if (!nodes.isEmpty())\n                contextElement.insertChildren(-1, nodes);\n            return contextElement.childNodes();\n        }\n        else\n            return doc.childNodes();\n    }\n\n    @Override\n    protected boolean process(Token token) {\n        HtmlTreeBuilderState dispatch = useCurrentOrForeignInsert(token) ? this.state : ForeignContent;\n        return dispatch.process(token, this);\n    }\n\n    boolean useCurrentOrForeignInsert(Token token) {\n        // https://html.spec.whatwg.org/multipage/parsing.html#tree-construction\n        // If the stack of open elements is empty\n        if (stack.isEmpty())\n            return true;\n        final Element el = currentElement();\n        final String ns = el.tag().namespace();\n\n        // If the adjusted current node is an element in the HTML namespace\n        if (NamespaceHtml.equals(ns))\n            return true;\n\n        // If the adjusted current node is a MathML text integration point and the token is a start tag whose tag name is neither \"mglyph\" nor \"malignmark\"\n        // If the adjusted current node is a MathML text integration point and the token is a character token\n        if (isMathmlTextIntegration(el)) {\n            if (token.isStartTag()\n                    && !\"mglyph\".equals(token.asStartTag().normalName)\n                    && !\"malignmark\".equals(token.asStartTag().normalName))\n                    return true;\n            if (token.isCharacter())\n                    return true;\n        }\n        // If the adjusted current node is a MathML annotation-xml element and the token is a start tag whose tag name is \"svg\"\n        if (Parser.NamespaceMathml.equals(ns)\n            && el.nameIs(\"annotation-xml\")\n            && token.isStartTag()\n            && \"svg\".equals(token.asStartTag().normalName))\n            return true;\n\n        // If the adjusted current node is an HTML integration point and the token is a start tag\n        // If the adjusted current node is an HTML integration point and the token is a character token\n        if (isHtmlIntegration(el)\n            && (token.isStartTag() || token.isCharacter()))\n            return true;\n\n        // If the token is an end-of-file token\n        return token.isEOF();\n    }\n\n    static boolean isMathmlTextIntegration(Element el) {\n        /*\n        A node is a MathML text integration point if it is one of the following elements:\n        A MathML mi element\n        A MathML mo element\n        A MathML mn element\n        A MathML ms element\n        A MathML mtext element\n         */\n        return (Parser.NamespaceMathml.equals(el.tag().namespace())\n            && StringUtil.inSorted(el.normalName(), TagMathMlTextIntegration));\n    }\n\n    static boolean isHtmlIntegration(Element el) {\n        /*\n        A node is an HTML integration point if it is one of the following elements:\n        A MathML annotation-xml element whose start tag token had an attribute with the name \"encoding\" whose value was an ASCII case-insensitive match for the string \"text/html\"\n        A MathML annotation-xml element whose start tag token had an attribute with the name \"encoding\" whose value was an ASCII case-insensitive match for the string \"application/xhtml+xml\"\n        An SVG foreignObject element\n        An SVG desc element\n        An SVG title element\n         */\n        if (Parser.NamespaceMathml.equals(el.tag().namespace())\n            && el.nameIs(\"annotation-xml\")) {\n            String encoding = Normalizer.normalize(el.attr(\"encoding\"));\n            if (encoding.equals(\"text/html\") || encoding.equals(\"application/xhtml+xml\"))\n                return true;\n        }\n        // note using .tagName for case-sensitive hit here of foreignObject\n        return Parser.NamespaceSvg.equals(el.tag().namespace()) && StringUtil.in(el.tagName(), TagSvgHtmlIntegration);\n    }\n\n    boolean process(Token token, HtmlTreeBuilderState state) {\n        return state.process(token, this);\n    }\n\n    void transition(HtmlTreeBuilderState state) {\n        this.state = state;\n    }\n\n    HtmlTreeBuilderState state() {\n        return state;\n    }\n\n    void markInsertionMode() {\n        originalState = state;\n    }\n\n    HtmlTreeBuilderState originalState() {\n        return originalState;\n    }\n\n    void framesetOk(boolean framesetOk) {\n        this.framesetOk = framesetOk;\n    }\n\n    boolean framesetOk() {\n        return framesetOk;\n    }\n\n    Document getDocument() {\n        return doc;\n    }\n\n    String getBaseUri() {\n        return baseUri;\n    }\n\n    void maybeSetBaseUri(Element base) {\n        if (baseUriSetFromDoc) // only listen to the first <base href> in parse\n            return;\n\n        String href = base.absUrl(\"href\");\n        if (href.length() != 0) { // ignore <base target> etc\n            baseUri = href;\n            baseUriSetFromDoc = true;\n            doc.setBaseUri(href); // set on the doc so doc.createElement(Tag) will get updated base, and to update all descendants\n        }\n    }\n\n    boolean isFragmentParsing() {\n        return fragmentParsing;\n    }\n\n    void error(HtmlTreeBuilderState state) {\n        if (parser.getErrors().canAddError())\n            parser.getErrors().add(new ParseError(reader, \"Unexpected %s token [%s] when in state [%s]\",\n                currentToken.tokenType(), currentToken, state));\n    }\n\n    Element createElementFor(Token.StartTag startTag, String namespace, boolean forcePreserveCase) {\n        // dedupe and normalize the attributes:\n        Attributes attributes = startTag.attributes;\n        if (attributes != null && !attributes.isEmpty()) {\n            if (!forcePreserveCase)\n                settings.normalizeAttributes(attributes);\n            int dupes = attributes.deduplicate(settings);\n            if (dupes > 0) {\n                error(\"Dropped duplicate attribute(s) in tag [%s]\", startTag.normalName);\n            }\n        }\n\n        Tag tag = tagFor(startTag.name(), startTag.normalName, namespace,\n            forcePreserveCase ? ParseSettings.preserveCase : settings);\n\n        return (tag.normalName().equals(\"form\")) ?\n            new FormElement(tag, null, attributes) :\n            new Element(tag, null, attributes);\n    }\n\n    /** Inserts an HTML element for the given tag */\n    Element insertElementFor(final Token.StartTag startTag) {\n        Element el = createElementFor(startTag, NamespaceHtml, false);\n        doInsertElement(el);\n\n        // handle self-closing tags. when the spec expects an empty (void) tag, will directly hit insertEmpty, so won't generate this fake end tag.\n        if (startTag.isSelfClosing()) {\n            Tag tag = el.tag();\n            tag.setSeenSelfClose(); // can infer output if in xml syntax\n            if (tag.isEmpty()) {\n                // treated as empty below; nothing further\n            } else if (tag.isKnownTag() && tag.isSelfClosing()) {\n                // ok, allow it. effectively a pop, but fiddles with the state. handles empty style, title etc which would otherwise leave us in data state\n                tokeniser.transition(TokeniserState.Data); // handles <script />, otherwise needs breakout steps from script data\n                tokeniser.emit(emptyEnd.reset().name(el.tagName()));  // ensure we get out of whatever state we are in. emitted for yielded processing\n            } else {\n                // error it, and leave the inserted element on\n                tokeniser.error(\"Tag [%s] cannot be self-closing; not a void tag\", tag.normalName());\n            }\n        }\n\n        if (el.tag().isEmpty()) {\n            pop(); // custom void tags behave like built-in voids (no children, not left on the stack); known empty go via insertEmpty\n        }\n\n        return el;\n    }\n\n    /**\n     Inserts a foreign element. Preserves the case of the tag name and of the attributes.\n     */\n    Element insertForeignElementFor(final Token.StartTag startTag, String namespace) {\n        Element el = createElementFor(startTag, namespace, true);\n        doInsertElement(el);\n\n        if (startTag.isSelfClosing()) { // foreign els are OK to self-close\n            el.tag().setSeenSelfClose(); // remember this is self-closing for output\n            pop();\n        }\n\n        return el;\n    }\n\n    Element insertEmptyElementFor(Token.StartTag startTag) {\n        Element el = createElementFor(startTag, NamespaceHtml, false);\n        doInsertElement(el);\n        pop();\n        return el;\n    }\n\n    FormElement insertFormElement(Token.StartTag startTag, boolean onStack, boolean checkTemplateStack) {\n        FormElement el = (FormElement) createElementFor(startTag, NamespaceHtml, false);\n\n        if (checkTemplateStack) {\n            if(!onStack(\"template\"))\n                setFormElement(el);\n        } else\n            setFormElement(el);\n\n        doInsertElement(el);\n        if (!onStack) pop();\n        return el;\n    }\n\n    /** Inserts the Element onto the stack. All element inserts must run through this method. Performs any general\n     tests on the Element before insertion.\n     * @param el the Element to insert and make the current element\n     */\n    private void doInsertElement(Element el) {\n        enforceStackDepthLimit();\n\n        if (formElement != null && el.tag().namespace.equals(NamespaceHtml) && StringUtil.inSorted(el.normalName(), TagFormListed))\n            formElement.addElement(el); // connect form controls to their form element\n\n        // in HTML, the xmlns attribute if set must match what the parser set the tag's namespace to\n        if (parser.getErrors().canAddError() && el.hasAttr(\"xmlns\") && !el.attr(\"xmlns\").equals(el.tag().namespace()))\n            error(\"Invalid xmlns attribute [%s] on tag [%s]\", el.attr(\"xmlns\"), el.tagName());\n\n        if (isFosterInserts() && StringUtil.inSorted(currentElement().normalName(), InTableFoster))\n            insertInFosterParent(el);\n        else\n            currentElement().appendChild(el);\n\n        push(el);\n    }\n\n    void insertCommentNode(Token.Comment token) {\n        Comment node = new Comment(token.getData());\n        currentElement().appendChild(node);\n        onNodeInserted(node);\n    }\n\n    /** Inserts the provided character token into the current element. Any nulls in the data will be removed. */\n    void insertCharacterNode(Token.Character characterToken) {\n        insertCharacterNode(characterToken, false);\n    }\n\n    /**\n     Inserts the provided character token into the current element. The tokenizer will have already raised precise character errors.\n\n     @param characterToken the character token to insert\n     @param replace if true, replaces any null chars in the data with the replacement char (U+FFFD). If false, removes\n     null chars.\n     */\n    void insertCharacterNode(Token.Character characterToken, boolean replace) {\n        characterToken.normalizeNulls(replace);\n        Element el = currentElement(); // will be doc if no current element; allows for whitespace to be inserted into the doc root object (not on the stack)\n        insertCharacterToElement(characterToken, el);\n    }\n\n    /** Inserts the provided character token into the provided element. */\n    void insertCharacterToElement(Token.Character characterToken, Element el) {\n        final Node node;\n        final String data = characterToken.getData();\n\n        if (characterToken.isCData())\n            node = new CDataNode(data);\n        else if (el.tag().is(Tag.Data))\n            node = new DataNode(data);\n        else\n            node = new TextNode(data);\n        el.appendChild(node); // doesn't use insertNode, because we don't foster these; and will always have a stack.\n        onNodeInserted(node);\n    }\n\n    ArrayList<Element> getStack() {\n        return stack;\n    }\n\n    boolean onStack(Element el) {\n        return onStack(stack, el);\n    }\n\n    /** Checks if there is an HTML element with the given name on the stack. */\n    boolean onStack(String elName) {\n        return getFromStack(elName) != null;\n    }\n\n    private static final int maxQueueDepth = 256; // an arbitrary tension point between real HTML and crafted pain\n    private static boolean onStack(ArrayList<Element> queue, Element element) {\n        final int bottom = queue.size() - 1;\n        final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0;\n        for (int pos = bottom; pos >= upper; pos--) {\n            Element next = queue.get(pos);\n            if (next == element) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /** Gets the nearest (lowest) HTML element with the given name from the stack. */\n    @Nullable\n    Element getFromStack(String elName) {\n        final int bottom = stack.size() - 1;\n        final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0;\n        for (int pos = bottom; pos >= upper; pos--) {\n            Element next = stack.get(pos);\n            if (next.elementIs(elName, NamespaceHtml)) {\n                return next;\n            }\n        }\n        return null;\n    }\n\n    boolean removeFromStack(Element el) {\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element next = stack.get(pos);\n            if (next == el) {\n                stack.remove(pos);\n                onNodeClosed(el);\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    void onStackPrunedForDepth(Element element) {\n        // handle other effects of popping to keep state correct\n        if (element == headElement) headElement = null;\n        if (element == formElement) setFormElement(null);\n        removeFromActiveFormattingElements(element);\n        if (element.nameIs(\"template\")) {\n            clearFormattingElementsToLastMarker();\n            if (templateModeSize() > 0)\n                popTemplateMode();\n            resetInsertionMode();\n        }\n    }\n\n    /** Pops the stack until the given HTML element is removed. */\n    @Nullable\n    Element popStackToClose(String elName) {\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element el = pop();\n            if (el.elementIs(elName, NamespaceHtml)) {\n                return el;\n            }\n        }\n        return null;\n    }\n\n    /** Pops the stack until an element with the supplied name is removed, irrespective of namespace. */\n    @Nullable\n    Element popStackToCloseAnyNamespace(String elName) {\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element el = pop();\n            if (el.nameIs(elName)) {\n                return el;\n            }\n        }\n        return null;\n    }\n\n    /** Pops the stack until one of the given HTML elements is removed. */\n    void popStackToClose(String... elNames) { // elnames is sorted, comes from Constants\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element el = pop();\n            if (inSorted(el.normalName(), elNames) && NamespaceHtml.equals(el.tag().namespace())) {\n                break;\n            }\n        }\n    }\n\n    void clearStackToTableContext() {\n        clearStackToContext(\"table\", \"template\");\n    }\n\n    void clearStackToTableBodyContext() {\n        clearStackToContext(\"tbody\", \"tfoot\", \"thead\", \"template\");\n    }\n\n    void clearStackToTableRowContext() {\n        clearStackToContext(\"tr\", \"template\");\n    }\n\n    /** Removes elements from the stack until one of the supplied HTML elements is removed. */\n    private void clearStackToContext(String... nodeNames) {\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element next = stack.get(pos);\n            if (NamespaceHtml.equals(next.tag().namespace()) &&\n                (StringUtil.in(next.normalName(), nodeNames) || next.nameIs(\"html\")))\n                break;\n            else\n                pop();\n        }\n    }\n\n    /**\n     Gets the Element immediately above the supplied element on the stack. Which due to adoption, may not necessarily be\n     its parent.\n\n     @param el\n     @return the Element immediately above the supplied element, or null if there is no such element.\n     */\n    @Nullable Element aboveOnStack(Element el) {\n        if (!onStack(el)) return null;\n        for (int pos = stack.size() -1; pos > 0; pos--) {\n            Element next = stack.get(pos);\n            if (next == el) {\n                return stack.get(pos-1);\n            }\n        }\n        return null;\n    }\n\n    void insertOnStackAfter(Element after, Element in) {\n        int i = stack.lastIndexOf(after);\n        if (i == -1) {\n            error(\"Did not find element on stack to insert after\");\n            stack.add(in);\n            // may happen on particularly malformed inputs during adoption\n        } else {\n            stack.add(i+1, in);\n        }\n    }\n\n    void replaceOnStack(Element out, Element in) {\n        replaceInQueue(stack, out, in);\n    }\n\n    private static void replaceInQueue(ArrayList<Element> queue, Element out, Element in) {\n        int i = queue.lastIndexOf(out);\n        Validate.isTrue(i != -1);\n        queue.set(i, in);\n    }\n\n    /**\n     * Reset the insertion mode, by searching up the stack for an appropriate insertion mode. The stack search depth\n     * is limited to {@link #maxQueueDepth}.\n     * @return true if the insertion mode was actually changed.\n     */\n    boolean resetInsertionMode() {\n        // https://html.spec.whatwg.org/multipage/parsing.html#the-insertion-mode\n        boolean last = false;\n        final int bottom = stack.size() - 1;\n        final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0;\n        final HtmlTreeBuilderState origState = this.state;\n\n        if (stack.size() == 0) { // nothing left of stack, just get to body\n            transition(HtmlTreeBuilderState.InBody);\n        }\n\n        LOOP: for (int pos = bottom; pos >= upper; pos--) {\n            Element node = stack.get(pos);\n            if (pos == upper) {\n                last = true;\n                if (fragmentParsing)\n                    node = contextElement;\n            }\n            String name = node != null ? node.normalName() : \"\";\n            if (!NamespaceHtml.equals(node.tag().namespace()))\n                continue; // only looking for HTML elements here\n\n            switch (name) {\n                case \"select\":\n                    transition(HtmlTreeBuilderState.InSelect);\n                    // todo - should loop up (with some limit) and check for table or template hits\n                    break LOOP;\n                case \"td\":\n                case \"th\":\n                    if (!last) {\n                        transition(HtmlTreeBuilderState.InCell);\n                        break LOOP;\n                    }\n                    break;\n                case \"tr\":\n                    transition(HtmlTreeBuilderState.InRow);\n                    break LOOP;\n                case \"tbody\":\n                case \"thead\":\n                case \"tfoot\":\n                    transition(HtmlTreeBuilderState.InTableBody);\n                    break LOOP;\n                case \"caption\":\n                    transition(HtmlTreeBuilderState.InCaption);\n                    break LOOP;\n                case \"colgroup\":\n                    transition(HtmlTreeBuilderState.InColumnGroup);\n                    break LOOP;\n                case \"table\":\n                    transition(HtmlTreeBuilderState.InTable);\n                    break LOOP;\n                case \"template\":\n                    HtmlTreeBuilderState tmplState = currentTemplateMode();\n                    Validate.notNull(tmplState, \"Bug: no template insertion mode on stack!\");\n                    transition(tmplState);\n                    break LOOP;\n                case \"head\":\n                    if (!last) {\n                        transition(HtmlTreeBuilderState.InHead);\n                        break LOOP;\n                    }\n                    break;\n                case \"body\":\n                    transition(HtmlTreeBuilderState.InBody);\n                    break LOOP;\n                case \"frameset\":\n                    transition(HtmlTreeBuilderState.InFrameset);\n                    break LOOP;\n                case \"html\":\n                    transition(headElement == null ? HtmlTreeBuilderState.BeforeHead : HtmlTreeBuilderState.AfterHead);\n                    break LOOP;\n            }\n            if (last) {\n                transition(HtmlTreeBuilderState.InBody);\n                break;\n            }\n        }\n        return state != origState;\n    }\n\n    /** Places the body back onto the stack and moves to InBody, for cases in AfterBody / AfterAfterBody when more content comes */\n    void resetBody() {\n        if (!onStack(\"body\")) {\n            stack.add(doc.body()); // not onNodeInserted, as already seen\n        }\n        transition(HtmlTreeBuilderState.InBody);\n    }\n\n    // todo: tidy up in specific scope methods\n    private final String[] specificScopeTarget = {null};\n\n    private boolean inSpecificScope(String targetName, String[] baseTypes, String[] extraTypes) {\n        specificScopeTarget[0] = targetName;\n        return inSpecificScope(specificScopeTarget, baseTypes, extraTypes);\n    }\n\n    private boolean inSpecificScope(String[] targetNames, String[] baseTypes, @Nullable String[] extraTypes) {\n        // https://html.spec.whatwg.org/multipage/parsing.html#has-an-element-in-the-specific-scope\n        final int bottom = stack.size() -1;\n        // don't walk too far up the tree\n        for (int pos = bottom; pos >= 0; pos--) {\n            Element el = stack.get(pos);\n            String elName = el.normalName();\n            // namespace checks - arguments provided are always in html ns, with this bolt-on for math and svg:\n            String ns = el.tag().namespace();\n            if (ns.equals(NamespaceHtml)) {\n                if (inSorted(elName, targetNames))\n                    return true;\n                if (inSorted(elName, baseTypes))\n                    return false;\n                if (extraTypes != null && inSorted(elName, extraTypes))\n                    return false;\n            } else if (baseTypes == TagsSearchInScope) {\n                if (ns.equals(NamespaceMathml) && inSorted(elName, TagSearchInScopeMath))\n                    return false;\n                if (ns.equals(NamespaceSvg) && inSorted(elName, TagSearchInScopeSvg))\n                    return false;\n            }\n        }\n        //Validate.fail(\"Should not be reachable\"); // would end up false because hitting 'html' at root (basetypes)\n        return false;\n    }\n\n    boolean inScope(String[] targetNames) {\n        return inSpecificScope(targetNames, TagsSearchInScope, null);\n    }\n\n    boolean inScope(String targetName) {\n        return inScope(targetName, null);\n    }\n\n    boolean inScope(String targetName, String[] extras) {\n        return inSpecificScope(targetName, TagsSearchInScope, extras);\n    }\n\n    boolean inListItemScope(String targetName) {\n        return inScope(targetName, TagSearchList);\n    }\n\n    boolean inButtonScope(String targetName) {\n        return inScope(targetName, TagSearchButton);\n    }\n\n    boolean inTableScope(String targetName) {\n        return inSpecificScope(targetName, TagSearchTableScope, null);\n    }\n\n    boolean inSelectScope(String targetName) {\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element el = stack.get(pos);\n            String elName = el.normalName();\n            if (elName.equals(targetName))\n                return true;\n            if (!inSorted(elName, TagSearchSelectScope)) // all elements except\n                return false;\n        }\n        return false; // nothing left on stack\n    }\n\n    /** Tests if there is some element on the stack that is not in the provided set. */\n    boolean onStackNot(String[] allowedTags) {\n        for (int pos = stack.size() - 1; pos >= 0; pos--) {\n            final String elName = stack.get(pos).normalName();\n            if (!inSorted(elName, allowedTags))\n                return true;\n        }\n        return false;\n    }\n\n    void setHeadElement(Element headElement) {\n        this.headElement = headElement;\n    }\n\n    Element getHeadElement() {\n        return headElement;\n    }\n\n    boolean isFosterInserts() {\n        return fosterInserts;\n    }\n\n    void setFosterInserts(boolean fosterInserts) {\n        this.fosterInserts = fosterInserts;\n    }\n\n    @Nullable FormElement getFormElement() {\n        return formElement;\n    }\n\n    void setFormElement(FormElement formElement) {\n        this.formElement = formElement;\n    }\n\n    void resetPendingTableCharacters() {\n        pendingTableCharacters.clear();\n    }\n\n    List<Token.Character> getPendingTableCharacters() {\n        return pendingTableCharacters;\n    }\n\n    void addPendingTableCharacters(Token.Character c) {\n        // make a copy of the token to maintain its state (as Tokens are otherwise reset)\n        Token.Character copy = new Token.Character(c);\n        pendingTableCharacters.add(copy);\n    }\n\n    /**\n     13.2.6.3 Closing elements that have implied end tags\n     When the steps below require the UA to generate implied end tags, then, while the current node is a dd element, a dt element, an li element, an optgroup element, an option element, a p element, an rb element, an rp element, an rt element, or an rtc element, the UA must pop the current node off the stack of open elements.\n\n     If a step requires the UA to generate implied end tags but lists an element to exclude from the process, then the UA must perform the above steps as if that element was not in the above list.\n\n     When the steps below require the UA to generate all implied end tags thoroughly, then, while the current node is a caption element, a colgroup element, a dd element, a dt element, an li element, an optgroup element, an option element, a p element, an rb element, an rp element, an rt element, an rtc element, a tbody element, a td element, a tfoot element, a th element, a thead element, or a tr element, the UA must pop the current node off the stack of open elements.\n\n     @param excludeTag If a step requires the UA to generate implied end tags but lists an element to exclude from the\n     process, then the UA must perform the above steps as if that element was not in the above list.\n     */\n    void generateImpliedEndTags(String excludeTag) {\n        while (inSorted(currentElement().normalName(), TagSearchEndTags)) {\n            if (excludeTag != null && currentElementIs(excludeTag))\n                break;\n            pop();\n        }\n    }\n\n    void generateImpliedEndTags() {\n        generateImpliedEndTags(false);\n    }\n\n    /**\n     Pops HTML elements off the stack according to the implied end tag rules\n     @param thorough if we are thorough (includes table elements etc) or not\n     */\n    void generateImpliedEndTags(boolean thorough) {\n        final String[] search = thorough ? TagThoroughSearchEndTags : TagSearchEndTags;\n        while (NamespaceHtml.equals(currentElement().tag().namespace())\n            && inSorted(currentElement().normalName(), search)) {\n            pop();\n        }\n    }\n\n    void closeElement(String name) {\n        generateImpliedEndTags(name);\n        if (!name.equals(currentElement().normalName())) error(state());\n        popStackToClose(name);\n    }\n\n    static boolean isSpecial(Element el) {\n        String namespace = el.tag().namespace();\n        String name = el.normalName();\n        switch (namespace) {\n            case NamespaceHtml:\n                return inSorted(name, TagSearchSpecial);\n            case Parser.NamespaceMathml:\n                return inSorted(name, TagSearchSpecialMath);\n            case Parser.NamespaceSvg:\n                return inSorted(name, TagSvgHtmlIntegration);\n            default:\n                return false;\n        }\n    }\n\n    Element lastFormattingElement() {\n        return formattingElements.size() > 0 ? formattingElements.get(formattingElements.size()-1) : null;\n    }\n\n    int positionOfElement(Element el){\n        for (int i = 0; i < formattingElements.size(); i++){\n            if (el == formattingElements.get(i))\n                return i;\n        }\n        return -1;\n    }\n\n    Element removeLastFormattingElement() {\n        int size = formattingElements.size();\n        if (size > 0)\n            return formattingElements.remove(size-1);\n        else\n            return null;\n    }\n\n    // active formatting elements\n    void pushActiveFormattingElements(Element in) {\n        checkActiveFormattingElements(in);\n        formattingElements.add(in);\n    }\n\n    void pushWithBookmark(Element in, int bookmark){\n        checkActiveFormattingElements(in);\n        // catch any range errors and assume bookmark is incorrect - saves a redundant range check.\n        try {\n            formattingElements.add(bookmark, in);\n        } catch (IndexOutOfBoundsException e) {\n            formattingElements.add(in);\n        }\n    }\n\n    void checkActiveFormattingElements(Element in){\n        int numSeen = 0;\n        final int size = formattingElements.size() -1;\n        int ceil = size - maxUsedFormattingElements; if (ceil <0) ceil = 0;\n\n        for (int pos = size; pos >= ceil; pos--) {\n            Element el = formattingElements.get(pos);\n            if (el == null) // marker\n                break;\n\n            if (isSameFormattingElement(in, el))\n                numSeen++;\n\n            if (numSeen == 3) {\n                formattingElements.remove(pos);\n                break;\n            }\n        }\n    }\n\n    private static boolean isSameFormattingElement(Element a, Element b) {\n        // same if: same namespace, tag, and attributes. Element.equals only checks tag, might in future check children\n        return a.normalName().equals(b.normalName()) &&\n                // a.namespace().equals(b.namespace()) &&\n                a.attributes().equals(b.attributes());\n        // todo: namespaces\n    }\n\n    void reconstructFormattingElements() {\n        if (stack.size() > maxQueueDepth)\n            return;\n        Element last = lastFormattingElement();\n        if (last == null || onStack(last))\n            return;\n\n        Element entry = last;\n        int size = formattingElements.size();\n        int ceil = size - maxUsedFormattingElements; if (ceil <0) ceil = 0;\n        int pos = size - 1;\n        boolean skip = false;\n        while (true) {\n            if (pos == ceil) { // step 4. if none before, skip to 8\n                skip = true;\n                break;\n            }\n            entry = formattingElements.get(--pos); // step 5. one earlier than entry\n            if (entry == null || onStack(entry)) // step 6 - neither marker nor on stack\n                break; // jump to 8, else continue back to 4\n        }\n        while(true) {\n            if (!skip) // step 7: on later than entry\n                entry = formattingElements.get(++pos);\n            Validate.notNull(entry); // should not occur, as we break at last element\n\n            // 8. create new element from element, 9 insert into current node, onto stack\n            skip = false; // can only skip increment from 4.\n            Element newEl = new Element(tagFor(entry.nodeName(), entry.normalName(), defaultNamespace(), settings), null, entry.attributes().clone());\n            doInsertElement(newEl);\n\n            // 10. replace entry with new entry\n            formattingElements.set(pos, newEl);\n\n            // 11\n            if (pos == size-1) // if not last entry in list, jump to 7\n                break;\n        }\n    }\n    private static final int maxUsedFormattingElements = 12; // limit how many elements get recreated\n\n    void clearFormattingElementsToLastMarker() {\n        while (!formattingElements.isEmpty()) {\n            Element el = removeLastFormattingElement();\n            if (el == null)\n                break;\n        }\n    }\n\n    void removeFromActiveFormattingElements(Element el) {\n        for (int pos = formattingElements.size() -1; pos >= 0; pos--) {\n            Element next = formattingElements.get(pos);\n            if (next == el) {\n                formattingElements.remove(pos);\n                break;\n            }\n        }\n    }\n\n    boolean isInActiveFormattingElements(Element el) {\n        return onStack(formattingElements, el);\n    }\n\n    @Nullable\n    Element getActiveFormattingElement(String nodeName) {\n        for (int pos = formattingElements.size() -1; pos >= 0; pos--) {\n            Element next = formattingElements.get(pos);\n            if (next == null) // scope marker\n                break;\n            else if (next.nameIs(nodeName))\n                return next;\n        }\n        return null;\n    }\n\n    void replaceActiveFormattingElement(Element out, Element in) {\n        replaceInQueue(formattingElements, out, in);\n    }\n\n    void insertMarkerToFormattingElements() {\n        formattingElements.add(null);\n    }\n\n    void insertInFosterParent(Node in) {\n        Element fosterParent;\n        Element lastTable = getFromStack(\"table\");\n        boolean isLastTableParent = false;\n        if (lastTable != null) {\n            if (lastTable.parent() != null) {\n                fosterParent = lastTable.parent();\n                isLastTableParent = true;\n            } else\n                fosterParent = aboveOnStack(lastTable);\n        } else { // no table == frag\n            fosterParent = stack.get(0);\n        }\n\n        if (isLastTableParent) {\n            Validate.notNull(lastTable); // last table cannot be null by this point.\n            lastTable.before(in);\n        }\n        else\n            fosterParent.appendChild(in);\n    }\n\n    // Template Insertion Mode stack\n    void pushTemplateMode(HtmlTreeBuilderState state) {\n        tmplInsertMode.add(state);\n    }\n\n    @Nullable HtmlTreeBuilderState popTemplateMode() {\n        if (tmplInsertMode.size() > 0) {\n            return tmplInsertMode.remove(tmplInsertMode.size() -1);\n        } else {\n            return null;\n        }\n    }\n\n    int templateModeSize() {\n        return tmplInsertMode.size();\n    }\n\n    @Nullable HtmlTreeBuilderState currentTemplateMode() {\n        return (tmplInsertMode.size() > 0) ? tmplInsertMode.get(tmplInsertMode.size() -1)  : null;\n    }\n\n    @Override\n    public String toString() {\n        return \"TreeBuilder{\" +\n                \"currentToken=\" + currentToken +\n                \", state=\" + state +\n                \", currentElement=\" + currentElement() +\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/HtmlTreeBuilderState.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.DocumentType;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.Range;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\n\nimport static org.jsoup.internal.StringUtil.inSorted;\nimport static org.jsoup.parser.HtmlTreeBuilder.isSpecial;\nimport static org.jsoup.parser.HtmlTreeBuilderState.Constants.*;\n\n/**\n * The Tree Builder's current state. Each state embodies the processing for the state, and transitions to other states.\n */\nenum HtmlTreeBuilderState {\n    Initial {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                return true; // ignore whitespace until we get the first content\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype()) {\n                // todo: parse error check on expected doctypes\n                Token.Doctype d = t.asDoctype();\n                DocumentType doctype = new DocumentType(\n                    tb.settings.normalizeTag(d.getName()), d.getPublicIdentifier(), d.getSystemIdentifier());\n                doctype.setPubSysKey(d.getPubSysKey());\n                tb.getDocument().appendChild(doctype);\n                tb.onNodeInserted(doctype);\n                // todo: quirk state check on more doctype ids, if deemed useful (most are ancient legacy and presumably irrelevant)\n                if (d.isForceQuirks() || !doctype.name().equals(\"html\") || doctype.publicId().equalsIgnoreCase(\"HTML\"))\n                    tb.getDocument().quirksMode(Document.QuirksMode.quirks);\n                tb.transition(BeforeHtml);\n            } else {\n                // todo: check not iframe srcdoc\n                tb.getDocument().quirksMode(Document.QuirksMode.quirks); // missing doctype\n                tb.transition(BeforeHtml);\n                return tb.process(t); // re-process token\n            }\n            return true;\n        }\n    },\n    BeforeHtml {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter()); // out of spec - include whitespace\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"html\")) {\n                tb.insertElementFor(t.asStartTag());\n                tb.transition(BeforeHead);\n            } else if (t.isEndTag() && (inSorted(t.asEndTag().normalName(), BeforeHtmlToHead))) {\n                return anythingElse(t, tb);\n            } else if (t.isEndTag()) {\n                tb.error(this);\n                return false;\n            } else {\n                return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            tb.processStartTag(\"html\");\n            tb.transition(BeforeHead);\n            return tb.process(t);\n        }\n    },\n    BeforeHead {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter()); // out of spec - include whitespace\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"html\")) {\n                return InBody.process(t, tb); // does not transition\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"head\")) {\n                Element head = tb.insertElementFor(t.asStartTag());\n                tb.setHeadElement(head);\n                tb.transition(InHead);\n            } else if (t.isEndTag() && (inSorted(t.asEndTag().normalName(), BeforeHtmlToHead))) {\n                tb.processStartTag(\"head\");\n                return tb.process(t);\n            } else if (t.isEndTag()) {\n                tb.error(this);\n                return false;\n            } else {\n                tb.processStartTag(\"head\");\n                return tb.process(t);\n            }\n            return true;\n        }\n    },\n    InHead {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter()); // out of spec - include whitespace\n                return true;\n            }\n            final String name;\n            switch (t.type) {\n                case Comment:\n                    tb.insertCommentNode(t.asComment());\n                    break;\n                case Doctype:\n                    tb.error(this);\n                    return false;\n                case StartTag:\n                    Token.StartTag start = t.asStartTag();\n                    name = start.normalName();\n                    if (name.equals(\"html\")) {\n                        return InBody.process(t, tb);\n                    } else if (inSorted(name, InHeadEmpty)) {\n                        Element el = tb.insertEmptyElementFor(start);\n                        // jsoup special: update base the first time it is seen\n                        if (name.equals(\"base\") && el.hasAttr(\"href\"))\n                            tb.maybeSetBaseUri(el);\n                    } else if (name.equals(\"meta\")) {\n                        tb.insertEmptyElementFor(start);\n                    } else if (name.equals(\"title\")) {\n                        HandleTextState(start, tb, tb.tagFor(start).textState());\n                    } else if (inSorted(name, InHeadRaw)) {\n                        HandleTextState(start, tb, tb.tagFor(start).textState());\n                    } else if (name.equals(\"noscript\")) {\n                        // else if noscript && scripting flag = true: rawtext (jsoup doesn't run script, to handle as noscript)\n                        tb.insertElementFor(start);\n                        tb.transition(InHeadNoscript);\n                    } else if (name.equals(\"script\")) {\n                        // skips some script rules as won't execute them\n                        tb.tokeniser.transition(TokeniserState.ScriptData);\n                        tb.markInsertionMode();\n                        tb.transition(Text);\n                        tb.insertElementFor(start);\n                    } else if (name.equals(\"head\")) {\n                        tb.error(this);\n                        return false;\n                    } else if (name.equals(\"template\")) {\n                        tb.insertElementFor(start);\n                        tb.insertMarkerToFormattingElements();\n                        tb.framesetOk(false);\n                        tb.transition(InTemplate);\n                        tb.pushTemplateMode(InTemplate);\n                    } else {\n                        return anythingElse(t, tb);\n                    }\n                    break;\n                case EndTag:\n                    Token.EndTag end = t.asEndTag();\n                    name = end.normalName();\n                    if (name.equals(\"head\")) {\n                        tb.pop();\n                        tb.transition(AfterHead);\n                    } else if (inSorted(name, Constants.InHeadEnd)) {\n                        return anythingElse(t, tb);\n                    } else if (name.equals(\"template\")) {\n                        if (!tb.onStack(name)) {\n                            tb.error(this);\n                        } else {\n                            tb.generateImpliedEndTags(true);\n                            if (!tb.currentElementIs(name)) tb.error(this);\n                            tb.popStackToClose(name);\n                            tb.clearFormattingElementsToLastMarker();\n                            tb.popTemplateMode();\n                            tb.resetInsertionMode();\n                        }\n                    }\n                    else {\n                        tb.error(this);\n                        return false;\n                    }\n                    break;\n                default:\n                    return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, TreeBuilder tb) {\n            tb.processEndTag(\"head\");\n            return tb.process(t);\n        }\n    },\n    InHeadNoscript {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isDoctype()) {\n                tb.error(this);\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"html\")) {\n                return tb.process(t, InBody);\n            } else if (t.isEndTag() && t.asEndTag().normalName().equals(\"noscript\")) {\n                tb.pop();\n                tb.transition(InHead);\n            } else if (isWhitespace(t) || t.isComment() || (t.isStartTag() && inSorted(t.asStartTag().normalName(),\n                    InHeadNoScriptHead))) {\n                return tb.process(t, InHead);\n            } else if (t.isEndTag() && t.asEndTag().normalName().equals(\"br\")) {\n                return anythingElse(t, tb);\n            } else if ((t.isStartTag() && inSorted(t.asStartTag().normalName(), InHeadNoscriptIgnore)) || t.isEndTag()) {\n                tb.error(this);\n                return false;\n            } else {\n                return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            // note that this deviates from spec, which is to pop out of noscript and reprocess in head:\n            // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inheadnoscript\n            // allows content to be inserted as data\n            tb.error(this);\n            tb.insertCharacterNode(new Token.Character().data(t.toString()));\n            return true;\n        }\n    },\n    AfterHead {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter());\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype()) {\n                tb.error(this);\n            } else if (t.isStartTag()) {\n                Token.StartTag startTag = t.asStartTag();\n                String name = startTag.normalName();\n                if (name.equals(\"html\")) {\n                    return tb.process(t, InBody);\n                } else if (name.equals(\"body\")) {\n                    tb.insertElementFor(startTag);\n                    tb.framesetOk(false);\n                    tb.transition(InBody);\n                } else if (name.equals(\"frameset\")) {\n                    tb.insertElementFor(startTag);\n                    tb.transition(InFrameset);\n                } else if (inSorted(name, InBodyStartToHead)) {\n                    tb.error(this);\n                    Element head = tb.getHeadElement();\n                    tb.push(head);\n                    tb.process(t, InHead);\n                    tb.removeFromStack(head);\n                } else if (name.equals(\"head\")) {\n                    tb.error(this);\n                    return false;\n                } else {\n                    anythingElse(t, tb);\n                }\n            } else if (t.isEndTag()) {\n                String name = t.asEndTag().normalName();\n                if (inSorted(name, AfterHeadBody)) {\n                    anythingElse(t, tb);\n                } else if (name.equals(\"template\")) {\n                    tb.process(t, InHead);\n                }\n                else {\n                    tb.error(this);\n                    return false;\n                }\n            } else {\n                anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            tb.processStartTag(\"body\");\n            tb.framesetOk(true);\n            return tb.process(t);\n        }\n    },\n    InBody {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            switch (t.type) {\n                case Character: {\n                    Token.Character c = t.asCharacter();\n                    if (tb.framesetOk() && isWhitespace(c)) { // don't check if whitespace if frames already closed\n                        tb.reconstructFormattingElements();\n                        tb.insertCharacterNode(c);\n                    } else {\n                        tb.reconstructFormattingElements();\n                        tb.insertCharacterNode(c); // strips nulls\n                        tb.framesetOk(false);\n                    }\n                    break;\n                }\n                case Comment: {\n                    tb.insertCommentNode(t.asComment());\n                    break;\n                }\n                case Doctype: {\n                    tb.error(this);\n                    return false;\n                }\n                case StartTag:\n                    return inBodyStartTag(t, tb);\n                case EndTag:\n                    return inBodyEndTag(t, tb);\n                case EOF:\n                    if (tb.templateModeSize() > 0)\n                        return tb.process(t, InTemplate);\n                    if (tb.onStackNot(InBodyEndOtherErrors))\n                        tb.error(this);\n                    // stop parsing\n                    break;\n                default:\n                    Validate.wtf(\"Unexpected state: \" + t.type); // XmlDecl only in XmlTreeBuilder\n            }\n            return true;\n        }\n\n        private boolean inBodyStartTag(Token t, HtmlTreeBuilder tb) {\n            final Token.StartTag startTag = t.asStartTag();\n            final String name = startTag.normalName();\n            final ArrayList<Element> stack;\n            Element el;\n\n            switch (name) {\n                case \"a\":\n                    if (tb.getActiveFormattingElement(\"a\") != null) {\n                        tb.error(this);\n                        tb.processEndTag(\"a\");\n\n                        // still on stack?\n                        Element remainingA = tb.getFromStack(\"a\");\n                        if (remainingA != null) {\n                            tb.removeFromActiveFormattingElements(remainingA);\n                            tb.removeFromStack(remainingA);\n                        }\n                    }\n                    tb.reconstructFormattingElements();\n                    el = tb.insertElementFor(startTag);\n                    tb.pushActiveFormattingElements(el);\n                    break;\n                case \"span\":\n                    // same as final else, but short circuits lots of checks\n                    tb.reconstructFormattingElements();\n                    tb.insertElementFor(startTag);\n                    break;\n                case \"li\":\n                    tb.framesetOk(false);\n                    stack = tb.getStack();\n                    for (int i = stack.size() - 1; i > 0; i--) {\n                        el = stack.get(i);\n                        if (el.nameIs(\"li\")) {\n                            tb.processEndTag(\"li\");\n                            break;\n                        }\n                        if (isSpecial(el) && !inSorted(el.normalName(), Constants.InBodyStartLiBreakers))\n                            break;\n                    }\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertElementFor(startTag);\n                    break;\n                case \"html\":\n                    tb.error(this);\n                    if (tb.onStack(\"template\")) return false; // ignore\n                    // otherwise, merge attributes onto real html (if present)\n                    stack = tb.getStack();\n                    if (stack.size() > 0) {\n                        Element html = tb.getStack().get(0);\n                        mergeAttributes(startTag, html);\n                    }\n                    break;\n                case \"body\":\n                    tb.error(this);\n                    stack = tb.getStack();\n                    if (stack.size() < 2 || (stack.size() > 2 && !stack.get(1).nameIs(\"body\")) || tb.onStack(\"template\")) {\n                        // only in fragment case\n                        return false; // ignore\n                    } else {\n                        tb.framesetOk(false);\n                        // will be on stack if this is a nested body. won't be if closed (which is a variance from spec, which leaves it on)\n                        Element body = tb.getFromStack(\"body\");\n                        if (body != null) mergeAttributes(startTag, body);\n                    }\n                    break;\n                case \"frameset\":\n                    tb.error(this);\n                    stack = tb.getStack();\n                    if (stack.size() < 2|| (stack.size() > 2 && !stack.get(1).nameIs(\"body\"))) {\n                        // only in fragment case\n                        return false; // ignore\n                    } else if (!tb.framesetOk()) {\n                        return false; // ignore frameset\n                    } else {\n                        Element second = stack.get(1);\n                        if (second.parent() != null)\n                            second.remove();\n                        // pop up to html element\n                        while (stack.size() > 1)\n                            stack.remove(stack.size() - 1);\n                        tb.insertElementFor(startTag);\n                        tb.transition(InFrameset);\n                    }\n                    break;\n                case \"form\":\n                    if (tb.getFormElement() != null && !tb.onStack(\"template\")) {\n                        tb.error(this);\n                        return false;\n                    }\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.closeElement(\"p\");\n                    }\n                    tb.insertFormElement(startTag, true, true); // won't associate to any template\n                    break;\n                case \"plaintext\":\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertElementFor(startTag);\n                    tb.tokeniser.transition(TokeniserState.PLAINTEXT); // once in, never gets out\n                    break;\n                case \"button\":\n                    if (tb.inButtonScope(\"button\")) {\n                        // close and reprocess\n                        tb.error(this);\n                        tb.processEndTag(\"button\");\n                        tb.process(startTag);\n                    } else {\n                        tb.reconstructFormattingElements();\n                        tb.insertElementFor(startTag);\n                        tb.framesetOk(false);\n                    }\n                    break;\n                case \"nobr\":\n                    tb.reconstructFormattingElements();\n                    if (tb.inScope(\"nobr\")) {\n                        tb.error(this);\n                        tb.processEndTag(\"nobr\");\n                        tb.reconstructFormattingElements();\n                    }\n                    el = tb.insertElementFor(startTag);\n                    tb.pushActiveFormattingElements(el);\n                    break;\n                case \"table\":\n                    if (tb.getDocument().quirksMode() != Document.QuirksMode.quirks && tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertElementFor(startTag);\n                    tb.framesetOk(false);\n                    tb.transition(InTable);\n                    break;\n                case \"input\":\n                    tb.reconstructFormattingElements();\n                    el = tb.insertEmptyElementFor(startTag);\n                    if (!el.attr(\"type\").equalsIgnoreCase(\"hidden\"))\n                        tb.framesetOk(false);\n                    break;\n                case \"hr\":\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertEmptyElementFor(startTag);\n                    tb.framesetOk(false);\n                    break;\n                case \"image\":\n                    if (tb.getFromStack(\"svg\") == null)\n                        return tb.process(startTag.name(\"img\")); // change <image> to <img>, unless in svg\n                    else\n                        tb.insertElementFor(startTag);\n                    break;\n                case \"textarea\":\n                    tb.framesetOk(false);\n                    HandleTextState(startTag, tb, tb.tagFor(startTag).textState());\n                    break;\n                case \"xmp\":\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.reconstructFormattingElements();\n                    tb.framesetOk(false);\n                    HandleTextState(startTag, tb, tb.tagFor(startTag).textState());\n                    break;\n                case \"iframe\":\n                    tb.framesetOk(false);\n                    HandleTextState(startTag, tb, tb.tagFor(startTag).textState());\n                    break;\n                case \"noembed\":\n                    // also handle noscript if script enabled\n                    HandleTextState(startTag, tb, tb.tagFor(startTag).textState());\n                    break;\n                case \"select\":\n                    tb.reconstructFormattingElements();\n                    tb.insertElementFor(startTag);\n                    tb.framesetOk(false);\n                    if (startTag.selfClosing) break; // don't change states if not added to the stack\n\n                    HtmlTreeBuilderState state = tb.state();\n                    if (state.equals(InTable) || state.equals(InCaption) || state.equals(InTableBody) || state.equals(InRow) || state.equals(InCell))\n                        tb.transition(InSelectInTable);\n                    else\n                        tb.transition(InSelect);\n                    break;\n                case \"math\":\n                    tb.reconstructFormattingElements();\n                    tb.insertForeignElementFor(startTag, Parser.NamespaceMathml);\n                    break;\n                case \"svg\":\n                    tb.reconstructFormattingElements();\n                    tb.insertForeignElementFor(startTag, Parser.NamespaceSvg);\n                    break;\n                // static final String[] Headings = new String[]{\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"};\n                case \"h1\":\n                case \"h2\":\n                case \"h3\":\n                case \"h4\":\n                case \"h5\":\n                case \"h6\":\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    if (inSorted(tb.currentElement().normalName(), Constants.Headings)) {\n                        tb.error(this);\n                        tb.pop();\n                    }\n                    tb.insertElementFor(startTag);\n                    break;\n                // static final String[] InBodyStartPreListing = new String[]{\"listing\", \"pre\"};\n                case \"pre\":\n                case \"listing\":\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertElementFor(startTag);\n                    tb.reader.matchConsume(\"\\n\"); // ignore LF if next token\n                    tb.framesetOk(false);\n                    break;\n                // static final String[] DdDt = new String[]{\"dd\", \"dt\"};\n                case \"dd\":\n                case \"dt\":\n                    tb.framesetOk(false);\n                    stack = tb.getStack();\n                    final int bottom = stack.size() - 1;\n                    final int upper = bottom >= MaxStackScan ? bottom - MaxStackScan : 0;\n                    for (int i = bottom; i >= upper; i--) {\n                        el = stack.get(i);\n                        if (inSorted(el.normalName(), Constants.DdDt)) {\n                            tb.processEndTag(el.normalName());\n                            break;\n                        }\n                        if (isSpecial(el) && !inSorted(el.normalName(), Constants.InBodyStartLiBreakers))\n                            break;\n                    }\n                    if (tb.inButtonScope(\"p\")) {\n                        tb.processEndTag(\"p\");\n                    }\n                    tb.insertElementFor(startTag);\n                    break;\n\n                case \"optgroup\":\n                case \"option\":\n                    if (tb.currentElementIs(\"option\"))\n                        tb.processEndTag(\"option\");\n                    tb.reconstructFormattingElements();\n                    tb.insertElementFor(startTag);\n                    break;\n\n                case \"rb\":\n                case \"rtc\":\n                    if (tb.inScope(\"ruby\")) {\n                        tb.generateImpliedEndTags();\n                        if (!tb.currentElementIs(\"ruby\"))\n                            tb.error(this);\n                    }\n                    tb.insertElementFor(startTag);\n                    break;\n\n                case \"rp\":\n                case \"rt\":\n                    if (tb.inScope(\"ruby\")) {\n                        tb.generateImpliedEndTags(\"rtc\");\n                        if (!tb.currentElementIs(\"rtc\") && !tb.currentElementIs(\"ruby\"))\n                            tb.error(this);\n                    }\n                    tb.insertElementFor(startTag);\n                    break;\n\n                // InBodyStartEmptyFormatters:\n                case \"area\":\n                case \"br\":\n                case \"embed\":\n                case \"img\":\n                case \"keygen\":\n                case \"wbr\":\n                    tb.reconstructFormattingElements();\n                    tb.insertEmptyElementFor(startTag);\n                    tb.framesetOk(false);\n                    break;\n                // Formatters:\n                case \"b\":\n                case \"big\":\n                case \"code\":\n                case \"em\":\n                case \"font\":\n                case \"i\":\n                case \"s\":\n                case \"small\":\n                case \"strike\":\n                case \"strong\":\n                case \"tt\":\n                case \"u\":\n                    tb.reconstructFormattingElements();\n                    el = tb.insertElementFor(startTag);\n                    tb.pushActiveFormattingElements(el);\n                    break;\n                default:\n                    Tag tag = tb.tagFor(startTag);\n                    TokeniserState textState = tag.textState();\n                    if (textState != null) { // custom rcdata or rawtext (if we were in head, will have auto-transitioned here)\n                        HandleTextState(startTag, tb, textState);\n                    } else if (!tag.isKnownTag()) { // no other special rules for custom tags\n                        tb.insertElementFor(startTag);\n                    } else if (inSorted(name, Constants.InBodyStartPClosers)) {\n                        if (tb.inButtonScope(\"p\")) tb.processEndTag(\"p\");\n                        tb.insertElementFor(startTag);\n                    } else if (inSorted(name, Constants.InBodyStartToHead)) {\n                        return tb.process(t, InHead);\n                    } else if (inSorted(name, Constants.InBodyStartApplets)) {\n                        tb.reconstructFormattingElements();\n                        tb.insertElementFor(startTag);\n                        tb.insertMarkerToFormattingElements();\n                        tb.framesetOk(false);\n                    } else if (inSorted(name, Constants.InBodyStartMedia)) {\n                        tb.insertEmptyElementFor(startTag);\n                    } else if (inSorted(name, Constants.InBodyStartDrop)) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        tb.reconstructFormattingElements();\n                        tb.insertElementFor(startTag);\n                    }\n            }\n            return true;\n        }\n        private static final int MaxStackScan = 24; // used for DD / DT scan, prevents runaway\n\n        private boolean inBodyEndTag(Token t, HtmlTreeBuilder tb) {\n            final Token.EndTag endTag = t.asEndTag();\n            final String name = endTag.normalName();\n\n            switch (name) {\n                case \"template\":\n                    tb.process(t, InHead);\n                    break;\n                case \"sarcasm\": // *sigh*\n                case \"span\":\n                    // same as final fall through, but saves short circuit\n                    return anyOtherEndTag(t, tb);\n                case \"li\":\n                    if (!tb.inListItemScope(name)) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        tb.generateImpliedEndTags(name);\n                        if (!tb.currentElementIs(name))\n                            tb.error(this);\n                        tb.popStackToClose(name);\n                    }\n                    break;\n                case \"body\":\n                    if (!tb.inScope(\"body\")) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        if (tb.onStackNot(InBodyEndOtherErrors))\n                            tb.error(this);\n                        tb.trackNodePosition(tb.getFromStack(\"body\"), false); // track source position of close; body is left on stack, in case of trailers\n                        tb.transition(AfterBody);\n                    }\n                    break;\n                case \"html\":\n                    if (!tb.onStack(\"body\")) {\n                        tb.error(this);\n                        return false; // ignore\n                    } else {\n                        if (tb.onStackNot(InBodyEndOtherErrors))\n                            tb.error(this);\n                        tb.transition(AfterBody);\n                        return tb.process(t); // re-process\n                    }\n\n                case \"form\":\n                    if (!tb.onStack(\"template\")) {\n                        Element currentForm = tb.getFormElement();\n                        tb.setFormElement(null);\n                        if (currentForm == null || !tb.inScope(name)) {\n                            tb.error(this);\n                            return false;\n                        }\n                        tb.generateImpliedEndTags();\n                        if (!tb.currentElementIs(name))\n                            tb.error(this);\n                        // remove currentForm from stack. will shift anything under up.\n                        tb.removeFromStack(currentForm);\n                    } else { // template on stack\n                        if (!tb.inScope(name)) {\n                            tb.error(this);\n                            return false;\n                        }\n                        tb.generateImpliedEndTags();\n                        if (!tb.currentElementIs(name)) tb.error(this);\n                        tb.popStackToClose(name);\n                    }\n                    break;\n                case \"p\":\n                    if (!tb.inButtonScope(name)) {\n                        tb.error(this);\n                        tb.processStartTag(name); // if no p to close, creates an empty <p></p>\n                        return tb.process(endTag);\n                    } else {\n                        tb.generateImpliedEndTags(name);\n                        if (!tb.currentElementIs(name))\n                            tb.error(this);\n                        tb.popStackToClose(name);\n                    }\n                    break;\n                case \"dd\":\n                case \"dt\":\n                    if (!tb.inScope(name)) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        tb.generateImpliedEndTags(name);\n                        if (!tb.currentElementIs(name))\n                            tb.error(this);\n                        tb.popStackToClose(name);\n                    }\n                    break;\n                case \"h1\":\n                case \"h2\":\n                case \"h3\":\n                case \"h4\":\n                case \"h5\":\n                case \"h6\":\n                    if (!tb.inScope(Constants.Headings)) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        tb.generateImpliedEndTags(name);\n                        if (!tb.currentElementIs(name))\n                            tb.error(this);\n                        tb.popStackToClose(Constants.Headings);\n                    }\n                    break;\n                case \"br\":\n                    tb.error(this);\n                    tb.processStartTag(\"br\");\n                    return false;\n                default:\n                    // todo - move rest to switch if desired\n                    if (inSorted(name, Constants.InBodyEndAdoptionFormatters)) {\n                        return inBodyEndTagAdoption(t, tb);\n                    } else if (inSorted(name, Constants.InBodyEndClosers)) {\n                        if (!tb.inScope(name)) {\n                            // nothing to close\n                            tb.error(this);\n                            return false;\n                        } else {\n                            tb.generateImpliedEndTags();\n                            if (!tb.currentElementIs(name))\n                                tb.error(this);\n                            tb.popStackToClose(name);\n                        }\n                    } else if (inSorted(name, Constants.InBodyStartApplets)) {\n                        if (!tb.inScope(\"name\")) {\n                            if (!tb.inScope(name)) {\n                                tb.error(this);\n                                return false;\n                            }\n                            tb.generateImpliedEndTags();\n                            if (!tb.currentElementIs(name))\n                                tb.error(this);\n                            tb.popStackToClose(name);\n                            tb.clearFormattingElementsToLastMarker();\n                        }\n                    } else {\n                        return anyOtherEndTag(t, tb);\n                    }\n            }\n            return true;\n        }\n\n        boolean anyOtherEndTag(Token t, HtmlTreeBuilder tb) {\n            final String name = t.asEndTag().normalName; // case insensitive search - goal is to preserve output case, not for the parse to be case sensitive\n            final ArrayList<Element> stack = tb.getStack();\n\n            // deviate from spec slightly to speed when super deeply nested\n            Element elFromStack = tb.getFromStack(name);\n            if (elFromStack == null) {\n                tb.error(this);\n                return false;\n            }\n\n            for (int pos = stack.size() - 1; pos >= 0; pos--) {\n                Element node = stack.get(pos);\n                if (node.nameIs(name)) {\n                    tb.generateImpliedEndTags(name);\n                    if (!tb.currentElementIs(name))\n                        tb.error(this);\n                    tb.popStackToClose(name);\n                    break;\n                } else {\n                    if (isSpecial(node)) {\n                        tb.error(this);\n                        return false;\n                    }\n                }\n            }\n            return true;\n        }\n\n        private boolean inBodyEndTagAdoption(Token t, HtmlTreeBuilder tb) {\n            // https://html.spec.whatwg.org/multipage/parsing.html#adoption-agency-algorithm\n            // JH: Including the spec notes here to simplify tracking / correcting. It's a bit gnarly and there may still be some nuances I haven't caught. But test cases and comparisons to browsers check out.\n\n            // The adoption agency algorithm, which takes as its only argument a token token for which the algorithm is being run, consists of the following steps:\n            final Token.EndTag endTag = t.asEndTag();\n            final String subject = endTag.normalName; // 1. Let subject be token's tag name.\n\n            // 2. If the [current node] is an [HTML element] whose tag name is subject, and the [current node] is not in the [list of active formatting elements], then pop the [current node] off the [stack of open elements] and return.\n            if (tb.currentElement().normalName().equals(subject) && !tb.isInActiveFormattingElements(tb.currentElement())) {\n                tb.pop();\n                return true;\n            }\n            int outer = 0; // 3. Let outerLoopCounter be 0.\n            while (true) { // 4. While true:\n                if (outer >= 8) { // 1. If outerLoopCounter is greater than or equal to 8, then return.\n                    return true;\n                }\n                outer++; // 2. Increment outerLoopCounter by 1.\n\n                // 3. Let formattingElement be the last element in the [list of active formatting elements] that:\n                //  - is between the end of the list and the last [marker] in the list, if any, or the start of the list otherwise, and\n                //  - has the tag name subject.\n                //  If there is no such element, then return and instead act as described in the \"any other end tag\" entry above.\n                Element formatEl = null;\n                for (int i = tb.formattingElements.size() - 1; i >= 0; i--) {\n                    Element next = tb.formattingElements.get(i);\n                    if (next == null) // marker\n                        break;\n                    if (next.normalName().equals(subject)) {\n                        formatEl = next;\n                        break;\n                    }\n                }\n                if (formatEl == null) {\n                    return anyOtherEndTag(t, tb);\n                }\n\n                // 4. If formattingElement is not in the [stack of open elements], then this is a [parse error]; remove the element from the list, and return.\n                if (!tb.onStack(formatEl)) {\n                    tb.error(this);\n                    tb.removeFromActiveFormattingElements(formatEl);\n                    return true;\n                }\n\n                //  5. If formattingElement is in the [stack of open elements], but the element is not [in scope], then this is a [parse error]; return.\n                if (!tb.inScope(formatEl.normalName())) {\n                    tb.error(this);\n                    return false;\n                } else if (tb.currentElement() != formatEl) { //  6. If formattingElement is not the [current node], this is a [parse error].\n                    tb.error(this);\n                }\n\n                //  7. Let furthestBlock be the topmost node in the [stack of open elements] that is lower in the stack than formattingElement, and is an element in the [special]category. There might not be one.\n                Element furthestBlock = null;\n                ArrayList<Element> stack = tb.getStack();\n                int fei = stack.lastIndexOf(formatEl);\n                if (fei != -1) { // look down the stack\n                    for (int i = fei + 1; i < stack.size(); i++) {\n                        Element el = stack.get(i);\n                        if (isSpecial(el)) {\n                            furthestBlock = el;\n                            break;\n                        }\n                    }\n                }\n\n                //  8. If there is no furthestBlock, then the UA must first pop all the nodes from the bottom of the [stack of open elements], from the [current node] up to and including formattingElement, then remove formattingElement from the [list of active formatting elements], and finally return.\n                if (furthestBlock == null) {\n                    while (tb.currentElement() != formatEl) {\n                        tb.pop();\n                    }\n                    tb.pop();\n                    tb.removeFromActiveFormattingElements(formatEl);\n                    return true;\n                }\n\n                Element commonAncestor = tb.aboveOnStack(formatEl); // 9. Let commonAncestor be the element immediately above formattingElement in the [stack of open elements].\n                if (commonAncestor == null) { tb.error(this); return true; } // Would be a WTF\n\n                // 10. Let a bookmark note the position of formattingElement in the [list of active formatting elements] relative to the elements on either side of it in the list.\n                // JH - I think this means its index? Or do we need a linked list?\n                int bookmark = tb.positionOfElement(formatEl);\n\n                Element el = furthestBlock; //  11. Let node and lastNode be furthestBlock.\n                Element lastEl = furthestBlock;\n                int inner = 0; // 12. Let innerLoopCounter be 0.\n\n                while (true) { // 13. While true:\n                    inner++; // 1. Increment innerLoopCounter by 1.\n                    // 2. Let node be the element immediately above node in the [stack of open elements], or if node is no longer in the [stack of open elements] , the element that was immediately above node in the [stack of open elements] before node was removed.\n                    if (!tb.onStack(el)) {\n                        // if node was removed from stack, use the element that was above it\n                        el = el.parent(); // JH - is there a situation where it's not the parent?\n                    } else {\n                        el = tb.aboveOnStack(el);\n                    }\n                    if (el == null || el.nameIs(\"body\")) {\n                        tb.error(this); // shouldn't be able to hit\n                        break;\n                    }\n                    //  3. If node is formattingElement, then [break].\n                    if (el == formatEl) {\n                        break;\n                    }\n\n                    //  4. If innerLoopCounter is greater than 3 and node is in the [list of active formatting elements], then remove node from the [list of active formatting elements].\n                    if (inner > 3 && tb.isInActiveFormattingElements(el)) {\n                        tb.removeFromActiveFormattingElements(el);\n                        break;\n                    }\n                    // 5. If node is not in the [list of active formatting elements], then remove node from the [stack of open elements] and [continue].\n                    if (!tb.isInActiveFormattingElements(el)) {\n                        tb.removeFromStack(el);\n                        continue;\n                    }\n\n                    //  6. [Create an element for the token] for which the element node was created, in the [HTML namespace], with commonAncestor as the intended parent; replace the entry for node in the [list of active formatting elements] with an entry for the new element, replace the entry for node in the [stack of open elements] with an entry for the new element, and let node be the new element.\n                    if (!tb.onStack(el)) { // stale formatting element; cannot adopt/replace\n                        tb.error(this);\n                        tb.removeFromActiveFormattingElements(el);\n                        break; // exit inner loop; proceed with step 14 using current lastEl\n                    }\n                    Element replacement = new Element(tb.tagFor(el.nodeName(), el.normalName(), tb.defaultNamespace(), ParseSettings.preserveCase), tb.getBaseUri());\n                    tb.replaceActiveFormattingElement(el, replacement);\n                    tb.replaceOnStack(el, replacement);\n                    el = replacement;\n\n                    //  7. If lastNode is furthestBlock, then move the aforementioned bookmark to be immediately after the new node in the [list of active formatting elements].\n                    if (lastEl == furthestBlock) {\n                        bookmark = tb.positionOfElement(el) + 1;\n                    }\n                    el.appendChild(lastEl); // 8. [Append] lastNode to node.\n                    lastEl = el; // 9. Set lastNode to node.\n                } // end inner loop # 13\n\n                // 14. Insert whatever lastNode ended up being in the previous step at the [appropriate place for inserting a node], but using commonAncestor as the _override target_.\n                // todo - impl https://html.spec.whatwg.org/multipage/parsing.html#appropriate-place-for-inserting-a-node fostering\n                // just use commonAncestor as target:\n                commonAncestor.appendChild(lastEl);\n                // 15. [Create an element for the token] for which formattingElement was created, in the [HTML namespace], with furthestBlock as the intended parent.\n                Element adoptor = new Element(formatEl.tag(), tb.getBaseUri());\n                adoptor.attributes().addAll(formatEl.attributes()); // also attributes\n                // 16. Take all of the child nodes of furthestBlock and append them to the element created in the last step.\n                for (Node child : furthestBlock.childNodes()) {\n                    adoptor.appendChild(child);\n                }\n\n                furthestBlock.appendChild(adoptor); // 17. Append that new element to furthestBlock.\n                // 18. Remove formattingElement from the [list of active formatting elements], and insert the new element into the [list of active formatting elements] at the position of the aforementioned bookmark.\n                tb.removeFromActiveFormattingElements(formatEl);\n                tb.pushWithBookmark(adoptor, bookmark);\n                // 19. Remove formattingElement from the [stack of open elements], and insert the new element into the [stack of open elements] immediately below the position of furthestBlock in that stack.\n                tb.removeFromStack(formatEl);\n                tb.insertOnStackAfter(furthestBlock, adoptor);\n            } // end of outer loop # 4\n        }\n    },\n    Text {\n        // in script, style etc. normally treated as data tags\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isCharacter()) {\n                tb.insertCharacterNode(t.asCharacter());\n            } else if (t.isEOF()) {\n                tb.error(this);\n                // if current node is script: already started\n                tb.pop();\n                tb.transition(tb.originalState());\n                if (tb.state() == Text) // stack is such that we couldn't transition out; just close\n                    tb.transition(InBody);\n                return tb.process(t);\n            } else if (t.isEndTag()) {\n                // if: An end tag whose tag name is \"script\" -- scripting nesting level, if evaluating scripts\n                tb.pop();\n                tb.transition(tb.originalState());\n            }\n            return true;\n        }\n    },\n    InTable {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isCharacter() && inSorted(tb.currentElement().normalName(), InTableFoster)) {\n                tb.resetPendingTableCharacters();\n                tb.markInsertionMode();\n                tb.transition(InTableText);\n                return tb.process(t);\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n                return true;\n            } else if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isStartTag()) {\n                Token.StartTag startTag = t.asStartTag();\n                String name = startTag.normalName();\n                if (name.equals(\"caption\")) {\n                    tb.clearStackToTableContext();\n                    tb.insertMarkerToFormattingElements();\n                    tb.insertElementFor(startTag);\n                    tb.transition(InCaption);\n                } else if (name.equals(\"colgroup\")) {\n                    tb.clearStackToTableContext();\n                    tb.insertElementFor(startTag);\n                    tb.transition(InColumnGroup);\n                } else if (name.equals(\"col\")) {\n                    tb.clearStackToTableContext();\n                    tb.processStartTag(\"colgroup\");\n                    return tb.process(t);\n                } else if (inSorted(name, InTableToBody)) {\n                    tb.clearStackToTableContext();\n                    tb.insertElementFor(startTag);\n                    tb.transition(InTableBody);\n                } else if (inSorted(name, InTableAddBody)) {\n                    tb.clearStackToTableContext();\n                    tb.processStartTag(\"tbody\");\n                    return tb.process(t);\n                } else if (name.equals(\"table\")) {\n                    tb.error(this);\n                    if (!tb.inTableScope(name)) { // ignore it\n                        return false;\n                    } else {\n                        tb.popStackToClose(name);\n                        if (!tb.resetInsertionMode()) {\n                            // not per spec - but haven't transitioned out of table. so try something else\n                            tb.insertElementFor(startTag);\n                            return true;\n                        }\n                        return tb.process(t);\n                    }\n                } else if (inSorted(name, InTableToHead)) {\n                    return tb.process(t, InHead);\n                } else if (name.equals(\"input\")) {\n                    if (!(startTag.hasAttributes() && startTag.attributes.get(\"type\").equalsIgnoreCase(\"hidden\"))) {\n                        return anythingElse(t, tb);\n                    } else {\n                        tb.insertEmptyElementFor(startTag);\n                    }\n                } else if (name.equals(\"form\")) {\n                    tb.error(this);\n                    if (tb.getFormElement() != null || tb.onStack(\"template\"))\n                        return false;\n                    else {\n                        tb.insertFormElement(startTag, false, false); // not added to stack. can associate to template\n                    }\n                } else {\n                    return anythingElse(t, tb);\n                }\n                return true; // todo: check if should return processed http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intable\n            } else if (t.isEndTag()) {\n                Token.EndTag endTag = t.asEndTag();\n                String name = endTag.normalName();\n\n                if (name.equals(\"table\")) {\n                    if (!tb.inTableScope(name)) {\n                        tb.error(this);\n                        return false;\n                    } else {\n                        tb.popStackToClose(\"table\");\n                        tb.resetInsertionMode();\n                    }\n                } else if (inSorted(name, InTableEndErr)) {\n                    tb.error(this);\n                    return false;\n                } else if (name.equals(\"template\")) {\n                    tb.process(t, InHead);\n                } else {\n                    return anythingElse(t, tb);\n                }\n                return true; // todo: as above todo\n            } else if (t.isEOF()) {\n                if (tb.currentElementIs(\"html\"))\n                    tb.error(this);\n                return true; // stops parsing\n            }\n            return anythingElse(t, tb);\n        }\n\n        boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            tb.error(this);\n            tb.setFosterInserts(true);\n            tb.process(t, InBody);\n            tb.setFosterInserts(false);\n            return true;\n        }\n    },\n    InTableText {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.type == Token.TokenType.Character) {\n                tb.addPendingTableCharacters(t.asCharacter()); // gets to insertCharacterNode, which strips nulls\n            } else {\n                // insert gathered table text into the correct element:\n                if (tb.getPendingTableCharacters().size() > 0) {\n                    final Token og = tb.currentToken; // update current token, so we can track cursor pos correctly\n                    for (Token.Character c : tb.getPendingTableCharacters()) {\n                        tb.currentToken = c;\n                        if (!isWhitespace(c)) {\n                            // InTable anything else section:\n                            tb.error(this);\n                            if (inSorted(tb.currentElement().normalName(), InTableFoster)) {\n                                tb.setFosterInserts(true);\n                                tb.process(c, InBody);\n                                tb.setFosterInserts(false);\n                            } else {\n                                tb.process(c, InBody);\n                            }\n                        } else\n                            tb.insertCharacterNode(c);\n                    }\n                    tb.currentToken = og;\n                    tb.resetPendingTableCharacters();\n                }\n                tb.transition(tb.originalState());\n                return tb.process(t);\n            }\n            return true;\n        }\n    },\n    InCaption {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isEndTag() && t.asEndTag().normalName().equals(\"caption\")) {\n                if (!tb.inTableScope(\"caption\")) { // fragment case\n                    tb.error(this);\n                    return false;\n                } else {\n                    tb.generateImpliedEndTags();\n                    if (!tb.currentElementIs(\"caption\")) tb.error(this);\n                    tb.popStackToClose(\"caption\");\n                    tb.clearFormattingElementsToLastMarker();\n                    tb.transition(InTable);\n                }\n            } else if ((\n                    t.isStartTag() && inSorted(t.asStartTag().normalName(), InCellCol) ||\n                            t.isEndTag() && t.asEndTag().normalName().equals(\"table\"))\n                    ) {\n                // same as above but processes after transition\n                if (!tb.inTableScope(\"caption\")) { // fragment case\n                    tb.error(this);\n                    return false;\n                }\n                tb.generateImpliedEndTags(false);\n                if (!tb.currentElementIs(\"caption\")) tb.error(this);\n                tb.popStackToClose(\"caption\");\n                tb.clearFormattingElementsToLastMarker();\n                tb.transition(InTable);\n                InTable.process(t, tb); // doesn't check foreign context\n            } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(), InCaptionIgnore)) {\n                tb.error(this);\n                return false;\n            } else {\n                return tb.process(t, InBody);\n            }\n            return true;\n        }\n    },\n    InColumnGroup {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter());\n                return true;\n            }\n            switch (t.type) {\n                case Comment:\n                    tb.insertCommentNode(t.asComment());\n                    break;\n                case Doctype:\n                    tb.error(this);\n                    break;\n                case StartTag:\n                    Token.StartTag startTag = t.asStartTag();\n                    switch (startTag.normalName()) {\n                        case \"html\":\n                            return tb.process(t, InBody);\n                        case \"col\":\n                            tb.insertEmptyElementFor(startTag);\n                            break;\n                        case \"template\":\n                            tb.process(t, InHead);\n                            break;\n                        default:\n                            return anythingElse(t, tb);\n                    }\n                    break;\n                case EndTag:\n                    Token.EndTag endTag = t.asEndTag();\n                    String name = endTag.normalName();\n                    switch (name) {\n                        case \"colgroup\":\n                            if (!tb.currentElementIs(name)) {\n                                tb.error(this);\n                                return false;\n                            } else {\n                                tb.pop();\n                                tb.transition(InTable);\n                            }\n                            break;\n                        case \"template\":\n                            tb.process(t, InHead);\n                            break;\n                        default:\n                            return anythingElse(t, tb);\n                    }\n                    break;\n                case EOF:\n                    if (tb.currentElementIs(\"html\"))\n                        return true; // stop parsing; frag case\n                    else\n                        return anythingElse(t, tb);\n                default:\n                    return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            if (!tb.currentElementIs(\"colgroup\")) {\n                tb.error(this);\n                return false;\n            }\n            tb.pop();\n            tb.transition(InTable);\n            tb.process(t);\n            return true;\n        }\n    },\n    InTableBody {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            final String name;\n\n            switch (t.type) {\n                case StartTag:\n                    Token.StartTag startTag = t.asStartTag();\n                    name = startTag.normalName();\n                    if (name.equals(\"tr\")) {\n                        tb.clearStackToTableBodyContext();\n                        tb.insertElementFor(startTag);\n                        tb.transition(InRow);\n                    } else if (inSorted(name, InCellNames)) {\n                        tb.error(this);\n                        tb.processStartTag(\"tr\");\n                        return tb.process(startTag);\n                    } else if (inSorted(name, InTableBodyExit)) {\n                        return exitTableBody(t, tb);\n                    } else\n                        return anythingElse(t, tb);\n                    break;\n                case EndTag:\n                    Token.EndTag endTag = t.asEndTag();\n                    name = endTag.normalName();\n                    if (inSorted(name, InTableEndIgnore)) {\n                        if (!tb.inTableScope(name)) {\n                            tb.error(this);\n                            return false;\n                        } else {\n                            tb.clearStackToTableBodyContext();\n                            tb.pop();\n                            tb.transition(InTable);\n                        }\n                    } else if (name.equals(\"table\")) {\n                        return exitTableBody(t, tb);\n                    } else if (inSorted(name, InTableBodyEndIgnore)) {\n                        tb.error(this);\n                        return false;\n                    } else\n                        return anythingElse(t, tb);\n                    break;\n                default:\n                    return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean exitTableBody(Token t, HtmlTreeBuilder tb) {\n            if (!(tb.inTableScope(\"tbody\") || tb.inTableScope(\"thead\") || tb.inScope(\"tfoot\"))) {\n                // frag case\n                tb.error(this);\n                return false;\n            }\n            tb.clearStackToTableBodyContext();\n            tb.processEndTag(tb.currentElement().normalName()); // tbody, tfoot, thead\n            return tb.process(t);\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            return tb.process(t, InTable);\n        }\n    },\n    InRow {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isStartTag()) {\n                Token.StartTag startTag = t.asStartTag();\n                String name = startTag.normalName();\n\n                if (inSorted(name, InCellNames)) { // td, th\n                    tb.clearStackToTableRowContext();\n                    tb.insertElementFor(startTag);\n                    tb.transition(InCell);\n                    tb.insertMarkerToFormattingElements();\n                } else if (inSorted(name, InRowMissing)) { // \"caption\", \"col\", \"colgroup\", \"tbody\", \"tfoot\", \"thead\", \"tr\"\n                    if (!tb.inTableScope(\"tr\")) {\n                        tb.error(this);\n                        return false;\n                    }\n                    tb.clearStackToTableRowContext();\n                    tb.pop(); // tr\n                    tb.transition(InTableBody);\n                    return tb.process(t);\n                } else {\n                    return anythingElse(t, tb);\n                }\n            } else if (t.isEndTag()) {\n                Token.EndTag endTag = t.asEndTag();\n                String name = endTag.normalName();\n\n                if (name.equals(\"tr\")) {\n                    if (!tb.inTableScope(name)) {\n                        tb.error(this); // frag\n                        return false;\n                    }\n                    tb.clearStackToTableRowContext();\n                    tb.pop(); // tr\n                    tb.transition(InTableBody);\n                } else if (name.equals(\"table\")) {\n                    if (!tb.inTableScope(\"tr\")) {\n                        tb.error(this);\n                        return false;\n                    }\n                    tb.clearStackToTableRowContext();\n                    tb.pop(); // tr\n                    tb.transition(InTableBody);\n                    return tb.process(t);\n                } else if (inSorted(name, InTableToBody)) { // \"tbody\", \"tfoot\", \"thead\"\n                    if (!tb.inTableScope(name)) {\n                        tb.error(this);\n                        return false;\n                    }\n                    if (!tb.inTableScope(\"tr\")) {\n                        // not an error per spec?\n                        return false;\n                    }\n                    tb.clearStackToTableRowContext();\n                    tb.pop(); // tr\n                    tb.transition(InTableBody);\n                    return tb.process(t);\n                } else if (inSorted(name, InRowIgnore)) {\n                    tb.error(this);\n                    return false;\n                } else {\n                    return anythingElse(t, tb);\n                }\n            } else {\n                return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            return tb.process(t, InTable);\n        }\n    },\n    InCell {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isEndTag()) {\n                Token.EndTag endTag = t.asEndTag();\n                String name = endTag.normalName();\n\n                if (inSorted(name, Constants.InCellNames)) { // td, th\n                    if (!tb.inTableScope(name)) {\n                        tb.error(this);\n                        tb.transition(InRow); // might not be in scope if empty: <td /> and processing fake end tag\n                        return false;\n                    }\n                    tb.generateImpliedEndTags();\n                    if (!tb.currentElementIs(name))\n                        tb.error(this);\n                    tb.popStackToClose(name);\n                    tb.clearFormattingElementsToLastMarker();\n                    tb.transition(InRow);\n                } else if (inSorted(name, Constants.InCellBody)) {\n                    tb.error(this);\n                    return false;\n                } else if (inSorted(name, Constants.InCellTable)) {\n                    if (!tb.inTableScope(name)) {\n                        tb.error(this);\n                        return false;\n                    }\n                    closeCell(tb);\n                    return tb.process(t);\n                } else {\n                    return anythingElse(t, tb);\n                }\n            } else if (t.isStartTag() &&\n                    inSorted(t.asStartTag().normalName(), Constants.InCellCol)) {\n                if (!(tb.inTableScope(\"td\") || tb.inTableScope(\"th\"))) {\n                    tb.error(this);\n                    return false;\n                }\n                closeCell(tb);\n                return tb.process(t);\n            } else {\n                return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            return tb.process(t, InBody);\n        }\n\n        private void closeCell(HtmlTreeBuilder tb) {\n            if (tb.inTableScope(\"td\"))\n                tb.processEndTag(\"td\");\n            else\n                tb.processEndTag(\"th\"); // only here if th or td in scope\n        }\n    },\n    InSelect {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            final String name;\n\n            switch (t.type) {\n                case Character:\n                    tb.insertCharacterNode(t.asCharacter());\n                    break;\n                case Comment:\n                    tb.insertCommentNode(t.asComment());\n                    break;\n                case Doctype:\n                    tb.error(this);\n                    return false;\n                case StartTag:\n                    Token.StartTag start = t.asStartTag();\n                    name = start.normalName();\n                    if (name.equals(\"html\"))\n                        return tb.process(start, InBody);\n                    else if (name.equals(\"option\")) {\n                        if (tb.currentElementIs(\"option\"))\n                            tb.processEndTag(\"option\");\n                        tb.insertElementFor(start);\n                    } else if (name.equals(\"optgroup\")) {\n                        if (tb.currentElementIs(\"option\"))\n                            tb.processEndTag(\"option\"); // pop option and flow to pop optgroup\n                        if (tb.currentElementIs(\"optgroup\"))\n                            tb.processEndTag(\"optgroup\");\n                        tb.insertElementFor(start);\n                    } else if (name.equals(\"select\")) {\n                        tb.error(this);\n                        return tb.processEndTag(\"select\");\n                    } else if (inSorted(name, InSelectEnd)) {\n                        tb.error(this);\n                        if (!tb.inSelectScope(\"select\"))\n                            return false; // frag\n                        // spec says close select then reprocess; leads to recursion. iter directly:\n                        do {\n                            tb.popStackToClose(\"select\");\n                            tb.resetInsertionMode();\n                        } while (tb.inSelectScope(\"select\")); // collapse invalid nested selects\n                        return tb.process(start);\n                    } else if (name.equals(\"script\") || name.equals(\"template\")) {\n                        return tb.process(t, InHead);\n                    } else {\n                        return anythingElse(t, tb);\n                    }\n                    break;\n                case EndTag:\n                    Token.EndTag end = t.asEndTag();\n                    name = end.normalName();\n                    switch (name) {\n                        case \"optgroup\":\n                            if (tb.currentElementIs(\"option\") && tb.aboveOnStack(tb.currentElement()) != null && tb.aboveOnStack(tb.currentElement()).nameIs(\"optgroup\"))\n                                tb.processEndTag(\"option\");\n                            if (tb.currentElementIs(\"optgroup\"))\n                                tb.pop();\n                            else\n                                tb.error(this);\n                            break;\n                        case \"option\":\n                            if (tb.currentElementIs(\"option\"))\n                                tb.pop();\n                            else\n                                tb.error(this);\n                            break;\n                        case \"select\":\n                            if (!tb.inSelectScope(name)) {\n                                tb.error(this);\n                                return false;\n                            } else {\n                                tb.popStackToClose(name);\n                                tb.resetInsertionMode();\n                            }\n                            break;\n                        case \"template\":\n                            return tb.process(t, InHead);\n                        default:\n                            return anythingElse(t, tb);\n                    }\n                    break;\n                case EOF:\n                    if (!tb.currentElementIs(\"html\"))\n                        tb.error(this);\n                    break;\n                default:\n                    return anythingElse(t, tb);\n            }\n            return true;\n        }\n\n        private boolean anythingElse(Token t, HtmlTreeBuilder tb) {\n            tb.error(this);\n            return false;\n        }\n    },\n    InSelectInTable {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isStartTag() && inSorted(t.asStartTag().normalName(), InSelectTableEnd)) {\n                tb.error(this);\n                tb.popStackToClose(\"select\");\n                tb.resetInsertionMode();\n                return tb.process(t);\n            } else if (t.isEndTag() && inSorted(t.asEndTag().normalName(), InSelectTableEnd)) {\n                tb.error(this);\n                if (tb.inTableScope(t.asEndTag().normalName())) {\n                    tb.popStackToClose(\"select\");\n                    tb.resetInsertionMode();\n                    return (tb.process(t));\n                } else\n                    return false;\n            } else {\n                return tb.process(t, InSelect);\n            }\n        }\n    },\n    InTemplate {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            final String name;\n            switch (t.type) {\n                case Character:\n                case Comment:\n                case Doctype:\n                    tb.process(t, InBody);\n                    break;\n                case StartTag:\n                    name = t.asStartTag().normalName();\n                    if (inSorted(name, InTemplateToHead))\n                        tb.process(t, InHead);\n                    else if (inSorted(name, InTemplateToTable)) {\n                        tb.popTemplateMode();\n                        tb.pushTemplateMode(InTable);\n                        tb.transition(InTable);\n                        return tb.process(t);\n                    }\n                    else if (name.equals(\"col\")) {\n                        tb.popTemplateMode();\n                        tb.pushTemplateMode(InColumnGroup);\n                        tb.transition(InColumnGroup);\n                        return tb.process(t);\n                    } else if (name.equals(\"tr\")) {\n                        tb.popTemplateMode();\n                        tb.pushTemplateMode(InTableBody);\n                        tb.transition(InTableBody);\n                        return tb.process(t);\n                    } else if (name.equals(\"td\") || name.equals(\"th\")) {\n                        tb.popTemplateMode();\n                        tb.pushTemplateMode(InRow);\n                        tb.transition(InRow);\n                        return tb.process(t);\n                    } else {\n                        tb.popTemplateMode();\n                        tb.pushTemplateMode(InBody);\n                        tb.transition(InBody);\n                        return tb.process(t);\n                    }\n\n                    break;\n                case EndTag:\n                    name = t.asEndTag().normalName();\n                    if (name.equals(\"template\"))\n                        tb.process(t, InHead);\n                    else {\n                        tb.error(this);\n                        return false;\n                    }\n                    break;\n                case EOF:\n                    if (!tb.onStack(\"template\")) {// stop parsing\n                        return true;\n                    }\n                    tb.error(this);\n                    tb.popStackToClose(\"template\");\n                    tb.clearFormattingElementsToLastMarker();\n                    tb.popTemplateMode();\n                    tb.resetInsertionMode();\n                    // spec deviation - if we did not break out of Template, stop processing, and don't worry about cleaning up ultra-deep template stacks\n                    // limited depth because this can recurse and will blow stack if too deep\n                    if (tb.state() != InTemplate && tb.templateModeSize() < 12)\n                        return tb.process(t);\n                    else return true;\n                default:\n                    Validate.wtf(\"Unexpected state: \" + t.type); // XmlDecl only in XmlTreeBuilder\n            }\n            return true;\n        }\n    },\n    AfterBody {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            Element html = tb.getFromStack(\"html\");\n            if (isWhitespace(t)) {\n                // spec deviation - currently body is still on stack, but we want this to go to the html node\n                if (html != null)\n                    tb.insertCharacterToElement(t.asCharacter(), html);\n                else\n                    tb.process(t, InBody); // will get into body\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment()); // into html node\n            } else if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"html\")) {\n                return tb.process(t, InBody);\n            } else if (t.isEndTag() && t.asEndTag().normalName().equals(\"html\")) {\n                if (tb.isFragmentParsing()) {\n                    tb.error(this);\n                    return false;\n                } else {\n                    if (html != null) tb.trackNodePosition(html, false); // track source position of close; html is left on stack, in case of trailers\n                    tb.transition(AfterAfterBody);\n                }\n            } else if (t.isEOF()) {\n                // chillax! we're done\n            } else {\n                tb.error(this);\n                tb.resetBody();\n                return tb.process(t);\n            }\n            return true;\n        }\n    },\n    InFrameset {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter());\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isStartTag()) {\n                Token.StartTag start = t.asStartTag();\n                switch (start.normalName()) {\n                    case \"html\":\n                        return tb.process(start, InBody);\n                    case \"frameset\":\n                        tb.insertElementFor(start);\n                        break;\n                    case \"frame\":\n                        tb.insertEmptyElementFor(start);\n                        break;\n                    case \"noframes\":\n                        return tb.process(start, InHead);\n                    default:\n                        tb.error(this);\n                        return false;\n                }\n            } else if (t.isEndTag() && t.asEndTag().normalName().equals(\"frameset\")) {\n                if (!tb.currentElementIs(\"frameset\")) { // spec checks if el is html; deviate to confirm we are about to pop the frameset el\n                    tb.error(this);\n                    return false;\n                } else {\n                    tb.pop();\n                    if (!tb.isFragmentParsing() && !tb.currentElementIs(\"frameset\")) {\n                        tb.transition(AfterFrameset);\n                    }\n                }\n            } else if (t.isEOF()) {\n                if (!tb.currentElementIs(\"html\")) {\n                    tb.error(this);\n                    return true;\n                }\n            } else {\n                tb.error(this);\n                return false;\n            }\n            return true;\n        }\n    },\n    AfterFrameset {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (isWhitespace(t)) {\n                tb.insertCharacterNode(t.asCharacter());\n            } else if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype()) {\n                tb.error(this);\n                return false;\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"html\")) {\n                return tb.process(t, InBody);\n            } else if (t.isEndTag() && t.asEndTag().normalName().equals(\"html\")) {\n                tb.transition(AfterAfterFrameset);\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"noframes\")) {\n                return tb.process(t, InHead);\n            } else if (t.isEOF()) {\n                // cool your heels, we're complete\n            } else {\n                tb.error(this);\n                return false;\n            }\n            return true;\n        }\n    },\n    AfterAfterBody {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype() || (t.isStartTag() && t.asStartTag().normalName().equals(\"html\"))) {\n                return tb.process(t, InBody);\n            } else if (isWhitespace(t)) {\n                // spec deviation - body and html still on stack, but want this space to go after </html>\n                Element doc = tb.getDocument();\n                tb.insertCharacterToElement(t.asCharacter(), doc);\n            }else if (t.isEOF()) {\n                // nice work chuck\n            } else {\n                tb.error(this);\n                tb.resetBody();\n                return tb.process(t);\n            }\n            return true;\n        }\n    },\n    AfterAfterFrameset {\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            if (t.isComment()) {\n                tb.insertCommentNode(t.asComment());\n            } else if (t.isDoctype() || isWhitespace(t) || (t.isStartTag() && t.asStartTag().normalName().equals(\"html\"))) {\n                return tb.process(t, InBody);\n            } else if (t.isEOF()) {\n                // nice work chuck\n            } else if (t.isStartTag() && t.asStartTag().normalName().equals(\"noframes\")) {\n                return tb.process(t, InHead);\n            } else {\n                tb.error(this);\n                return false;\n            }\n            return true;\n        }\n    },\n    ForeignContent {\n        // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign\n        @Override boolean process(Token t, HtmlTreeBuilder tb) {\n            switch (t.type) {\n                case Character:\n                    Token.Character c = t.asCharacter();\n                    if (HtmlTreeBuilderState.isWhitespace(c))\n                        tb.insertCharacterNode(c);\n                    else {\n                        tb.insertCharacterNode(c, true); // replace nulls\n                        tb.framesetOk(false);\n                    }\n                    break;\n                case Comment:\n                    tb.insertCommentNode(t.asComment());\n                    break;\n                case Doctype:\n                    tb.error(this);\n                    break;\n                case StartTag:\n                    Token.StartTag start = t.asStartTag();\n                    if (StringUtil.in(start.normalName, InForeignToHtml))\n                        return processAsHtml(t, tb);\n                    if (start.normalName.equals(\"font\") && (\n                        start.hasAttributeIgnoreCase(\"color\")\n                            || start.hasAttributeIgnoreCase(\"face\")\n                            || start.hasAttributeIgnoreCase(\"size\")))\n                        return processAsHtml(t, tb);\n\n                    // Any other start:\n                    // (whatwg says to fix up tag name and attribute case per a table - we will preserve original case instead)\n                    String namespace = tb.currentElement().tag().namespace();\n                    tb.insertForeignElementFor(start, namespace);\n                    // (self-closing handled in insert)\n                    // if self-closing svg script -- level and execution elided\n\n                    // seemingly not in spec, but as browser behavior, get into ScriptData state for svg script; and allow custom data tags\n                    TokeniserState textState = tb.tagFor(start.tagName.value(), start.normalName, namespace, tb.settings).textState();\n                    if (textState != null) {\n                        if (start.normalName.equals(\"script\"))\n                            tb.tokeniser.transition(TokeniserState.ScriptData);\n                        else\n                            tb.tokeniser.transition(textState);\n                    }\n\n                    break;\n\n                case EndTag:\n                    Token.EndTag end = t.asEndTag();\n                    if (end.normalName.equals(\"br\") || end.normalName.equals(\"p\"))\n                        return processAsHtml(t, tb);\n                    if (end.normalName.equals(\"script\") && tb.currentElementIs(\"script\", Parser.NamespaceSvg)) {\n                        // script level and execution elided.\n                        tb.pop();\n                        return true;\n                    }\n\n                    // Any other end tag\n                    ArrayList<Element> stack = tb.getStack();\n                    if (stack.isEmpty())\n                        Validate.wtf(\"Stack unexpectedly empty\");\n                    int i = stack.size() - 1;\n                    Element el = stack.get(i);\n                    if (!el.nameIs(end.normalName))\n                        tb.error(this);\n                    while (i != 0) {\n                        if (el.nameIs(end.normalName)) {\n                            tb.popStackToCloseAnyNamespace(el.normalName());\n                            return true;\n                        }\n                        i--;\n                        el = stack.get(i);\n                        if (el.tag().namespace().equals(Parser.NamespaceHtml)) {\n                            return processAsHtml(t, tb);\n                        }\n                    }\n                    break;\n\n                case EOF:\n                    // won't come through here, but for completion:\n                    break;\n                default:\n                    Validate.wtf(\"Unexpected state: \" + t.type); // XmlDecl only in XmlTreeBuilder\n            }\n            return true;\n        }\n\n        boolean processAsHtml(Token t, HtmlTreeBuilder tb) {\n            return tb.state().process(t, tb);\n        }\n    };\n\n    private static void mergeAttributes(Token.StartTag source, Element dest) {\n        if (!source.hasAttributes()) return;\n        for (Attribute attr : source.attributes) { // only iterates public attributes\n            Attributes destAttrs = dest.attributes();\n            if (!destAttrs.hasKey(attr.getKey())) {\n                Range.AttributeRange range = attr.sourceRange(); // need to grab range before its parent changes\n                destAttrs.put(attr);\n                if (source.trackSource) { // copy the attribute range\n                    destAttrs.sourceRange(attr.getKey(), range);\n                }\n            }\n        }\n    }\n\n    private static final String nullString = String.valueOf('\\u0000');\n\n    abstract boolean process(Token t, HtmlTreeBuilder tb);\n\n    private static boolean isWhitespace(Token t) {\n        if (t.isCharacter()) {\n            String data = t.asCharacter().getData();\n            return StringUtil.isBlank(data);\n        }\n        return false;\n    }\n\n    private static void HandleTextState(Token.StartTag startTag, HtmlTreeBuilder tb, @Nullable TokeniserState state) {\n        if (state != null)\n            tb.tokeniser.transition(state);\n        tb.markInsertionMode();\n        tb.transition(Text);\n        tb.insertElementFor(startTag);\n    }\n\n    // lists of tags to search through\n    static final class Constants {\n        static final String[] InHeadEmpty = new String[]{\"base\", \"basefont\", \"bgsound\", \"command\", \"link\"};\n        static final String[] InHeadRaw = new String[]{\"noframes\", \"style\"};\n        static final String[] InHeadEnd = new String[]{\"body\", \"br\", \"html\"};\n        static final String[] AfterHeadBody = new String[]{\"body\", \"br\", \"html\"};\n        static final String[] BeforeHtmlToHead = new String[]{\"body\", \"br\", \"head\", \"html\", };\n        static final String[] InHeadNoScriptHead = new String[]{\"basefont\", \"bgsound\", \"link\", \"meta\", \"noframes\", \"style\"};\n        static final String[] InBodyStartToHead = new String[]{\"base\", \"basefont\", \"bgsound\", \"command\", \"link\", \"meta\", \"noframes\", \"script\", \"style\", \"template\", \"title\"};\n        static final String[] InBodyStartPClosers = new String[]{\"address\", \"article\", \"aside\", \"blockquote\", \"center\", \"details\", \"dir\", \"div\", \"dl\",\n            \"fieldset\", \"figcaption\", \"figure\", \"footer\", \"header\", \"hgroup\", \"menu\", \"nav\", \"ol\",\n            \"p\", \"section\", \"summary\", \"ul\"};\n        static final String[] Headings = new String[]{\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"};\n        static final String[] InBodyStartLiBreakers = new String[]{\"address\", \"div\", \"p\"};\n        static final String[] DdDt = new String[]{\"dd\", \"dt\"};\n        static final String[] InBodyStartApplets = new String[]{\"applet\", \"marquee\", \"object\"};\n        static final String[] InBodyStartMedia = new String[]{\"param\", \"source\", \"track\"};\n        static final String[] InBodyStartInputAttribs = new String[]{\"action\", \"name\", \"prompt\"};\n        static final String[] InBodyStartDrop = new String[]{\"caption\", \"col\", \"colgroup\", \"frame\", \"head\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InBodyEndClosers = new String[]{\"address\", \"article\", \"aside\", \"blockquote\", \"button\", \"center\", \"details\", \"dir\", \"div\",\n            \"dl\", \"fieldset\", \"figcaption\", \"figure\", \"footer\", \"header\", \"hgroup\", \"listing\", \"menu\",\n            \"nav\", \"ol\", \"pre\", \"section\", \"summary\", \"ul\"};\n        static final String[] InBodyEndOtherErrors = new String[] {\"body\", \"dd\", \"dt\", \"html\", \"li\", \"optgroup\", \"option\", \"p\", \"rb\", \"rp\", \"rt\", \"rtc\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InBodyEndAdoptionFormatters = new String[]{\"a\", \"b\", \"big\", \"code\", \"em\", \"font\", \"i\", \"nobr\", \"s\", \"small\", \"strike\", \"strong\", \"tt\", \"u\"};\n        static final String[] InTableToBody = new String[]{\"tbody\", \"tfoot\", \"thead\"};\n        static final String[] InTableAddBody = new String[]{\"td\", \"th\", \"tr\"};\n        static final String[] InTableToHead = new String[]{\"script\", \"style\", \"template\"};\n        static final String[] InCellNames = new String[]{\"td\", \"th\"};\n        static final String[] InCellBody = new String[]{\"body\", \"caption\", \"col\", \"colgroup\", \"html\"};\n        static final String[] InCellTable = new String[]{ \"table\", \"tbody\", \"tfoot\", \"thead\", \"tr\"};\n        static final String[] InCellCol = new String[]{\"caption\", \"col\", \"colgroup\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InTableEndErr = new String[]{\"body\", \"caption\", \"col\", \"colgroup\", \"html\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InTableFoster = new String[]{\"table\", \"tbody\", \"tfoot\", \"thead\", \"tr\"};\n        static final String[] InTableBodyExit = new String[]{\"caption\", \"col\", \"colgroup\", \"tbody\", \"tfoot\", \"thead\"};\n        static final String[] InTableBodyEndIgnore = new String[]{\"body\", \"caption\", \"col\", \"colgroup\", \"html\", \"td\", \"th\", \"tr\"};\n        static final String[] InRowMissing = new String[]{\"caption\", \"col\", \"colgroup\", \"tbody\", \"tfoot\", \"thead\", \"tr\"};\n        static final String[] InRowIgnore = new String[]{\"body\", \"caption\", \"col\", \"colgroup\", \"html\", \"td\", \"th\"};\n        static final String[] InSelectEnd = new String[]{\"input\", \"keygen\", \"textarea\"};\n        static final String[] InSelectTableEnd = new String[]{\"caption\", \"table\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InTableEndIgnore = new String[]{\"tbody\", \"tfoot\", \"thead\"};\n        static final String[] InHeadNoscriptIgnore = new String[]{\"head\", \"noscript\"};\n        static final String[] InCaptionIgnore = new String[]{\"body\", \"col\", \"colgroup\", \"html\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\"};\n        static final String[] InTemplateToHead = new String[] {\"base\", \"basefont\", \"bgsound\", \"link\", \"meta\", \"noframes\", \"script\", \"style\", \"template\", \"title\"};\n        static final String[] InTemplateToTable = new String[] {\"caption\", \"colgroup\", \"tbody\", \"tfoot\", \"thead\"};\n        static final String[] InForeignToHtml = new String[] {\"b\", \"big\", \"blockquote\", \"body\", \"br\", \"center\", \"code\", \"dd\", \"div\", \"dl\", \"dt\", \"em\", \"embed\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"head\", \"hr\", \"i\", \"img\", \"li\", \"listing\", \"menu\", \"meta\", \"nobr\", \"ol\", \"p\", \"pre\", \"ruby\", \"s\", \"small\", \"span\", \"strike\", \"strong\", \"sub\", \"sup\", \"table\", \"tt\", \"u\", \"ul\", \"var\"};\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/ParseError.java",
    "content": "package org.jsoup.parser;\n\n/**\n * A Parse Error records an error in the input HTML that occurs in either the tokenisation or the tree building phase.\n */\npublic class ParseError {\n    private final int pos;\n    private final String cursorPos;\n    private final String errorMsg;\n\n    ParseError(CharacterReader reader, String errorMsg) {\n        pos = reader.pos();\n        cursorPos = reader.posLineCol();\n        this.errorMsg = errorMsg;\n    }\n\n    ParseError(CharacterReader reader, String errorFormat, Object... args) {\n        pos = reader.pos();\n        cursorPos = reader.posLineCol();\n        this.errorMsg = String.format(errorFormat, args);\n    }\n\n    ParseError(int pos, String errorMsg) {\n        this.pos = pos;\n        cursorPos = String.valueOf(pos);\n        this.errorMsg = errorMsg;\n    }\n\n    ParseError(int pos, String errorFormat, Object... args) {\n        this.pos = pos;\n        cursorPos = String.valueOf(pos);\n        this.errorMsg = String.format(errorFormat, args);\n    }\n\n    /**\n     * Retrieve the error message.\n     * @return the error message.\n     */\n    public String getErrorMessage() {\n        return errorMsg;\n    }\n\n    /**\n     * Retrieves the offset of the error.\n     * @return error offset within input\n     */\n    public int getPosition() {\n        return pos;\n    }\n\n    /**\n     Get the formatted line:column cursor position where the error occurred.\n     @return line:number cursor position\n     */\n    public String getCursorPos() {\n        return cursorPos;\n    }\n\n    @Override\n    public String toString() {\n        return \"<\" + cursorPos + \">: \" + errorMsg;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/ParseErrorList.java",
    "content": "package org.jsoup.parser;\n\nimport java.util.ArrayList;\n\n/**\n * A container for ParseErrors.\n * \n * @author Jonathan Hedley\n */\npublic class ParseErrorList extends ArrayList<ParseError>{\n    private static final int INITIAL_CAPACITY = 16;\n    private final int initialCapacity;\n    private final int maxSize;\n    \n    ParseErrorList(int initialCapacity, int maxSize) {\n        super(initialCapacity);\n        this.initialCapacity = initialCapacity;\n        this.maxSize = maxSize;\n    }\n\n    /**\n     Create a new ParseErrorList with the same settings, but no errors in the list\n     @param copy initial and max size details to copy\n     */\n    ParseErrorList(ParseErrorList copy) {\n        this(copy.initialCapacity, copy.maxSize);\n    }\n    \n    boolean canAddError() {\n        return size() < maxSize;\n    }\n\n    int getMaxSize() {\n        return maxSize;\n    }\n\n    public static ParseErrorList noTracking() {\n        return new ParseErrorList(0, 0);\n    }\n    \n    public static ParseErrorList tracking(int maxSize) {\n        return new ParseErrorList(INITIAL_CAPACITY, maxSize);\n    }\n\n    @Override\n    public Object clone() {\n        // all class fields are primitive, so native clone is enough.\n        return super.clone();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/ParseSettings.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.nodes.Attributes;\nimport org.jspecify.annotations.Nullable;\n\nimport static org.jsoup.internal.Normalizer.lowerCase;\nimport static org.jsoup.internal.Normalizer.normalize;\n\n/**\n * Controls parser case settings, to optionally preserve tag and/or attribute name case.\n */\npublic class ParseSettings {\n    /**\n     * HTML default settings: both tag and attribute names are lower-cased during parsing.\n     */\n    public static final ParseSettings htmlDefault;\n    /**\n     * Preserve both tag and attribute case.\n     */\n    public static final ParseSettings preserveCase;\n\n    static {\n        htmlDefault = new ParseSettings(false, false);\n        preserveCase = new ParseSettings(true, true);\n    }\n\n    private final boolean preserveTagCase;\n    private final boolean preserveAttributeCase;\n\n    /**\n     * Returns true if preserving tag name case.\n     */\n    public boolean preserveTagCase() {\n        return preserveTagCase;\n    }\n\n    /**\n     * Returns true if preserving attribute case.\n     */\n    public boolean preserveAttributeCase() {\n        return preserveAttributeCase;\n    }\n\n    /**\n     * Define parse settings.\n     * @param tag preserve tag case?\n     * @param attribute preserve attribute name case?\n     */\n    public ParseSettings(boolean tag, boolean attribute) {\n        preserveTagCase = tag;\n        preserveAttributeCase = attribute;\n    }\n\n    ParseSettings(ParseSettings copy) {\n        this(copy.preserveTagCase, copy.preserveAttributeCase);\n    }\n\n    /**\n     * Normalizes a tag name according to the case preservation setting.\n     */\n    public String normalizeTag(String name) {\n        name = name.trim();\n        if (!preserveTagCase)\n            name = lowerCase(name);\n        return name;\n    }\n\n    /**\n     * Normalizes an attribute according to the case preservation setting.\n     */\n    public String normalizeAttribute(String name) {\n        name = name.trim();\n        if (!preserveAttributeCase)\n            name = lowerCase(name);\n        return name;\n    }\n\n    void normalizeAttributes(Attributes attributes) {\n        if (!preserveAttributeCase) {\n            attributes.normalize();\n        }\n    }\n\n    /** Returns the normal name that a Tag will have (trimmed and lower-cased) */\n    static String normalName(String name) {\n        return normalize(name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/Parser.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.List;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n Parses HTML or XML into a {@link org.jsoup.nodes.Document}. Generally, it is simpler to use one of the parse methods in\n {@link org.jsoup.Jsoup}.\n <p>Note that a given Parser instance object is threadsafe, but not concurrent. (Concurrent parse calls will\n synchronize.) To reuse a Parser configuration in a multithreaded environment, use {@link #newInstance()} to make\n copies.</p>\n */\npublic class Parser implements Cloneable {\n    public static final String NamespaceHtml = \"http://www.w3.org/1999/xhtml\";\n    public static final String NamespaceXml = \"http://www.w3.org/XML/1998/namespace\";\n    public static final String NamespaceMathml = \"http://www.w3.org/1998/Math/MathML\";\n    public static final String NamespaceSvg = \"http://www.w3.org/2000/svg\";\n\n    private final TreeBuilder treeBuilder;\n    private ParseErrorList errors;\n    private ParseSettings settings;\n    private boolean trackPosition = false;\n    private @Nullable TagSet tagSet;\n    private final ReentrantLock lock = new ReentrantLock();\n    private int maxDepth;\n\n    /**\n     * Create a new Parser, using the specified TreeBuilder\n     * @param treeBuilder TreeBuilder to use to parse input into Documents.\n     */\n    public Parser(TreeBuilder treeBuilder) {\n        this.treeBuilder = treeBuilder;\n        settings = treeBuilder.defaultSettings();\n        errors = ParseErrorList.noTracking();\n        maxDepth = treeBuilder.defaultMaxDepth();\n    }\n\n    /**\n     Creates a new Parser as a deep copy of this; including initializing a new TreeBuilder. Allows independent (multi-threaded) use.\n     @return a copied parser\n     */\n    public Parser newInstance() {\n        return new Parser(this);\n    }\n\n    @SuppressWarnings(\"MethodDoesntCallSuperMethod\") // because we use the copy constructor instead\n    @Override\n    public Parser clone() {\n        return new Parser(this);\n    }\n\n    private Parser(Parser copy) {\n        treeBuilder = copy.treeBuilder.newInstance(); // because extended\n        errors = new ParseErrorList(copy.errors); // only copies size, not contents\n        settings = new ParseSettings(copy.settings);\n        trackPosition = copy.trackPosition;\n        maxDepth = copy.maxDepth;\n        tagSet = new TagSet(copy.tagSet());\n    }\n\n    /**\n     Parse the contents of a String.\n\n     @param html HTML to parse\n     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     @return parsed Document\n     */\n    public Document parseInput(String html, String baseUri) {\n        return parseInput(new StringReader(html), baseUri);\n    }\n\n    /**\n     Parse the contents of Reader.\n\n     @param inputHtml HTML to parse\n     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     @return parsed Document\n     @throws java.io.UncheckedIOException if an I/O error occurs in the Reader\n     */\n    public Document parseInput(Reader inputHtml, String baseUri) {\n        try {\n            lock.lock(); // using a lock vs synchronized to support loom threads\n            return treeBuilder.parse(inputHtml, baseUri, this);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    /**\n     Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.\n\n     @param fragment the fragment of HTML to parse\n     @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML).\n     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     @return list of nodes parsed from the input HTML.\n     */\n    public List<Node> parseFragmentInput(String fragment, @Nullable Element context, String baseUri) {\n        return parseFragmentInput(new StringReader(fragment), context, baseUri);\n    }\n\n    /**\n     Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.\n\n     @param fragment the fragment of HTML to parse\n     @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML).\n     @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     @return list of nodes parsed from the input HTML.\n     @throws java.io.UncheckedIOException if an I/O error occurs in the Reader\n     */\n    public List<Node> parseFragmentInput(Reader fragment, @Nullable Element context, String baseUri) {\n        try {\n            lock.lock();\n            return treeBuilder.parseFragment(fragment, context, baseUri, this);\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    // gets & sets\n    /**\n     * Get the TreeBuilder currently in use.\n     * @return current TreeBuilder.\n     */\n    public TreeBuilder getTreeBuilder() {\n        return treeBuilder;\n    }\n\n    /**\n     * Check if parse error tracking is enabled.\n     * @return current track error state.\n     */\n    public boolean isTrackErrors() {\n        return errors.getMaxSize() > 0;\n    }\n\n    /**\n     * Enable or disable parse error tracking for the next parse.\n     * @param maxErrors the maximum number of errors to track. Set to 0 to disable.\n     * @return this, for chaining\n     */\n    public Parser setTrackErrors(int maxErrors) {\n        errors = maxErrors > 0 ? ParseErrorList.tracking(maxErrors) : ParseErrorList.noTracking();\n        return this;\n    }\n\n    /**\n     * Retrieve the parse errors, if any, from the last parse.\n     * @return list of parse errors, up to the size of the maximum errors tracked.\n     * @see #setTrackErrors(int)\n     */\n    public ParseErrorList getErrors() {\n        return errors;\n    }\n\n    /**\n     Test if position tracking is enabled. If it is, Nodes will have a Position to track where in the original input\n     source they were created from. By default, tracking is not enabled.\n     * @return current track position setting\n     */\n    public boolean isTrackPosition() {\n        return trackPosition;\n    }\n\n    /**\n     Enable or disable source position tracking. If enabled, Nodes will have a Position to track where in the original\n     input source they were created from.\n     @param trackPosition position tracking setting; {@code true} to enable\n     @return this Parser, for chaining\n     */\n    public Parser setTrackPosition(boolean trackPosition) {\n        this.trackPosition = trackPosition;\n        return this;\n    }\n\n    /**\n     Update the ParseSettings of this Parser, to control the case sensitivity of tags and attributes.\n     * @param settings the new settings\n     * @return this Parser\n     */\n    public Parser settings(ParseSettings settings) {\n        this.settings = settings;\n        return this;\n    }\n\n    /**\n     Gets the current ParseSettings for this Parser\n     * @return current ParseSettings\n     */\n    public ParseSettings settings() {\n        return settings;\n    }\n\n    /**\n     Set the parser's maximum stack depth (maximum number of open elements). When reached, new open elements will be\n     removed to prevent excessive nesting. Defaults to 512 for the HTML parser, and unlimited for the XML\n     parser.\n\n     @param maxDepth maximum parser depth; must be >= 1\n     @return this Parser, for chaining\n     */\n    public Parser setMaxDepth(int maxDepth) {\n        Validate.isTrue(maxDepth >= 1, \"maxDepth must be >= 1\");\n        this.maxDepth = maxDepth;\n        return this;\n    }\n\n    /**\n     * Get the maximum parser depth (maximum number of open elements).\n     * @return the current max parser depth\n     */\n    public int getMaxDepth() {\n        return maxDepth;\n    }\n\n    /**\n     Set a custom TagSet to use for this Parser. This allows you to define your own tags, and control how they are\n     parsed. For example, you can set a tag to preserve whitespace, or to be treated as a block tag.\n     <p>You can start with the {@link TagSet#Html()} defaults and customize, or a new empty TagSet.</p>\n\n     @param tagSet the TagSet to use. This gets copied, so that changes that the parse makes (tags found in the document will be added) do not clobber the original TagSet.\n     @return this Parser\n     @since 1.20.1\n     */\n    public Parser tagSet(TagSet tagSet) {\n        Validate.notNull(tagSet);\n        this.tagSet = new TagSet(tagSet); // copy it as we are going to mutate it\n        return this;\n    }\n\n    /**\n     Get the current TagSet for this Parser, which will be either this parser's default, or one that you have set.\n     @return the current TagSet. After the parse, this will contain any new tags that were found in the document.\n     @since 1.20.1\n     */\n    public TagSet tagSet() {\n        if (tagSet == null)\n            tagSet = treeBuilder.defaultTagSet();\n        return tagSet;\n    }\n\n    public String defaultNamespace() {\n        return getTreeBuilder().defaultNamespace();\n    }\n\n    // static parse functions below\n    /**\n     * Parse HTML into a Document.\n     *\n     * @param html HTML to parse\n     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     *\n     * @return parsed Document\n     */\n    public static Document parse(String html, String baseUri) {\n        TreeBuilder treeBuilder = new HtmlTreeBuilder();\n        return treeBuilder.parse(new StringReader(html), baseUri, new Parser(treeBuilder));\n    }\n\n    /**\n     * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.\n     *\n     * @param fragmentHtml the fragment of HTML to parse\n     * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This\n     * provides stack context (for implicit element creation).\n     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     *\n     * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.\n     */\n    public static List<Node> parseFragment(String fragmentHtml, Element context, String baseUri) {\n        HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder();\n        return treeBuilder.parseFragment(new StringReader(fragmentHtml), context, baseUri, new Parser(treeBuilder));\n    }\n\n    /**\n     * Parse a fragment of HTML into a list of nodes. The context element, if supplied, supplies parsing context.\n     *\n     * @param fragmentHtml the fragment of HTML to parse\n     * @param context (optional) the element that this HTML fragment is being parsed for (i.e. for inner HTML). This\n     * provides stack context (for implicit element creation).\n     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     * @param errorList list to add errors to\n     *\n     * @return list of nodes parsed from the input HTML. Note that the context element, if supplied, is not modified.\n     */\n    public static List<Node> parseFragment(String fragmentHtml, Element context, String baseUri, ParseErrorList errorList) {\n        HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder();\n        Parser parser = new Parser(treeBuilder);\n        parser.errors = errorList;\n        return treeBuilder.parseFragment(new StringReader(fragmentHtml), context, baseUri, parser);\n    }\n\n    /**\n     * Parse a fragment of XML into a list of nodes.\n     *\n     * @param fragmentXml the fragment of XML to parse\n     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     * @return list of nodes parsed from the input XML.\n     */\n    public static List<Node> parseXmlFragment(String fragmentXml, String baseUri) {\n        XmlTreeBuilder treeBuilder = new XmlTreeBuilder();\n        return treeBuilder.parseFragment(new StringReader(fragmentXml), null, baseUri, new Parser(treeBuilder));\n    }\n\n    /**\n     * Parse a fragment of HTML into the {@code body} of a Document.\n     *\n     * @param bodyHtml fragment of HTML\n     * @param baseUri base URI of document (i.e. original fetch location), for resolving relative URLs.\n     *\n     * @return Document, with empty head, and HTML parsed into body\n     */\n    public static Document parseBodyFragment(String bodyHtml, String baseUri) {\n        Document doc = Document.createShell(baseUri);\n        Element body = doc.body();\n        List<Node> nodeList = parseFragment(bodyHtml, body, baseUri);\n        body.appendChildren(nodeList);\n        return doc;\n    }\n\n    /**\n     Utility method to unescape HTML entities from a string.\n     <p>To track errors while unescaping, use\n     {@link #unescape(String, boolean)} with a Parser instance that has error tracking enabled.</p>\n\n     @param string HTML escaped string\n     @param inAttribute if the string is to be escaped in strict mode (as attributes are)\n     @return an unescaped string\n     @see #unescape(String, boolean)\n     */\n    public static String unescapeEntities(String string, boolean inAttribute) {\n        Validate.notNull(string);\n        if (string.indexOf('&') < 0) return string; // nothing to unescape\n        return Parser.htmlParser().unescape(string, inAttribute);\n    }\n\n    /**\n     Utility method to unescape HTML entities from a string, using this {@code Parser}'s configuration (for example, to\n     collect errors while unescaping).\n\n     @param string HTML escaped string\n     @param inAttribute if the string is to be escaped in strict mode (as attributes are)\n     @return an unescaped string\n     @see #setTrackErrors(int)\n     @see #unescapeEntities(String, boolean)\n     */\n    public String unescape(String string, boolean inAttribute) {\n        Validate.notNull(string);\n        if (string.indexOf('&') < 0) return string; // nothing to unescape\n        this.treeBuilder.initialiseParse(new StringReader(string), \"\", this);\n        Tokeniser tokeniser = new Tokeniser(this.treeBuilder);\n        return tokeniser.unescapeEntities(inAttribute);\n    }\n\n    // builders\n\n    /**\n     * Create a new HTML parser. This parser treats input as HTML5, and enforces the creation of a normalised document,\n     * based on a knowledge of the semantics of the incoming tags.\n     * @return a new HTML parser.\n     */\n    public static Parser htmlParser() {\n        return new Parser(new HtmlTreeBuilder());\n    }\n\n    /**\n     * Create a new XML parser. This parser assumes no knowledge of the incoming tags and does not treat it as HTML,\n     * rather creates a simple tree directly from the input.\n     * @return a new simple XML parser.\n     */\n    public static Parser xmlParser() {\n        return new Parser(new XmlTreeBuilder()).setMaxDepth(Integer.MAX_VALUE);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/StreamParser.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Connection;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.NodeVisitor;\nimport org.jsoup.select.Selector;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.io.UncheckedIOException;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.NoSuchElementException;\nimport java.util.Queue;\nimport java.util.Spliterator;\nimport java.util.Spliterators;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\n/**\n A StreamParser provides a progressive parse of its input. As each Element is completed, it is emitted via a Stream or\n Iterator interface. Elements returned will be complete with all their children, and an (empty) next sibling, if\n applicable.\n <p>To conserve memory, you can {@link Node#remove() remove()} Elements (or their children) from the DOM during the\n parse. This provides a mechanism to parse an input document that would otherwise be too large to fit into memory, yet\n still providing a DOM interface to the document and its elements.</p>\n <p>\n Additionally, the parser provides a {@link #selectFirst(String query)} / {@link #selectNext(String query)}, which will\n run the parser until a hit is found, at which point the parse is suspended. It can be resumed via another\n {@code select()} call, or via the {@link #stream()} or {@link #iterator()} methods.\n </p>\n <p>Once the input has been fully read, the input Reader will be closed. Or, if the whole document does not need to be\n read, call {@link #stop()} and {@link #close()}.</p>\n <p>The {@link #document()} method will return the Document being parsed into, which will be only partially complete\n until the input is fully consumed.</p>\n <p>A StreamParser can be reused via a new {@link #parse(Reader, String)}, but is not thread-safe for concurrent inputs.\n New parsers should be used in each thread.</p>\n <p>If created via {@link Connection.Response#streamParser()}, or another Reader that is I/O backed, the iterator and\n stream consumers will throw an {@link java.io.UncheckedIOException} if the underlying Reader errors during read.</p>\n <p>For examples, see the jsoup\n <a href=\"https://jsoup.org/cookbook/input/streamparser-dom-sax\">StreamParser cookbook.</a></p>\n <p>\n Selectors that depend on knowing all siblings (e.g. {@code :last-child}, {@code :last-of-type}, {@code :nth-last-child},\n {@code :only-child} and their negations) cannot be correctly evaluated while streaming, because the parser does not know\n if a later sibling will appear. For those cases, run {@link #complete()} first to finish the parse (which is effectively\n the same as using {@code Jsoup.parse(...)} unless you have already removed nodes during streaming).\n </p>\n @since 1.18.1 */\npublic class StreamParser implements Closeable {\n    final private Parser parser;\n    final private TreeBuilder treeBuilder;\n    final private ElementIterator it = new ElementIterator();\n    @Nullable private Document document;\n    private boolean stopped = false;\n\n    /**\n     Construct a new StreamParser, using the supplied base Parser.\n     @param parser the configured base parser\n     */\n    public StreamParser(Parser parser) {\n        this.parser = parser;\n        treeBuilder = parser.getTreeBuilder();\n        treeBuilder.nodeListener(it);\n    }\n\n    /**\n     Provide the input for a Document parse. The input is not read until a consuming operation is called.\n     @param input the input to be read.\n     @param baseUri the URL of this input, for absolute link resolution\n     @return this parser, for chaining\n     */\n    public StreamParser parse(Reader input, String baseUri) {\n        close(); // probably a no-op, but ensures any previous reader is closed\n        it.reset();\n        treeBuilder.initialiseParse(input, baseUri, parser); // reader is not read, so no chance of IO error\n        document = treeBuilder.doc;\n        return this;\n    }\n\n    /**\n     Provide the input for a Document parse. The input is not read until a consuming operation is called.\n     @param input the input to be read\n     @param baseUri the URL of this input, for absolute link resolution\n     @return this parser\n     */\n    public StreamParser parse(String input, String baseUri) {\n        return parse(new StringReader(input), baseUri);\n    }\n\n    /**\n     Provide the input for a fragment parse. The input is not read until a consuming operation is called.\n     @param input the input to be read\n     @param context the optional fragment context element\n     @param baseUri the URL of this input, for absolute link resolution\n     @return this parser\n     @see #completeFragment()\n     */\n    public StreamParser parseFragment(Reader input, @Nullable Element context, String baseUri) {\n        parse(input, baseUri);\n        treeBuilder.initialiseParseFragment(context);\n        return this;\n    }\n\n    /**\n     Provide the input for a fragment parse. The input is not read until a consuming operation is called.\n     @param input the input to be read\n     @param context the optional fragment context element\n     @param baseUri the URL of this input, for absolute link resolution\n     @return this parser\n     @see #completeFragment()\n     */\n    public StreamParser parseFragment(String input, @Nullable Element context, String baseUri) {\n        return parseFragment(new StringReader(input), context, baseUri);\n    }\n\n    /**\n     Creates a {@link Stream} of {@link Element}s, with the input being parsed as each element is consumed. Each\n     Element returned will be complete (that is, all of its children will be included, and if it has a next sibling, that\n     (empty) sibling will exist at {@link Element#nextElementSibling()}). The stream will be emitted in document order as\n     each element is closed. That means that child elements will be returned prior to their parents.\n     <p>The stream will start from the current position of the backing iterator and the parse.</p>\n     <p>When consuming the stream, if the Reader that the Parser is reading throws an I/O exception (for example a\n     SocketTimeoutException), that will be emitted as an {@link UncheckedIOException}</p>\n     @return a stream of Element objects\n     @throws UncheckedIOException if the underlying Reader excepts during a read (in stream consuming methods)\n     */\n    public Stream<Element> stream() {\n        return StreamSupport.stream(\n            Spliterators.spliteratorUnknownSize(\n                it, Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED),\n            false);\n    }\n\n    /**\n     Returns an {@link Iterator} of {@link Element}s, with the input being parsed as each element is consumed. Each\n     Element returned will be complete (that is, all of its children will be included, and if it has a next sibling, that\n     (empty) sibling will exist at {@link Element#nextElementSibling()}). The elements will be emitted in document order as\n     each element is closed. That means that child elements will be returned prior to their parents.\n     <p>The iterator will start from the current position of the parse.</p>\n     <p>The iterator is backed by this StreamParser, and the resources it holds.</p>\n     @return a stream of Element objects\n     */\n    public Iterator<Element> iterator() {\n        //noinspection ReturnOfInnerClass\n        return it;\n    }\n\n    /**\n     Flags that the parse should be stopped; the backing iterator will not return any more Elements.\n     @return this parser\n     */\n    public StreamParser stop() {\n        stopped = true;\n        return this;\n    }\n\n    /**\n     Closes the input and releases resources including the underlying parser and reader.\n     <p>The parser will also be closed when the input is fully read.</p>\n     <p>The parser can be reused with another call to {@link #parse(Reader, String)}.</p>\n     */\n    @Override public void close() {\n        treeBuilder.completeParse(); // closes the reader, frees resources\n    }\n\n    /**\n     Get the current {@link Document} as it is being parsed. It will be only partially complete until the input is fully\n     read. Structural changes (e.g. insert, remove) may be made to the Document contents.\n     @return the (partial) Document\n     */\n    public Document document() {\n        document = treeBuilder.doc;\n        Validate.notNull(document, \"Must run parse() before calling.\");\n        return document;\n    }\n\n    /**\n     Runs the parser until the input is fully read, and returns the completed Document.\n     @return the completed Document\n     @throws IOException if an I/O error occurs\n     */\n    public Document complete() throws IOException {\n        Document doc = document();\n        treeBuilder.runParser();\n        return doc;\n    }\n\n    /**\n     When initialized as a fragment parse, runs the parser until the input is fully read, and returns the completed\n     fragment child nodes.\n     @return the completed child nodes\n     @throws IOException if an I/O error occurs\n     @see #parseFragment(Reader, Element, String)\n     */\n    public List<Node> completeFragment() throws IOException {\n        treeBuilder.runParser();\n        return treeBuilder.completeParseFragment();\n    }\n\n    /**\n     Finds the first Element that matches the provided query. If the parsed Document does not already have a match, the\n     input will be parsed until the first match is found, or the input is completely read.\n     @param query the {@link org.jsoup.select.Selector} query.\n     @return the first matching {@link Element}, or {@code null} if there's no match\n     @throws IOException if an I/O error occurs\n     @see #selectFirst(Evaluator)\n     */\n    public @Nullable Element selectFirst(String query) throws IOException {\n        return selectFirst(Selector.evaluatorOf(query));\n    }\n\n    /**\n     Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}. This\n     is useful if you want to simply abort processing on a failed match.\n     @param query the {@link org.jsoup.select.Selector} query.\n     @return the first matching element\n     @throws IllegalArgumentException if no match is found\n     @throws IOException if an I/O error occurs\n     */\n    public Element expectFirst(String query) throws IOException {\n        return Validate.expectNotNull(\n            selectFirst(query),\n            \"No elements matched the query '%s' in the document.\"\n            , query\n        );\n    }\n\n    /**\n     Finds the first Element that matches the provided query. If the parsed Document does not already have a match, the\n     input will be parsed until the first match is found, or the input is completely read.\n     <p>By providing a compiled evaluator vs a CSS selector, this method may be more efficient when executing the same\n     query against multiple documents.</p>\n     @param eval the {@link org.jsoup.select.Selector} evaluator.\n     @return the first matching {@link Element}, or {@code null} if there's no match\n     @throws IOException if an I/O error occurs\n     @see Selector#evaluatorOf(String css)\n     */\n    public @Nullable Element selectFirst(Evaluator eval) throws IOException {\n        final Document doc = document();\n\n        // run the query on the existing (partial) doc first, as there may be a hit already parsed\n        Element first = doc.selectFirst(eval);\n        if (first != null) return first;\n\n        return selectNext(eval);\n    }\n\n    /**\n     Finds the next Element that matches the provided query. The input will be parsed until the next match is found, or\n     the input is completely read.\n     @param query the {@link org.jsoup.select.Selector} query.\n     @return the next matching {@link Element}, or {@code null} if there's no match\n     @throws IOException if an I/O error occurs\n     @see #selectNext(Evaluator)\n     */\n    public @Nullable Element selectNext(String query) throws IOException {\n        return selectNext(Selector.evaluatorOf(query));\n    }\n\n    /**\n     Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}. This\n     is useful if you want to simply abort processing on a failed match.\n     @param query the {@link org.jsoup.select.Selector} query.\n     @return the first matching element\n     @throws IllegalArgumentException if no match is found\n     @throws IOException if an I/O error occurs\n     */\n    public Element expectNext(String query) throws IOException {\n        return Validate.expectNotNull(\n            selectNext(query),\n            \"No elements matched the query '%s' in the document.\"\n            , query\n        );\n    }\n\n    /**\n     Finds the next Element that matches the provided query. The input will be parsed until the next match is found, or\n     the input is completely read.\n     <p>By providing a compiled evaluator vs a CSS selector, this method may be more efficient when executing the same\n     query against multiple documents.</p>\n     @param eval the {@link org.jsoup.select.Selector} evaluator.\n     @return the next matching {@link Element}, or {@code null} if there's no match\n     @throws IOException if an I/O error occurs\n     @see Selector#evaluatorOf(String css)\n     */\n    public @Nullable Element selectNext(Evaluator eval) throws IOException {\n        try {\n            final Document doc = document(); // validates the parse was initialized, keeps stack trace out of stream\n            return stream()\n                .filter(eval.asPredicate(doc))\n                .findFirst()\n                .orElse(null);\n        } catch (UncheckedIOException e) {\n            // Reader threw an IO exception emitted via Iterator's next()\n            throw e.getCause();\n        }\n    }\n\n    final class ElementIterator implements Iterator<Element>, NodeVisitor {\n        // listeners add to a next emit queue, as a single token read step may yield multiple elements\n        final private Queue<Element> emitQueue = new LinkedList<>();\n        private @Nullable Element current;  // most recently emitted\n        private @Nullable Element next;     // element waiting to be picked up\n        private @Nullable Element tail;     // The last tailed element (</html>), on hold for final pop\n\n        void reset() {\n            emitQueue.clear();\n            current = next = tail = null;\n            stopped = false;\n        }\n\n        // Iterator Interface:\n        /**\n         {@inheritDoc}\n         @throws UncheckedIOException if the underlying Reader errors during a read\n         */\n        @Override public boolean hasNext() {\n            maybeFindNext();\n            return next != null;\n        }\n\n        /**\n         {@inheritDoc}\n         @throws UncheckedIOException if the underlying Reader errors during a read\n         */\n        @Override public Element next() {\n            maybeFindNext();\n            if (next == null) throw new NoSuchElementException();\n            current = next;\n            next = null;\n            return current;\n        }\n\n        private void maybeFindNext() {\n            if (stopped || next != null) return;\n\n            // drain the current queue before stepping to get more\n            if (!emitQueue.isEmpty()) {\n                next = emitQueue.remove();\n                return;\n            }\n\n            // step the parser, which will hit the node listeners to add to the queue:\n            while (treeBuilder.stepParser()) {\n                if (!emitQueue.isEmpty()) {\n                    next = emitQueue.remove();\n                    return;\n                }\n            }\n            stop();\n            close();\n\n            // send the final element out:\n            if (tail != null) {\n                next = tail;\n                tail = null;\n            }\n        }\n\n        @Override public void remove() {\n            if (current == null) throw new NoSuchElementException();\n            current.remove();\n        }\n\n        // NodeVisitor Interface:\n        @Override public void head(Node node, int depth) {\n            if (node instanceof Element) {\n                Element prev = node.previousElementSibling();\n                // We prefer to wait until an element has a next sibling before emitting it; otherwise, get it in tail\n                if (prev != null) emitQueue.add(prev);\n            }\n        }\n\n        @Override public void tail(Node node, int depth) {\n            if (node instanceof Element) {\n                tail = (Element) node; // kept for final hit\n                Element lastChild = tail.lastElementChild(); // won't get a nextsib, so emit that:\n                if (lastChild != null) emitQueue.add(lastChild);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/Tag.java",
    "content": "package org.jsoup.parser;\n\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.Objects;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\n\n/**\n A Tag represents an Element's name and configured options, common throughout the Document. Options may affect the parse\n and output.\n\n @see TagSet\n @see Parser#tagSet(TagSet) */\npublic class Tag implements Cloneable {\n    /** Tag option: the tag is known (specifically defined). This impacts if options may need to be inferred (when not\n     known) in, e.g., the pretty-printer. Set when a tag is added to a TagSet, or when settings are set(). */\n    public static int Known = 1;\n    /** Tag option: the tag is a void tag (e.g., {@code <img>}), that can contain no children, and in HTML does not require closing. */\n    public static int Void = 1 << 1;\n    /** Tag option: the tag is a block tag (e.g., {@code <div>}, {@code <p>}). Causes the element to be indented when pretty-printing. If not a block, it is inline. */\n    public static int Block = 1 << 2;\n    /** Tag option: the tag is a block tag that will only hold inline tags (e.g., {@code <p>}); used for formatting. (Must also set Block.) */\n    public static int InlineContainer = 1 << 3;\n    /** Tag option: the tag can self-close (e.g., {@code <foo />}). */\n    public static int SelfClose = 1 << 4;\n    /** Tag option: the tag has been seen self-closing in this parse. */\n    public static int SeenSelfClose = 1 << 5;\n    /** Tag option: the tag preserves whitespace (e.g., {@code <pre>}). */\n    public static int PreserveWhitespace = 1 << 6;\n    /** Tag option: the tag is an RCDATA element that can have text and character references (e.g., {@code <title>}, {@code <textarea>}). */\n    public static int RcData = 1 << 7;\n    /** Tag option: the tag is a Data element that can have text but not character references (e.g., {@code <style>}, {@code <script>}). */\n    public static int Data = 1 << 8;\n    /** Tag option: the tag's value will be included when submitting a form (e.g., {@code <input>}). */\n    public static int FormSubmittable = 1 << 9;\n\n    String namespace;\n    String tagName;\n    String normalName; // always the lower case version of this tag, regardless of case preservation mode\n    int options = 0;\n\n    /**\n     Create a new Tag, with the given name and namespace.\n     <p>The tag is not implicitly added to any TagSet.</p>\n     @param tagName the name of the tag. Case-sensitive.\n     @param namespace the namespace for the tag.\n     @see TagSet#valueOf(String, String)\n     @since 1.20.1\n     */\n    public Tag(String tagName, String namespace) {\n        this(tagName, ParseSettings.normalName(tagName), namespace);\n    }\n\n    /**\n     Create a new Tag, with the given name, in the HTML namespace.\n     <p>The tag is not implicitly added to any TagSet.</p>\n     @param tagName the name of the tag. Case-sensitive.\n     @see TagSet#valueOf(String, String)\n     @since 1.20.1\n     */\n    public Tag(String tagName) {\n        this(tagName, ParseSettings.normalName(tagName), NamespaceHtml);\n    }\n\n    /** Path for TagSet defaults, no options set; normal name is already LC. */\n    Tag(String tagName, String normalName, String namespace) {\n        this.tagName = tagName;\n        this.normalName = normalName;\n        this.namespace = namespace;\n    }\n\n    /**\n     * Get this tag's name.\n     *\n     * @return the tag's name\n     */\n    public String getName() {\n        return tagName;\n    }\n\n    /**\n     Get this tag's name.\n     @return the tag's name\n     */\n    public String name() {\n        return tagName;\n    }\n\n    /**\n     Change the tag's name. As Tags are reused throughout a Document, this will change the name for all uses of this tag.\n     @param tagName the new name of the tag. Case-sensitive.\n     @return this tag\n     @since 1.20.1\n     */\n    public Tag name(String tagName) {\n        this.tagName = tagName;\n        this.normalName = ParseSettings.normalName(tagName);\n        return this;\n    }\n\n    /**\n     Get this tag's prefix, if it has one; else the empty string.\n     <p>For example, {@code <book:title>} has prefix {@code book}, and tag name {@code book:title}.</p>\n     @return the tag's prefix\n     @since 1.20.1\n     */\n    public String prefix() {\n        int pos = tagName.indexOf(':');\n        if (pos == -1) return \"\";\n        else return tagName.substring(0, pos);\n    }\n\n    /**\n     Get this tag's local name. The local name is the name without the prefix (if any).\n     <p>For exmaple, {@code <book:title>} has local name {@code title}, and tag name {@code book:title}.</p>\n     @return the tag's local name\n     @since 1.20.1\n     */\n    public String localName() {\n        int pos = tagName.indexOf(':');\n        if (pos == -1) return tagName;\n        else return tagName.substring(pos + 1);\n    }\n\n    /**\n     * Get this tag's normalized (lowercased) name.\n     * @return the tag's normal name.\n     */\n    public String normalName() {\n        return normalName;\n    }\n\n    /**\n     Get this tag's namespace.\n     @return the tag's namespace\n     */\n    public String namespace() {\n        return namespace;\n    }\n\n    /**\n     Set the tag's namespace. As Tags are reused throughout a Document, this will change the namespace for all uses of this tag.\n     @param namespace the new namespace of the tag.\n     @return this tag\n     @since 1.20.1\n     */\n    public Tag namespace(String namespace) {\n        this.namespace = namespace;\n        return this;\n    }\n\n    /**\n     Set an option on this tag.\n     <p>Once a tag has a setting applied, it will be considered a known tag.</p>\n     @param option the option to set\n     @return this tag\n     @since 1.20.1\n     */\n    public Tag set(int option) {\n        options |= option;\n        options |= Tag.Known; // considered known if touched\n        return this;\n    }\n\n    /**\n     Test if an option is set on this tag.\n\n     @param option the option to test\n     @return true if the option is set\n     @since 1.20.1\n     */\n    public boolean is(int option) {\n        return (options & option) != 0;\n    }\n\n    /**\n     Clear (unset) an option from this tag.\n     @param option the option to clear\n     @return this tag\n     @since 1.20.1\n     */\n    public Tag clear(int option) {\n        options &= ~option;\n        // considered known if touched, unless explicitly clearing known\n        if (option != Tag.Known) options |= Tag.Known;\n        return this;\n    }\n\n    /**\n     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.\n     * <p>\n     * Pre-defined tags (p, div etc) will be ==, but unknown tags are not registered and will only .equals().\n     * </p>\n     * \n     * @param tagName Name of tag, e.g. \"p\". Case-insensitive.\n     * @param namespace the namespace for the tag.\n     * @param settings used to control tag name sensitivity\n     * @see TagSet\n     * @return The tag, either defined or new generic.\n     */\n    public static Tag valueOf(String tagName, String namespace, ParseSettings settings) {\n        return TagSet.Html().valueOf(tagName, null, namespace, settings.preserveTagCase());\n    }\n\n    /**\n     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.\n     * <p>\n     * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().\n     * </p>\n     *\n     * @param tagName Name of tag, e.g. \"p\". <b>Case sensitive</b>.\n     * @return The tag, either defined or new generic.\n     * @see #valueOf(String tagName, String namespace, ParseSettings settings)\n     */\n    public static Tag valueOf(String tagName) {\n        return valueOf(tagName, NamespaceHtml, ParseSettings.preserveCase);\n    }\n\n    /**\n     * Get a Tag by name. If not previously defined (unknown), returns a new generic tag, that can do anything.\n     * <p>\n     * Pre-defined tags (P, DIV etc) will be ==, but unknown tags are not registered and will only .equals().\n     * </p>\n     *\n     * @param tagName Name of tag, e.g. \"p\". <b>Case sensitive</b>.\n     * @param settings used to control tag name sensitivity\n     * @return The tag, either defined or new generic.\n     * @see #valueOf(String tagName, String namespace, ParseSettings settings)\n     */\n    public static Tag valueOf(String tagName, ParseSettings settings) {\n        return valueOf(tagName, NamespaceHtml, settings);\n    }\n\n    /**\n     * Gets if this is a block tag.\n     *\n     * @return if block tag\n     */\n    public boolean isBlock() {\n        return (options & Block) != 0;\n    }\n\n    /**\n     Get if this is an InlineContainer tag.\n\n     @return true if an InlineContainer (which formats children as inline).\n     @deprecated internal pretty-printing flag; use {@link #isInline()} or {@link #isBlock()} to check layout intent. Will be removed in jsoup 1.24.1.\n     */\n    @Deprecated public boolean formatAsBlock() {\n        return (options & InlineContainer) != 0;\n    }\n\n    /**\n     * Gets if this tag is an inline tag. Just the opposite of isBlock.\n     *\n     * @return if this tag is an inline tag.\n     */\n    public boolean isInline() {\n        return (options & Block) == 0;\n    }\n\n    /**\n     Get if this is void (aka empty) tag.\n\n     @return true if this is a void tag\n     */\n    public boolean isEmpty() {\n        return (options & Void) != 0;\n    }\n\n    /**\n     * Get if this tag is self-closing.\n     *\n     * @return if this tag should be output as self-closing.\n     */\n    public boolean isSelfClosing() {\n        return (options & SelfClose) != 0 || (options & Void) != 0;\n    }\n\n    /**\n     * Get if this is a pre-defined tag in the TagSet, or was auto created on parsing.\n     *\n     * @return if a known tag\n     */\n    public boolean isKnownTag() {\n        return (options & Known) != 0;\n    }\n\n    /**\n     * Check if this tag name is a known HTML tag.\n     *\n     * @param tagName name of tag\n     * @return if known HTML tag\n     */\n    public static boolean isKnownTag(String tagName) {\n        return TagSet.HtmlTagSet.get(tagName, NamespaceHtml) != null;\n    }\n\n    /**\n     * Get if this tag should preserve whitespace within child text nodes.\n     *\n     * @return if preserve whitespace\n     */\n    public boolean preserveWhitespace() {\n        return (options & PreserveWhitespace) != 0;\n    }\n\n    /**\n     * Get if this tag represents an element that should be submitted with a form. E.g. input, option\n     * @return if submittable with a form\n     */\n    public boolean isFormSubmittable() {\n        return (options & FormSubmittable) != 0;\n    }\n\n    void setSeenSelfClose() {\n        options |= Tag.SeenSelfClose; // does not change known status\n    }\n\n    /**\n     If this Tag uses a specific text TokeniserState for its content, returns that; otherwise null.\n     */\n    @Nullable TokeniserState textState() {\n        if (is(RcData)) return TokeniserState.Rcdata;\n        if (is(Data))   return TokeniserState.Rawtext;\n        else            return null;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (!(o instanceof Tag)) return false;\n        Tag tag = (Tag) o;\n        return Objects.equals(tagName, tag.tagName) &&\n            Objects.equals(namespace, tag.namespace) &&\n            Objects.equals(normalName, tag.normalName) &&\n            options == tag.options;\n    }\n\n    /**\n     Hashcode of this Tag, consisting of the tag name and namespace.\n     */\n    @Override\n    public int hashCode() {\n        return Objects.hash(tagName, namespace); // options not included so that mutations do not prevent use as a key\n    }\n\n    @Override\n    public String toString() {\n        return tagName;\n    }\n\n    @Override\n    protected Tag clone() {\n        try {\n            return (Tag) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/TagSet.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.SharedConstants;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.jsoup.parser.Parser.NamespaceMathml;\nimport static org.jsoup.parser.Parser.NamespaceSvg;\n\n/**\n A TagSet controls the {@link Tag} configuration for a Document's parse, and its serialization. It contains the initial\n defaults, and after the parse, any additionally discovered tags.\n\n @see Parser#tagSet(TagSet)\n @since 1.20.1\n */\npublic class TagSet {\n    static final TagSet HtmlTagSet = initHtmlDefault();\n\n    private final Map<String, Map<String, Tag>> tags = new HashMap<>(); // namespace -> tag name -> Tag\n    private final @Nullable TagSet source; // internal fallback for lazy tag copies\n    private @Nullable ArrayList<Consumer<Tag>> customizers; // optional onNewTag tag customizer\n\n    /**\n     Returns a mutable copy of the default HTML tag set.\n     */\n    public static TagSet Html() {\n        return new TagSet(HtmlTagSet, null);\n    }\n\n    private TagSet(@Nullable TagSet source, @Nullable ArrayList<Consumer<Tag>> customizers) {\n        this.source = source;\n        this.customizers = customizers;\n    }\n\n    public TagSet() {\n        this(null, null);\n    }\n\n    /**\n     Creates a new TagSet by copying the current tags and customizers from the provided source TagSet. Changes made to\n     one TagSet will not affect the other.\n     @param template the TagSet to copy\n     */\n    public TagSet(TagSet template) {\n        this(template.source, copyCustomizers(template));\n        // copy tags eagerly; any lazy pull-through should come only from the root source (which would be the HTML defaults), not the template itself.\n        // that way the template tagset is not mutated when we do read through\n        if (template.tags.isEmpty()) return;\n\n        for (Map.Entry<String, Map<String, Tag>> namespaceEntry : template.tags.entrySet()) {\n            Map<String, Tag> nsTags = new HashMap<>(namespaceEntry.getValue().size());\n            for (Map.Entry<String, Tag> tagEntry : namespaceEntry.getValue().entrySet()) {\n                nsTags.put(tagEntry.getKey(), tagEntry.getValue().clone());\n            }\n            tags.put(namespaceEntry.getKey(), nsTags);\n        }\n    }\n\n    private static @Nullable ArrayList<Consumer<Tag>> copyCustomizers(TagSet base) {\n        if (base.customizers == null) return null;\n        return new ArrayList<>(base.customizers);\n    }\n\n    /**\n     Insert a tag into this TagSet. If the tag already exists, it is replaced.\n     <p>Tags explicitly added like this are considered to be known tags (vs those that are dynamically created via\n     .valueOf() if not already in the set.</p>\n\n     @param tag the tag to add\n     @return this TagSet\n     */\n    public TagSet add(Tag tag) {\n        tag.set(Tag.Known);\n        doAdd(tag);\n        return this;\n    }\n\n    /** Adds the tag, but does not set defined. Used in .valueOf */\n    private void doAdd(Tag tag) {\n        if (customizers != null) {\n            for (Consumer<Tag> customizer : customizers) {\n                customizer.accept(tag);\n            }\n        }\n\n        tags.computeIfAbsent(tag.namespace, ns -> new HashMap<>())\n            .put(tag.tagName, tag);\n    }\n\n    /**\n     Get an existing Tag from this TagSet by tagName and namespace. The tag name is not normalized, to support mixed\n     instances.\n\n     @param tagName the case-sensitive tag name\n     @param namespace the namespace\n     @return the tag, or null if not found\n     */\n    public @Nullable Tag get(String tagName, String namespace) {\n        Validate.notNull(tagName);\n        Validate.notNull(namespace);\n\n        // get from our tags\n        Map<String, Tag> nsTags = tags.get(namespace);\n        if (nsTags != null) {\n            Tag tag = nsTags.get(tagName);\n            if (tag != null) {\n                return tag;\n            }\n        }\n\n        // not found; clone on demand from source if exists\n        if (source != null) {\n            Tag tag = source.get(tagName, namespace);\n            if (tag != null) {\n                Tag copy = tag.clone();\n                doAdd(copy);\n                return copy;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     Tag.valueOf with the normalName via the token.normalName, to save redundant lower-casing passes.\n     Provide a null normalName unless we already have one; will be normalized if required from tagName.\n     */\n    Tag valueOf(String tagName, @Nullable String normalName, String namespace, boolean preserveTagCase) {\n        Validate.notNull(tagName);\n        Validate.notNull(namespace);\n        tagName = tagName.trim();\n        Validate.notEmpty(tagName);\n        Tag tag = get(tagName, namespace);\n        if (tag != null) return tag;\n\n        // not found by tagName, try by normal\n        if (normalName == null) normalName = ParseSettings.normalName(tagName);\n        tagName = preserveTagCase ? tagName : normalName;\n        tag = get(normalName, namespace);\n        if (tag != null) {\n            if (preserveTagCase && !tagName.equals(normalName)) {\n                tag = tag.clone(); // copy so that the name update doesn't reset all instances\n                tag.tagName = tagName;\n                doAdd(tag);\n            }\n            return tag;\n        }\n\n        // not defined: return a new one\n        tag = new Tag(tagName, normalName, namespace);\n        doAdd(tag);\n\n        return tag;\n    }\n\n    /**\n     Get a Tag by name from this TagSet. If not previously defined (unknown), returns a new tag.\n     <p>New tags will be added to this TagSet.</p>\n\n     @param tagName Name of tag, e.g. \"p\".\n     @param namespace the namespace for the tag.\n     @param settings used to control tag name sensitivity\n     @return The tag, either defined or new generic.\n     */\n    public Tag valueOf(String tagName, String namespace, ParseSettings settings) {\n        return valueOf(tagName, null, namespace, settings.preserveTagCase());\n    }\n\n    /**\n     Get a Tag by name from this TagSet. If not previously defined (unknown), returns a new tag.\n     <p>New tags will be added to this TagSet.</p>\n\n     @param tagName Name of tag, e.g. \"p\". <b>Case-sensitive</b>.\n     @param namespace the namespace for the tag.\n     @return The tag, either defined or new generic.\n     @see #valueOf(String tagName, String namespace, ParseSettings settings)\n     */\n    public Tag valueOf(String tagName, String namespace) {\n        return valueOf(tagName, namespace, ParseSettings.preserveCase);\n    }\n\n    /**\n     Register a callback to customize each {@link Tag} as it's added to this TagSet.\n     <p>Customizers are invoked once per Tag, when they are added (explicitly or via the valueOf methods).</p>\n\n     <p>For example, to allow all unknown tags to be self-closing during when parsing as HTML:</p>\n     <pre><code>\n     Parser parser = Parser.htmlParser();\n     parser.tagSet().onNewTag(tag -> {\n     if (!tag.isKnownTag())\n        tag.set(Tag.SelfClose);\n     });\n\n     Document doc = Jsoup.parse(html, parser);\n     </code></pre>\n\n     @param customizer a {@code Consumer<Tag>} that will be called for each newly added or cloned Tag; callers can\n     inspect and modify the Tag's state (e.g. set options)\n     @return this TagSet, to allow method chaining\n     @since 1.21.0\n     */\n    public TagSet onNewTag(Consumer<Tag> customizer) {\n        Validate.notNull(customizer);\n        if (customizers == null)\n            customizers = new ArrayList<>();\n        customizers.add(customizer);\n        return this;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof TagSet)) return false;\n        TagSet tagSet = (TagSet) o;\n        return Objects.equals(tags, tagSet.tags);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hashCode(tags);\n    }\n\n    // Default HTML initialization\n\n    /**\n     Initialize the default HTML tag set.\n     */\n    static TagSet initHtmlDefault() {\n        String[] blockTags = {\n            \"html\", \"head\", \"body\", \"frameset\", \"script\", \"noscript\", \"style\", \"meta\", \"link\", \"title\", \"frame\",\n            \"noframes\", \"section\", \"nav\", \"aside\", \"hgroup\", \"header\", \"footer\", \"p\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\",\n            \"h6\", \"button\",\n            \"ul\", \"ol\", \"pre\", \"div\", \"blockquote\", \"hr\", \"address\", \"figure\", \"figcaption\", \"form\", \"fieldset\", \"ins\",\n            \"del\", \"dl\", \"dt\", \"dd\", \"li\", \"table\", \"caption\", \"thead\", \"tfoot\", \"tbody\", \"colgroup\", \"col\", \"tr\", \"th\",\n            \"td\", \"video\", \"audio\", \"canvas\", \"details\", \"menu\", \"plaintext\", \"template\", \"article\", \"main\",\n            \"center\", \"template\",\n            \"dir\", \"applet\", \"marquee\", \"listing\", // deprecated but still known / special handling\n            \"#root\" // the outer Document\n        };\n        String[] inlineTags = {\n            \"object\", \"base\", \"font\", \"tt\", \"i\", \"b\", \"u\", \"big\", \"small\", \"em\", \"strong\", \"dfn\", \"code\", \"samp\", \"kbd\",\n            \"var\", \"cite\", \"abbr\", \"time\", \"acronym\", \"mark\", \"ruby\", \"rt\", \"rp\", \"rtc\", \"a\", \"img\", \"wbr\", \"map\",\n            \"q\",\n            \"sub\", \"sup\", \"bdo\", \"iframe\", \"embed\", \"span\", \"input\", \"select\", \"textarea\", \"label\", \"optgroup\",\n            \"option\", \"legend\", \"datalist\", \"keygen\", \"output\", \"progress\", \"meter\", \"area\", \"param\", \"source\", \"track\",\n            \"summary\", \"command\", \"device\", \"area\", \"basefont\", \"bgsound\", \"menuitem\", \"param\", \"source\", \"track\",\n            \"data\", \"bdi\", \"s\", \"strike\", \"nobr\",\n            \"rb\", // deprecated but still known / special handling\n        };\n        String[] inlineContainers = { // can only contain inline; aka phrasing content\n            \"title\", \"a\", \"p\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"pre\", \"address\", \"li\", \"th\", \"td\", \"script\", \"style\",\n            \"ins\", \"del\", \"s\", \"button\"\n        };\n        String[] voidTags = {\n            \"meta\", \"link\", \"base\", \"frame\", \"img\", \"br\", \"wbr\", \"embed\", \"hr\", \"input\", \"keygen\", \"col\", \"command\",\n            \"device\", \"area\", \"basefont\", \"bgsound\", \"menuitem\", \"param\", \"source\", \"track\"\n        };\n        String[] preserveWhitespaceTags = {\n            \"pre\", \"plaintext\", \"title\", \"textarea\", \"script\"\n        };\n        String[] rcdataTags = { \"title\", \"textarea\" };\n        String[] dataTags = { \"iframe\", \"noembed\", \"noframes\", \"script\", \"style\", \"xmp\" };\n        String[] formSubmitTags = SharedConstants.FormSubmitTags;\n        String[] blockMathTags = {\"math\"};\n        String[] inlineMathTags = {\"mi\", \"mo\", \"msup\", \"mn\", \"mtext\"};\n        String[] blockSvgTags = {\"svg\", \"femerge\", \"femergenode\"}; // note these are LC versions, but actually preserve case\n        String[] inlineSvgTags = {\"text\"};\n        String[] dataSvgTags = {\"script\"};\n\n        return new TagSet()\n            .setupTags(NamespaceHtml, blockTags, tag -> tag.set(Tag.Block))\n            .setupTags(NamespaceHtml, inlineTags, tag -> tag.set(0))\n            .setupTags(NamespaceHtml, inlineContainers, tag -> tag.set(Tag.InlineContainer))\n            .setupTags(NamespaceHtml, voidTags, tag -> tag.set(Tag.Void))\n            .setupTags(NamespaceHtml, preserveWhitespaceTags, tag -> tag.set(Tag.PreserveWhitespace))\n            .setupTags(NamespaceHtml, rcdataTags, tag -> tag.set(Tag.RcData))\n            .setupTags(NamespaceHtml, dataTags, tag -> tag.set(Tag.Data))\n            .setupTags(NamespaceHtml, formSubmitTags, tag -> tag.set(Tag.FormSubmittable))\n            .setupTags(NamespaceMathml, blockMathTags, tag -> tag.set(Tag.Block))\n            .setupTags(NamespaceMathml, inlineMathTags, tag -> tag.set(0))\n            .setupTags(NamespaceSvg, blockSvgTags, tag -> tag.set(Tag.Block))\n            .setupTags(NamespaceSvg, inlineSvgTags, tag -> tag.set(0))\n            .setupTags(NamespaceSvg, dataSvgTags, tag -> tag.set(Tag.Data))\n            ;\n    }\n\n    private TagSet setupTags(String namespace, String[] tagNames, Consumer<Tag> tagModifier) {\n        for (String tagName : tagNames) {\n            Tag tag = get(tagName, namespace);\n            if (tag == null) {\n                tag = new Tag(tagName, tagName, namespace); // normal name is already normal here\n                tag.options = 0; // clear defaults\n                add(tag);\n            }\n            tagModifier.accept(tag);\n        }\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/Token.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Range;\nimport org.jspecify.annotations.Nullable;\n\n/**\n * Parse tokens for the Tokeniser.\n */\nabstract class Token {\n    static final int UnsetPos = -1;\n    final TokenType type; // used in switches in TreeBuilder vs .getClass()\n    int startPos, endPos = UnsetPos; // position in CharacterReader this token was read from\n\n    private Token(TokenType type) {\n        this.type = type;\n    }\n    \n    String tokenType() {\n        return this.getClass().getSimpleName();\n    }\n\n    /**\n     * Reset the data represent by this token, for reuse. Prevents the need to create transfer objects for every\n     * piece of data, which immediately get GCed.\n     */\n    Token reset() {\n        startPos = UnsetPos;\n        endPos = UnsetPos;\n        return this;\n    }\n\n    int startPos() {\n        return startPos;\n    }\n\n    void startPos(int pos) {\n        startPos = pos;\n    }\n\n    int endPos() {\n        return endPos;\n    }\n\n    void endPos(int pos) {\n        endPos = pos;\n    }\n\n    static final class Doctype extends Token {\n        final TokenData name = new TokenData();\n        @Nullable String pubSysKey = null;\n        final TokenData publicIdentifier = new TokenData();\n        final TokenData systemIdentifier = new TokenData();\n        boolean forceQuirks = false;\n\n        Doctype() {\n            super(TokenType.Doctype);\n        }\n\n        @Override\n        Token reset() {\n            super.reset();\n            name.reset();\n            pubSysKey = null;\n            publicIdentifier.reset();\n            systemIdentifier.reset();\n            forceQuirks = false;\n            return this;\n        }\n\n        String getName() {\n            return name.value();\n        }\n\n        @Nullable String getPubSysKey() {\n            return pubSysKey;\n        }\n\n        String getPublicIdentifier() {\n            return publicIdentifier.value();\n        }\n\n        public String getSystemIdentifier() {\n            return systemIdentifier.value();\n        }\n\n        public boolean isForceQuirks() {\n            return forceQuirks;\n        }\n\n        @Override\n        public String toString() {\n            return \"<!doctype \" + getName() + \">\";\n        }\n    }\n\n    static abstract class Tag extends Token {\n        protected TokenData tagName = new TokenData();\n        @Nullable protected String normalName; // lc version of tag name, for case-insensitive tree build\n        boolean selfClosing = false;\n        @Nullable Attributes attributes; // start tags get attributes on construction. End tags get attributes on first new attribute (but only for parser convenience, not used).\n\n        final private TokenData attrName = new TokenData();\n        final private TokenData attrValue = new TokenData();\n        private boolean hasEmptyAttrValue = false; // distinguish boolean attribute from empty string value\n\n        // attribute source range tracking\n        final TreeBuilder treeBuilder;\n        final boolean trackSource;\n        int attrNameStart, attrNameEnd, attrValStart, attrValEnd;\n\n        Tag(TokenType type, TreeBuilder treeBuilder) {\n            super(type);\n            this.treeBuilder = treeBuilder;\n            this.trackSource = treeBuilder.trackSourceRange;\n        }\n\n        @Override\n        Tag reset() {\n            super.reset();\n            tagName.reset();\n            normalName = null;\n            selfClosing = false;\n            attributes = null;\n            resetPendingAttr();\n            return this;\n        }\n\n        private void resetPendingAttr() {\n            attrName.reset();\n            attrValue.reset();\n            hasEmptyAttrValue = false;\n\n            if (trackSource)\n                attrNameStart = attrNameEnd = attrValStart = attrValEnd = UnsetPos;\n        }\n\n        /* Limits runaway crafted HTML from spewing attributes and getting a little sluggish in ensureCapacity.\n        Real-world HTML will P99 around 8 attributes, so plenty of headroom. Implemented here and not in the Attributes\n        object so that API users can add more if ever required. */\n        private static final int MaxAttributes = 512;\n\n        final void newAttribute() {\n            if (attributes == null)\n                attributes = new Attributes();\n\n            if (attrName.hasData() && attributes.size() < MaxAttributes) {\n                // the tokeniser has skipped whitespace control chars, but trimming could collapse to empty for other control codes, so verify here\n                String name = attrName.value();\n                name = name.trim();\n                if (!name.isEmpty()) {\n                    String value;\n                    if (attrValue.hasData())\n                        value = attrValue.value();\n                    else if (hasEmptyAttrValue)\n                        value = \"\";\n                    else\n                        value = null;\n                    // note that we add, not put. So that the first is kept, and rest are deduped, once in a context where case sensitivity is known, and we can warn for duplicates.\n                    attributes.add(name, value);\n\n                    trackAttributeRange(name);\n                }\n            }\n            resetPendingAttr();\n        }\n\n        private void trackAttributeRange(String name) {\n            if (trackSource && isStartTag()) {\n                final StartTag start = asStartTag();\n                final CharacterReader r = start.treeBuilder.reader;\n                final boolean preserve = start.treeBuilder.settings.preserveAttributeCase();\n\n                assert attributes != null;\n                if (!preserve) name = Normalizer.lowerCase(name);\n                if (attributes.sourceRange(name).nameRange().isTracked()) return; // dedupe ranges as we go; actual attributes get deduped later for error count\n\n                // if there's no value (e.g. boolean), make it an implicit range at current\n                if (!attrValue.hasData()) attrValStart = attrValEnd = attrNameEnd;\n\n                Range.AttributeRange range = new Range.AttributeRange(\n                    new Range(\n                        new Range.Position(attrNameStart, r.lineNumber(attrNameStart), r.columnNumber(attrNameStart)),\n                        new Range.Position(attrNameEnd, r.lineNumber(attrNameEnd), r.columnNumber(attrNameEnd))),\n                    new Range(\n                        new Range.Position(attrValStart, r.lineNumber(attrValStart), r.columnNumber(attrValStart)),\n                        new Range.Position(attrValEnd, r.lineNumber(attrValEnd), r.columnNumber(attrValEnd)))\n                );\n                attributes.sourceRange(name, range);\n            }\n        }\n\n        final boolean hasAttributes() {\n            return attributes != null;\n        }\n\n        final boolean hasAttributeIgnoreCase(String key) {\n            return attributes != null && attributes.hasKeyIgnoreCase(key);\n        }\n\n        final void finaliseTag() {\n            // finalises for emit\n            if (attrName.hasData()) {\n                newAttribute();\n            }\n        }\n\n        /** Preserves case */\n        final String name() { // preserves case, for input into Tag.valueOf (which may drop case)\n            return tagName.value();\n        }\n\n        /** Lower case */\n        final String normalName() { // lower case, used in tree building for working out where in tree it should go\n            Validate.isFalse(normalName == null || normalName.isEmpty());\n            return normalName;\n        }\n\n        final String toStringName() {\n            String name = tagName.value();\n            return (name.isEmpty()) ? \"[unset]\" : name;\n        }\n\n        final Tag name(String name) {\n            tagName.set(name);\n            normalName = ParseSettings.normalName(tagName.value());\n            return this;\n        }\n\n        final boolean isSelfClosing() {\n            return selfClosing;\n        }\n\n        // these appenders are rarely hit in not null state-- caused by null chars.\n        final void appendTagName(String append) {\n            // might have null chars - need to replace with null replacement character\n            append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar);\n            tagName.append(append);\n            normalName = ParseSettings.normalName(tagName.value());\n        }\n\n        final void appendTagName(char append) {\n            appendTagName(String.valueOf(append)); // so that normalname gets updated too\n        }\n\n        final void appendAttributeName(String append, int startPos, int endPos) {\n            // might have null chars because we eat in one pass - need to replace with null replacement character\n            append = append.replace(TokeniserState.nullChar, Tokeniser.replacementChar);\n            attrName.append(append);\n            attrNamePos(startPos, endPos);\n        }\n\n        final void appendAttributeName(char append, int startPos, int endPos) {\n            attrName.append(append);\n            attrNamePos(startPos, endPos);\n        }\n\n        final void appendAttributeValue(String append, int startPos, int endPos) {\n            attrValue.append(append);\n            attrValPos(startPos, endPos);\n        }\n\n        final void appendAttributeValue(char append, int startPos, int endPos) {\n            attrValue.append(append);\n            attrValPos(startPos, endPos);\n        }\n\n        final void appendAttributeValue(int[] appendCodepoints, int startPos, int endPos) {\n            for (int codepoint : appendCodepoints) {\n                attrValue.appendCodePoint(codepoint);\n            }\n            attrValPos(startPos, endPos);\n        }\n        \n        final void setEmptyAttributeValue() {\n            hasEmptyAttrValue = true;\n        }\n\n        private void attrNamePos(int startPos, int endPos) {\n            if (trackSource) {\n                attrNameStart = attrNameStart > UnsetPos ? attrNameStart : startPos; // latches to first\n                attrNameEnd = endPos;\n            }\n        }\n\n        private void attrValPos(int startPos, int endPos) {\n            if (trackSource) {\n                attrValStart = attrValStart > UnsetPos ? attrValStart : startPos; // latches to first\n                attrValEnd = endPos;\n            }\n        }\n\n        @Override\n        abstract public String toString();\n    }\n\n    final static class StartTag extends Tag {\n\n        // TreeBuilder is provided so if tracking, can get line / column positions for Range; and can dedupe as we go\n        StartTag(TreeBuilder treeBuilder) {\n            super(TokenType.StartTag, treeBuilder);\n        }\n\n        @Override\n        Tag reset() {\n            super.reset();\n            attributes = null;\n            return this;\n        }\n\n        StartTag nameAttr(String name, Attributes attributes) {\n            this.tagName.set(name);\n            this.attributes = attributes;\n            normalName = ParseSettings.normalName(name);\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            String closer = isSelfClosing() ? \"/>\" : \">\";\n            if (hasAttributes() && attributes.size() > 0)\n                return \"<\" + toStringName() + \" \" + attributes.toString() + closer;\n            else\n                return \"<\" + toStringName() + closer;\n        }\n    }\n\n    final static class EndTag extends Tag{\n        EndTag(TreeBuilder treeBuilder) {\n            super(TokenType.EndTag, treeBuilder);\n        }\n\n        @Override\n        public String toString() {\n            return \"</\" + toStringName() + \">\";\n        }\n    }\n\n    final static class Comment extends Token {\n        private final TokenData data = new TokenData();\n        boolean bogus = false;\n\n        @Override\n        Token reset() {\n            super.reset();\n            data.reset();\n            bogus = false;\n            return this;\n        }\n\n        Comment() {\n            super(TokenType.Comment);\n        }\n\n        String getData() {\n            return data.value();\n        }\n\n        Comment append(String append) {\n            data.append(append);\n            return this;\n        }\n\n        Comment append(char append) {\n            data.append(append);\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            return \"<!--\" + getData() + \"-->\";\n        }\n    }\n\n    static class Character extends Token {\n        final TokenData data = new TokenData();\n\n        Character() {\n            super(TokenType.Character);\n        }\n\n        /** Deep copy */\n        Character(Character source) {\n            super(TokenType.Character);\n            this.startPos = source.startPos;\n            this.endPos = source.endPos;\n            this.data.set(source.data.value());\n        }\n\n        @Override\n        Token reset() {\n            super.reset();\n            data.reset();\n            return this;\n        }\n\n        Character data(String str) {\n            data.set(str);\n            return this;\n        }\n\n        Character append(String str) {\n            data.append(str);\n            return this;\n        }\n\n        String getData() {\n            return data.value();\n        }\n\n        @Override\n        public String toString() {\n            return getData();\n        }\n\n        /**\n         Normalize null chars in the data. If replace is true, replaces with the replacement char; if false, removes.\n         */\n        public void normalizeNulls(boolean replace) {\n            String data = this.data.value();\n            if (data.indexOf(TokeniserState.nullChar) == -1) return;\n\n            data = (replace ?\n                data.replace(TokeniserState.nullChar, Tokeniser.replacementChar) :\n                data.replace(nullString, \"\"));\n            this.data.set(data);\n        }\n\n        private static final String nullString = String.valueOf(TokeniserState.nullChar);\n    }\n\n    final static class CData extends Character {\n        CData(String data) {\n            super();\n            this.data(data);\n        }\n\n        @Override\n        public String toString() {\n            return \"<![CDATA[\" + getData() + \"]]>\";\n        }\n\n    }\n\n    /**\n     XmlDeclaration - extends Tag for pseudo attribute support\n     */\n    final static class XmlDecl extends Tag {\n        boolean isDeclaration = true; // <!..>, or <?...?> if false (a processing instruction)\n\n        public XmlDecl(TreeBuilder treeBuilder) {\n            super(TokenType.XmlDecl, treeBuilder);\n        }\n\n        @Override\n        XmlDecl reset() {\n            super.reset();\n            isDeclaration = true;\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            String open = isDeclaration ? \"<!\" : \"<?\";\n            String close = isDeclaration ? \">\" : \"?>\";\n            if (hasAttributes() && attributes.size() > 0)\n                return open + toStringName() + \" \" + attributes.toString() + close;\n            else\n                return open + toStringName() + close;\n        }\n    }\n\n    final static class EOF extends Token {\n        EOF() {\n            super(Token.TokenType.EOF);\n        }\n\n        @Override\n        Token reset() {\n            super.reset();\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            return \"\";\n        }\n    }\n\n    final boolean isDoctype() {\n        return type == TokenType.Doctype;\n    }\n\n    final Doctype asDoctype() {\n        return (Doctype) this;\n    }\n\n    final boolean isStartTag() {\n        return type == TokenType.StartTag;\n    }\n\n    final StartTag asStartTag() {\n        return (StartTag) this;\n    }\n\n    final boolean isEndTag() {\n        return type == TokenType.EndTag;\n    }\n\n    final EndTag asEndTag() {\n        return (EndTag) this;\n    }\n\n    final boolean isComment() {\n        return type == TokenType.Comment;\n    }\n\n    final Comment asComment() {\n        return (Comment) this;\n    }\n\n    final boolean isCharacter() {\n        return type == TokenType.Character;\n    }\n\n    final boolean isCData() {\n        return this instanceof CData;\n    }\n\n    final Character asCharacter() {\n        return (Character) this;\n    }\n\n    final XmlDecl asXmlDecl() {\n        return (XmlDecl) this;\n    }\n\n    final boolean isEOF() {\n        return type == TokenType.EOF;\n    }\n\n    public enum TokenType {\n        Doctype,\n        StartTag,\n        EndTag,\n        Comment,\n        Character, // note no CData - treated in builder as an extension of Character\n        XmlDecl,\n        EOF\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/TokenData.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.internal.StringUtil;\nimport org.jspecify.annotations.Nullable;\n\n/**\n A value holder for Tokens, as the stream is Tokenized. Can hold a String or a StringBuilder.\n <p>The goal is to minimize String copies -- the tokenizer tries to read the entirety of the token's data in one it, and\n set that as the simple String value. But if it turns out we need to append, fall back to a StringBuilder, which we get\n out of the pool (to reduce the GC load).</p>\n */\nclass TokenData {\n    private @Nullable String value;\n    private @Nullable StringBuilder builder;\n\n    TokenData() {}\n\n    void set(String str) {\n        reset();\n        value = str;\n    }\n\n    void append(String str) {\n        if (builder != null) {\n            builder.append(str);\n        } else if (value != null) {\n            flipToBuilder();\n            builder.append(str);\n        } else {\n            value = str;\n        }\n    }\n\n    void append(char c) {\n        if (builder != null) {\n            builder.append(c);\n        } else if (value != null) {\n            flipToBuilder();\n            builder.append(c);\n        } else {\n            value = String.valueOf(c);\n        }\n    }\n\n    void appendCodePoint(int codepoint) {\n        if (builder != null) {\n            builder.appendCodePoint(codepoint);\n        } else if (value != null) {\n            flipToBuilder();\n            builder.appendCodePoint(codepoint);\n        } else {\n            value = String.valueOf(Character.toChars(codepoint));\n        }\n    }\n\n    private void flipToBuilder() {\n        builder = StringUtil.borrowBuilder();\n        builder.append(value);\n        value = null;\n    }\n\n    boolean hasData() {\n        return builder != null || value != null;\n    }\n\n    void reset() {\n        if (builder != null) {\n            StringUtil.releaseBuilderVoid(builder);\n            builder = null;\n        }\n        value = null;\n    }\n\n    String value() {\n        if (builder != null) {\n            // in rare case we get hit twice, don't toString the builder twice\n            value = builder.toString();\n            StringUtil.releaseBuilder(builder);\n            builder = null;\n            return value;\n        }\n        return value != null ? value : \"\";\n    }\n\n    @Override\n    public String toString() {\n        // for debug views; no side effects\n        if (builder != null) return builder.toString();\n        return value != null ? value : \"\";\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/TokenQueue.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.helper.Validate;\n\n/**\n A character reader with helpers focusing on parsing CSS selectors. Used internally by jsoup. API subject to changes.\n */\n\npublic class TokenQueue implements AutoCloseable {\n    private static final char Esc = '\\\\'; // escape char for chomp balanced.\n    private static final char Hyphen_Minus = '-';\n    private static final char Unicode_Null = '\\u0000';\n    private static final char Replacement = '\\uFFFD';\n\n    private final CharacterReader reader;\n\n    /**\n     Create a new TokenQueue.\n     @param data string of data to back queue.\n     */\n    public TokenQueue(String data) {\n        reader = new CharacterReader(data);\n    }\n\n    /**\n     Is the queue empty?\n     @return true if no data left in queue.\n     */\n    public boolean isEmpty() {\n        return reader.isEmpty();\n    }\n\n    /**\n     Consume one character off queue.\n     @return first character on queue.\n     */\n    public char consume() {\n        return reader.consume();\n    }\n\n    /**\n     Drops the next character off the queue.\n     */\n    public void advance() {\n        if (!isEmpty()) reader.advance();\n    }\n\n    char current() {\n        return reader.current();\n    }\n\n    /**\n     Tests if the next characters on the queue match the sequence, case-insensitively.\n     @param seq String to check queue for.\n     @return true if the next characters match.\n     */\n    public boolean matches(String seq) {\n        return reader.matchesIgnoreCase(seq);\n    }\n\n    /** Tests if the next character on the queue matches the character, case-sensitively. */\n    public boolean matches(char c) {\n        return reader.matches(c);\n    }\n\n    /**\n     Tests if the next characters match any of the sequences, case-<b>sensitively</b>.\n     @param seq list of chars to case-sensitively check for\n     @return true of any matched, false if none did\n     */\n    public boolean matchesAny(char... seq) {\n        return reader.matchesAny(seq);\n    }\n\n    /**\n     If the queue case-insensitively matches the supplied string, consume it off the queue.\n     @param seq String to search for, and if found, remove from queue.\n     @return true if found and removed, false if not found.\n     */\n    public boolean matchChomp(String seq) {\n        return reader.matchConsumeIgnoreCase(seq);\n    }\n\n    /** If the queue matches the supplied (case-sensitive) character, consume it off the queue. */\n    public boolean matchChomp(char c) {\n        if (reader.matches(c)) {\n            consume();\n            return true;\n        }\n        return false;\n    }\n\n    /**\n     Tests if queue starts with a whitespace character.\n     @return if starts with whitespace\n     */\n    public boolean matchesWhitespace() {\n        return StringUtil.isWhitespace(reader.current());\n    }\n\n    /**\n     Test if the queue matches a tag word character (letter or digit).\n     @return if matches a word character\n     */\n    public boolean matchesWord() {\n        return Character.isLetterOrDigit(reader.current());\n    }\n\n    /**\n     Consumes the supplied sequence of the queue, case-insensitively. If the queue does not start with the supplied\n     sequence, will throw an illegal state exception -- but you should be running match() against that condition.\n\n     @param seq sequence to remove from head of queue.\n     */\n    public void consume(String seq) {\n        boolean found = reader.matchConsumeIgnoreCase(seq);\n        if (!found) throw new IllegalStateException(\"Queue did not match expected sequence\");\n    }\n\n    /**\n     Pulls a string off the queue, up to but exclusive of the match sequence, or to the queue running out.\n     @param seq String to end on (and not include in return, but leave on queue). <b>Case-sensitive.</b>\n     @return The matched data consumed from queue.\n     */\n    public String consumeTo(String seq) {\n        return reader.consumeTo(seq);\n    }\n\n    /**\n     Consumes to the first sequence provided, or to the end of the queue. Leaves the terminator on the queue.\n     @param seq any number of terminators to consume to. <b>Case-insensitive.</b>\n     @return consumed string\n     */\n    public String consumeToAny(String... seq) {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        OUT: while (!isEmpty()) {\n            for (String s : seq) {\n                if (reader.matchesIgnoreCase(s)) break OUT;\n            }\n            sb.append(consume());\n        }\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    /**\n     Pulls a balanced string off the queue. E.g. if queue is \"(one (two) three) four\", (,) will return \"one (two) three\",\n     and leave \" four\" on the queue. Unbalanced openers and closers can be quoted (with ' or \") or escaped (with \\).\n     Those escapes will be left in the returned string, which is suitable for regexes (where we need to preserve the\n     escape), but unsuitable for contains text strings; use unescape for that.\n\n     @param open opener\n     @param close closer\n     @return data matched from the queue\n     */\n    public String chompBalanced(char open, char close) {\n        StringBuilder accum = StringUtil.borrowBuilder();\n        int depth = 0;\n        char prev = 0;\n        boolean inSingle = false;\n        boolean inDouble = false;\n        boolean inRegexQE = false; // regex \\Q .. \\E escapes from Pattern.quote()\n        reader.mark(); // mark the initial position to restore if needed\n\n        do {\n            if (isEmpty()) break;\n            char c = consume();\n            if (prev == Esc) {\n                if      (c == 'Q') inRegexQE = true;\n                else if (c == 'E') inRegexQE = false;\n                accum.append(c);\n            } else {\n                if      (c == '\\'' && c != open && !inDouble) inSingle = !inSingle;\n                else if (c == '\"'  && c != open && !inSingle) inDouble = !inDouble;\n\n                if (inSingle || inDouble || inRegexQE) {\n                    accum.append(c);\n                } else if (c == open) {\n                    depth++;\n                    if (depth > 1) accum.append(c); // don't include the outer match pair in the return\n                } else if (c == close) {\n                    depth--;\n                    if (depth > 0) accum.append(c);\n                } else {\n                    accum.append(c);\n                }\n            }\n            prev = c;\n        } while (depth > 0);\n\n        String out = StringUtil.releaseBuilder(accum);\n        if (depth > 0) {// ran out of queue before seeing enough )\n            reader.rewindToMark(); // restore position if we don't have a balanced string\n            Validate.fail(\"Did not find balanced marker at '\" + out + \"'\");\n        }\n        return out;\n    }\n    \n    /**\n     * Unescape a \\ escaped string.\n     * @param in backslash escaped string\n     * @return unescaped string\n     */\n    public static String unescape(String in) {\n        if (in.indexOf(Esc) == -1) return in;\n\n        StringBuilder out = StringUtil.borrowBuilder();\n        char last = 0;\n        for (char c : in.toCharArray()) {\n            if (c == Esc) {\n                if (last == Esc) {\n                    out.append(c);\n                    c = 0;\n                }\n            }\n            else \n                out.append(c);\n            last = c;\n        }\n        return StringUtil.releaseBuilder(out);\n    }\n\n    /**\n     Given a CSS identifier (such as a tag, ID, or class), escape any CSS special characters that would otherwise not be\n     valid in a selector.\n\n     @see <a href=\"https://www.w3.org/TR/cssom-1/#serialize-an-identifier\">CSS Object Model, serialize an identifier</a>\n     */\n    public static String escapeCssIdentifier(String in) {\n        if (in.isEmpty()) return in;\n\n        StringBuilder out = StringUtil.borrowBuilder();\n        TokenQueue q = new TokenQueue(in);\n\n        char firstChar = q.current();\n        if (firstChar == Hyphen_Minus) {\n            q.advance();\n            if (q.isEmpty()) {\n                // If the character is the first character and is a \"-\" (U+002D), and there is no second character, then\n                // the escaped character.\n                appendEscaped(out, Hyphen_Minus);\n            } else {\n                out.append(Hyphen_Minus);\n\n                char secondChar = q.current();\n                if (StringUtil.isDigit(secondChar)) {\n                    // If the character is the second character and is in the range [0-9] (U+0030 to U+0039) and the\n                    // first character is a \"-\" (U+002D), then the character escaped as code point.\n                    appendEscapedCodepoint(out, q.consume());\n                }\n            }\n        } else if (StringUtil.isDigit(firstChar)) {\n            // If the character is the first character and is in the range [0-9] (U+0030 to U+0039), then the character\n            // escaped as code point.\n            appendEscapedCodepoint(out, q.consume());\n        }\n\n        while (!q.isEmpty()) {\n            // Note: It's fine to iterate on chars because non-ASCII characters are never escaped. So surrogate pairs\n            // are kept intact.\n            char c = q.consume();\n            if (c == Unicode_Null) {\n                // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD).\n                out.append(Replacement);\n            } else if (c <= '\\u001F' || c == '\\u007F') {\n                // If the character is in the range [\\1-\\1f] (U+0001 to U+001F) or is U+007F, then the character\n                // escaped as code point.\n                appendEscapedCodepoint(out, c);\n            } else if (isIdent(c)) {\n                // If the character is not handled by one of the above rules and is greater than or equal to U+0080,\n                // is \"-\" (U+002D) or \"_\" (U+005F), or is in one of the ranges [0-9] (U+0030 to U+0039),\n                // [A-Z] (U+0041 to U+005A), or [a-z] (U+0061 to U+007A), then the character itself.\n                out.append(c);\n            } else {\n                // Otherwise, the escaped character.\n                appendEscaped(out, c);\n            }\n        }\n\n        q.close();\n        return StringUtil.releaseBuilder(out);\n    }\n\n    private static void appendEscaped(StringBuilder out, char c) {\n        out.append(Esc).append(c);\n    }\n\n    private static void appendEscapedCodepoint(StringBuilder out, char c) {\n        out.append(Esc).append(Integer.toHexString(c)).append(' ');\n    }\n\n    /**\n     * Pulls the next run of whitespace characters of the queue.\n     * @return Whether consuming whitespace or not\n     */\n    public boolean consumeWhitespace() {\n        boolean seen = false;\n        while (matchesWhitespace()) {\n            advance();\n            seen = true;\n        }\n        return seen;\n    }\n\n    /**\n     * Consume a CSS element selector (tag name, but | instead of : for namespaces (or *| for wildcard namespace), to not conflict with :pseudo selects).\n     * \n     * @return tag name\n     */\n    public String consumeElementSelector() {\n        return consumeEscapedCssIdentifier(ElementSelectorChars);\n    }\n    private static final char[] ElementSelectorChars = {'*', '|', '_', '-'};\n\n    /**\n     Consume a CSS identifier (ID or class) off the queue.\n     <p>Note: For backwards compatibility this method supports improperly formatted CSS identifiers, e.g. {@code 1} instead\n     of {@code \\31}.</p>\n\n     @return The unescaped identifier.\n     @throws IllegalArgumentException if an invalid escape sequence was found. Afterward, the state of the TokenQueue\n     is undefined.\n     @see <a href=\"https://www.w3.org/TR/css-syntax-3/#consume-name\">CSS Syntax Module Level 3, Consume an ident sequence</a>\n     @see <a href=\"https://www.w3.org/TR/css-syntax-3/#typedef-ident-token\">CSS Syntax Module Level 3, ident-token</a>\n     */\n    public String consumeCssIdentifier() {\n        if (isEmpty()) throw new IllegalArgumentException(\"CSS identifier expected, but end of input found\");\n\n        // Fast path for CSS identifiers that don't contain escape sequences.\n        String identifier = reader.consumeMatching(TokenQueue::isIdent);\n        char c = current();\n        if (c != Esc && c != Unicode_Null) {\n            // If we didn't end on an Esc or a Null, we consumed the whole identifier\n            return identifier;\n        }\n\n        // An escape sequence was found. Use a StringBuilder to store the decoded CSS identifier.\n        StringBuilder out = StringUtil.borrowBuilder();\n        if (!identifier.isEmpty()) {\n            // Copy the CSS identifier up to the first escape sequence.\n            out.append(identifier);\n        }\n\n        while (!isEmpty()) {\n            c = current();\n            if (isIdent(c)) {\n                out.append(consume());\n            } else if (c == Unicode_Null) {\n                // https://www.w3.org/TR/css-syntax-3/#input-preprocessing\n                advance();\n                out.append(Replacement);\n            } else if (c == Esc) {\n                advance();\n                if (!isEmpty() && isNewline(current())) {\n                    // Not a valid escape sequence. This is treated as the end of the CSS identifier.\n                    reader.unconsume();\n                    break;\n                } else {\n                    consumeCssEscapeSequenceInto(out);\n                }\n            } else {\n                break;\n            }\n        }\n        return StringUtil.releaseBuilder(out);\n    }\n\n    private void consumeCssEscapeSequenceInto(StringBuilder out) {\n        if (isEmpty()) {\n            out.append(Replacement);\n            return;\n        }\n\n        char firstEscaped = consume();\n        if (!StringUtil.isHexDigit(firstEscaped)) {\n            out.append(firstEscaped);\n        } else {\n            reader.unconsume(); // put back the first hex digit\n            String hexString = reader.consumeMatching(StringUtil::isHexDigit, 6); // consume up to 6 hex digits\n            int codePoint;\n            try {\n                codePoint = Integer.parseInt(hexString, 16);\n            } catch (NumberFormatException e) {\n                throw new IllegalArgumentException(\"Invalid escape sequence: \" + hexString, e);\n            }\n            if (isValidCodePoint(codePoint)) {\n                out.appendCodePoint(codePoint);\n            } else {\n                out.append(Replacement);\n            }\n\n            if (!isEmpty()) {\n                char c = current();\n                if (c == '\\r') {\n                    // Since there's currently no input preprocessing, check for CRLF here.\n                    // https://www.w3.org/TR/css-syntax-3/#input-preprocessing\n                    advance();\n                    if (!isEmpty() && current() == '\\n') advance();\n                } else if (c == ' ' || c == '\\t' || isNewline(c)) {\n                    advance();\n                }\n            }\n        }\n    }\n\n    // statics below specifically for CSS identifiers:\n\n    // https://www.w3.org/TR/css-syntax-3/#non-ascii-code-point\n    private static boolean isNonAscii(char c) {\n        return c >= '\\u0080';\n    }\n\n    // https://www.w3.org/TR/css-syntax-3/#ident-start-code-point\n    private static boolean isIdentStart(char c) {\n        return c == '_' || StringUtil.isAsciiLetter(c) || isNonAscii(c);\n    }\n\n    // https://www.w3.org/TR/css-syntax-3/#ident-code-point\n    private static boolean isIdent(char c) {\n        return c == Hyphen_Minus || StringUtil.isDigit(c) || isIdentStart(c);\n    }\n\n    // https://www.w3.org/TR/css-syntax-3/#newline\n    // Note: currently there's no preprocessing happening.\n    private static boolean isNewline(char c) {\n        return c == '\\n' || c == '\\r' || c == '\\f';\n    }\n\n    // https://www.w3.org/TR/css-syntax-3/#consume-an-escaped-code-point\n    private static boolean isValidCodePoint(int codePoint) {\n        return codePoint != 0 && Character.isValidCodePoint(codePoint) && !Character.isSurrogate((char) codePoint);\n    }\n\n    private static final char[] CssIdentifierChars = {'-', '_'};\n\n    private String consumeEscapedCssIdentifier(char... matches) {\n        StringBuilder sb = StringUtil.borrowBuilder();\n        while (!isEmpty()) {\n            char c = current();\n            if (c == Esc) {\n                advance();\n                if (!isEmpty()) sb.append(consume());\n                else break;\n            } else if (matchesCssIdentifier(matches)) {\n                sb.append(c);\n                advance();\n            } else {\n                break;\n            }\n        }\n        return StringUtil.releaseBuilder(sb);\n    }\n\n    private boolean matchesCssIdentifier(char... matches) {\n        return matchesWord() || reader.matchesAny(matches);\n    }\n\n    /**\n     Consume and return whatever is left on the queue.\n     @return remainder of queue.\n     */\n    public String remainder() {\n        return reader.consumeToEnd();\n    }\n\n    @Override\n    public String toString() {\n        return reader.toString();\n    }\n\n    @Override\n    public void close() {\n        reader.close(); // releases buffer back to pool\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/Tokeniser.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Entities;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.Arrays;\n\n/**\n * Readers the input stream into tokens.\n */\nfinal class Tokeniser {\n    static final char replacementChar = '\\uFFFD'; // replaces null character\n    private static final char[] notCharRefCharsSorted = new char[]{'\\t', '\\n', '\\r', '\\f', ' ', '<', '&'};\n\n    // Some illegal character escapes are parsed by browsers as windows-1252 instead. See issue #1034\n    // https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state\n    static final int win1252ExtensionsStart = 0x80;\n    static final int[] win1252Extensions = new int[] {\n            // we could build this manually, but Windows-1252 is not a standard java charset so that could break on\n            // some platforms - this table is verified with a test\n            0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,\n            0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,\n            0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,\n            0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,\n    };\n\n    static {\n        Arrays.sort(notCharRefCharsSorted);\n    }\n\n    private final CharacterReader reader; // html input\n    private final ParseErrorList errors; // errors found while tokenising\n\n    private TokeniserState state = TokeniserState.Data; // current tokenisation state\n    @Nullable private Token emitPending = null; // the token we are about to emit on next read\n    private boolean isEmitPending = false;\n    final TokenData dataBuffer = new TokenData(); // buffers data looking for </script>\n\n    final Document.OutputSettings.Syntax syntax; // html or xml syntax; affects processing of xml declarations vs as bogus comments\n    final Token.StartTag startPending;\n    final Token.EndTag endPending;\n    Token.Tag tagPending; // tag we are building up: start or end pending\n    final Token.Character charPending = new Token.Character();\n    final Token.Doctype doctypePending = new Token.Doctype(); // doctype building up\n    final Token.Comment commentPending = new Token.Comment(); // comment building up\n    final Token.XmlDecl xmlDeclPending; // xml decl building up\n    @Nullable private String lastStartTag; // the last start tag emitted, to test appropriate end tag\n    @Nullable private String lastStartCloseSeq; // \"</\" + lastStartTag, so we can quickly check for that in RCData\n\n    private int markupStartPos, charStartPos = 0; // reader pos at the start of markup / characters. markup updated on state transition, char on token emit.\n\n    Tokeniser(TreeBuilder treeBuilder) {\n        syntax = treeBuilder instanceof XmlTreeBuilder ? Document.OutputSettings.Syntax.xml : Document.OutputSettings.Syntax.html;\n        tagPending = startPending  = new Token.StartTag(treeBuilder);\n        endPending = new Token.EndTag(treeBuilder);\n        xmlDeclPending = new Token.XmlDecl(treeBuilder);\n        this.reader = treeBuilder.reader;\n        this.errors = treeBuilder.parser.getErrors();\n    }\n\n    Token read() {\n        while (!isEmitPending) {\n            state.read(this, reader);\n        }\n\n        // if emit is pending, a non-character token was found: return any chars in buffer, and leave token for next read:\n        if (charPending.data.hasData()) {\n            return charPending;\n        } else {\n            isEmitPending = false;\n            assert emitPending != null;\n            return emitPending;\n        }\n    }\n\n    void emit(Token token) {\n        Validate.isFalse(isEmitPending);\n\n        emitPending = token;\n        isEmitPending = true;\n        token.startPos(markupStartPos);\n        token.endPos(reader.pos());\n        charStartPos = reader.pos(); // update char start when we complete a token emit\n\n        if (token.type == Token.TokenType.StartTag) {\n            Token.StartTag startTag = (Token.StartTag) token;\n            lastStartTag = startTag.name();\n            lastStartCloseSeq = null; // only lazy inits\n        } else if (token.type == Token.TokenType.EndTag) {\n            Token.EndTag endTag = (Token.EndTag) token;\n            if (endTag.hasAttributes())\n                error(\"Attributes incorrectly present on end tag [/%s]\", endTag.normalName());\n        }\n    }\n\n    void emit(final String str) {\n        // buffer strings up until last string token found, to emit only one token for a run of character refs etc.\n        // does not set isEmitPending; read checks that\n        // todo move \"<\" to '<'...\n        charPending.append(str);\n        charPending.startPos(charStartPos);\n        charPending.endPos(reader.pos());\n    }\n\n    void emit(char c) {\n        charPending.data.append(c);\n        charPending.startPos(charStartPos);\n        charPending.endPos(reader.pos());\n    }\n\n    void emit(int[] codepoints) {\n        emit(new String(codepoints, 0, codepoints.length));\n    }\n\n    void transition(TokeniserState newState) {\n        // track markup position on state transitions\n        if (newState == TokeniserState.TagOpen)\n            markupStartPos = reader.pos();\n\n        this.state = newState;\n    }\n\n    void advanceTransition(TokeniserState newState) {\n        transition(newState);\n        reader.advance();\n    }\n\n    final private int[] codepointHolder = new int[1]; // holder to not have to keep creating arrays\n    final private int[] multipointHolder = new int[2];\n\n    /** Tries to consume a character reference, and returns: null if nothing, int[1], or int[2]. */\n    int @Nullable [] consumeCharacterReference(@Nullable Character additionalAllowedCharacter, boolean inAttribute) {\n        if (reader.isEmpty())\n            return null;\n        if (additionalAllowedCharacter != null && additionalAllowedCharacter == reader.current())\n            return null;\n        if (reader.matchesAnySorted(notCharRefCharsSorted))\n            return null;\n\n        final int[] codeRef = codepointHolder;\n        reader.mark();\n        if (reader.matchConsume(\"#\")) { // numbered\n            boolean isHexMode = reader.matchConsumeIgnoreCase(\"X\");\n            String numRef = isHexMode ? reader.consumeHexSequence() : reader.consumeDigitSequence();\n            if (numRef.isEmpty()) { // didn't match anything\n                characterReferenceError(\"numeric reference with no numerals\");\n                reader.rewindToMark();\n                return null;\n            }\n\n            reader.unmark();\n            if (!reader.matchConsume(\";\"))\n                characterReferenceError(\"missing semicolon on [&#%s]\", numRef); // missing semi\n            int charval = -1;\n            try {\n                int base = isHexMode ? 16 : 10;\n                charval = Integer.valueOf(numRef, base);\n            } catch (NumberFormatException ignored) {\n                // skip\n            }\n            // todo: check for extra illegal unicode points as parse errors - described https://html.spec.whatwg.org/multipage/syntax.html#character-references and in Infra\n            // The numeric character reference forms described above are allowed to reference any code point excluding U+000D CR, noncharacters, and controls other than ASCII whitespace.\n            if (charval == -1 || charval > 0x10FFFF) {\n                characterReferenceError(\"character [%s] outside of valid range\", charval);\n                codeRef[0] = replacementChar;\n            } else {\n                // fix illegal unicode characters to match browser behavior\n                if (charval >= win1252ExtensionsStart && charval < win1252ExtensionsStart + win1252Extensions.length) {\n                    characterReferenceError(\"character [%s] is not a valid unicode code point\", charval);\n                    charval = win1252Extensions[charval - win1252ExtensionsStart];\n                }\n\n                // todo: implement number replacement table\n                // todo: check for extra illegal unicode points as parse errors\n                codeRef[0] = charval;\n            }\n            return codeRef;\n        } else { // named\n            // get as many letters as possible, and look for matching entities.\n            String nameRef = reader.consumeLetterThenDigitSequence();\n            boolean looksLegit = reader.matches(';');\n            // found if a base named entity without a ;, or an extended entity with the ;.\n            boolean found = (Entities.isBaseNamedEntity(nameRef) || (Entities.isNamedEntity(nameRef) && looksLegit));\n\n            if (!found) {\n                reader.rewindToMark();\n                if (looksLegit) // named with semicolon\n                    characterReferenceError(\"invalid named reference [%s]\", nameRef);\n                if (inAttribute) return null;\n                // check if there's a base prefix match; consume and use that if so\n                String prefix = Entities.findPrefix(nameRef);\n                if (prefix.isEmpty()) return null;\n                reader.matchConsume(prefix);\n                nameRef = prefix;\n            }\n            if (inAttribute && (reader.matchesAsciiAlpha() || reader.matchesDigit() || reader.matchesAny('=', '-', '_'))) {\n                // don't want that to match\n                reader.rewindToMark();\n                return null;\n            }\n\n            reader.unmark();\n            if (!reader.matchConsume(\";\"))\n                characterReferenceError(\"missing semicolon on [&%s]\", nameRef); // missing semi\n            int numChars = Entities.codepointsForName(nameRef, multipointHolder);\n            if (numChars == 1) {\n                codeRef[0] = multipointHolder[0];\n                return codeRef;\n            } else if (numChars ==2) {\n                return multipointHolder;\n            } else {\n                Validate.fail(\"Unexpected characters returned for \" + nameRef);\n                return multipointHolder;\n            }\n        }\n    }\n\n    Token.Tag createTagPending(boolean start) {\n        tagPending = start ? startPending.reset() : endPending.reset();\n        return tagPending;\n    }\n\n    Token.XmlDecl createXmlDeclPending(boolean isDeclaration) {\n        Token.XmlDecl decl = xmlDeclPending.reset();\n        decl.isDeclaration = isDeclaration;\n        tagPending = decl;\n        return decl;\n    }\n\n    void emitTagPending() {\n        tagPending.finaliseTag();\n        emit(tagPending);\n    }\n\n    void createCommentPending() {\n        commentPending.reset();\n    }\n\n    void emitCommentPending() {\n        emit(commentPending);\n    }\n\n    void createBogusCommentPending() {\n        commentPending.reset();\n        commentPending.bogus = true;\n    }\n\n    void createDoctypePending() {\n        doctypePending.reset();\n    }\n\n    void emitDoctypePending() {\n        emit(doctypePending);\n    }\n\n    void createTempBuffer() {\n        dataBuffer.reset();\n    }\n\n    boolean isAppropriateEndTagToken() {\n        return lastStartTag != null && tagPending.name().equalsIgnoreCase(lastStartTag);\n    }\n\n    @Nullable String appropriateEndTagName() {\n        return lastStartTag; // could be null\n    }\n\n    /** Returns the closer sequence {@code </lastStart} */\n    String appropriateEndTagSeq() {\n        if (lastStartCloseSeq == null) // reset on start tag emit\n            lastStartCloseSeq = \"</\" + lastStartTag;\n        return lastStartCloseSeq;\n    }\n\n    void error(TokeniserState state) {\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, \"Unexpected character '%s' in input state [%s]\", reader.current(), state));\n    }\n\n    void eofError(TokeniserState state) {\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, \"Unexpectedly reached end of file (EOF) in input state [%s]\", state));\n    }\n\n    private void characterReferenceError(String message, Object... args) {\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, String.format(\"Invalid character reference: \" + message, args)));\n    }\n\n    void error(String errorMsg) {\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, errorMsg));\n    }\n\n    void error(String errorMsg, Object... args) {\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, errorMsg, args));\n    }\n\n    /**\n     * Utility method to consume reader and unescape entities found within.\n     * @param inAttribute if the text to be unescaped is in an attribute\n     * @return unescaped string from reader\n     */\n    String unescapeEntities(boolean inAttribute) {\n        StringBuilder builder = StringUtil.borrowBuilder();\n        while (!reader.isEmpty()) {\n            builder.append(reader.consumeTo('&'));\n            if (reader.matches('&')) {\n                reader.consume();\n                int[] c = consumeCharacterReference(null, inAttribute);\n                if (c == null || c.length==0)\n                    builder.append('&');\n                else {\n                    builder.appendCodePoint(c[0]);\n                    if (c.length == 2)\n                        builder.appendCodePoint(c[1]);\n                }\n\n            }\n        }\n        return StringUtil.releaseBuilder(builder);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/TokeniserState.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.nodes.DocumentType;\n\nimport static org.jsoup.nodes.Document.OutputSettings.Syntax.xml;\n\n/**\n * States and transition activations for the Tokeniser.\n */\nenum TokeniserState {\n    Data {\n        // in data state, gather characters until a character reference or tag is found\n        @Override void read(Tokeniser t, CharacterReader r) {\n            switch (r.current()) {\n                case '&':\n                    t.advanceTransition(CharacterReferenceInData);\n                    break;\n                case '<':\n                    t.advanceTransition(TagOpen);\n                    break;\n                case nullChar:\n                    t.error(this); // NOT replacement character (oddly?)\n                    t.emit(r.consume());\n                    break;\n                case eof:\n                    t.emit(new Token.EOF());\n                    break;\n                default:\n                    String data = r.consumeData();\n                    t.emit(data);\n                    break;\n            }\n        }\n    },\n    CharacterReferenceInData {\n        // from & in data\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readCharRef(t, Data);\n        }\n    },\n    Rcdata { // Rcdata has text with character references\n        /// handles data in title, textarea etc\n        @Override void read(Tokeniser t, CharacterReader r) {\n            switch (r.current()) {\n                case '&':\n                    t.advanceTransition(CharacterReferenceInRcdata);\n                    break;\n                case '<':\n                    t.advanceTransition(RcdataLessthanSign);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    r.advance();\n                    t.emit(replacementChar);\n                    break;\n                case eof:\n                    t.emit(new Token.EOF());\n                    break;\n                default:\n                    String data = r.consumeData();\n                    t.emit(data);\n                    break;\n            }\n        }\n    },\n    CharacterReferenceInRcdata {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readCharRef(t, Rcdata);\n        }\n    },\n    Rawtext {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readRawData(t, r, this, RawtextLessthanSign);\n        }\n    },\n    ScriptData {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readRawData(t, r, this, ScriptDataLessthanSign);\n        }\n    },\n    PLAINTEXT {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            switch (r.current()) {\n                case nullChar:\n                    t.error(this);\n                    r.advance();\n                    t.emit(replacementChar);\n                    break;\n                case eof:\n                    t.emit(new Token.EOF());\n                    break;\n                default:\n                    String data = r.consumeTo(nullChar);\n                    t.emit(data);\n                    break;\n            }\n        }\n    },\n    TagOpen {\n        // from < in data\n        @Override void read(Tokeniser t, CharacterReader r) {\n            switch (r.current()) {\n                case '!':\n                    t.advanceTransition(MarkupDeclarationOpen);\n                    break;\n                case '/':\n                    t.advanceTransition(EndTagOpen);\n                    break;\n                case '?':\n                    if (t.syntax == xml) {\n                        t.advanceTransition(MarkupProcessingOpen);\n                    } else {\n                        t.createBogusCommentPending();\n                        t.transition(BogusComment);\n                    }\n                    break;\n                default:\n                    if (r.matchesAsciiAlpha()) {\n                        t.createTagPending(true);\n                        t.transition(TagName);\n                    } else {\n                        t.error(this);\n                        t.emit('<'); // char that got us here\n                        t.transition(Data);\n                    }\n                    break;\n            }\n        }\n    },\n    EndTagOpen {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.isEmpty()) {\n                t.eofError(this);\n                t.emit(\"</\");\n                t.transition(Data);\n            } else if (r.matchesAsciiAlpha()) {\n                t.createTagPending(false);\n                t.transition(TagName);\n            } else if (r.matches('>')) {\n                t.error(this);\n                t.advanceTransition(Data);\n            } else {\n                t.error(this);\n                t.createBogusCommentPending();\n                t.commentPending.append('/'); // push the / back on that got us here\n                t.transition(BogusComment);\n            }\n        }\n    },\n    TagName {\n        // from < or </ in data, will have start or end tag pending\n        @Override void read(Tokeniser t, CharacterReader r) {\n            // previous TagOpen state did NOT consume, will have a letter char in current\n            String tagName = r.consumeTagName();\n            t.tagPending.appendTagName(tagName);\n\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeAttributeName);\n                    break;\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case nullChar: // replacement\n                    t.tagPending.appendTagName(replacementStr);\n                    break;\n                case eof: // should emit pending tag?\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default: // buffer underrun\n                    t.tagPending.appendTagName(c);\n            }\n        }\n    },\n    RcdataLessthanSign {\n        // from < in rcdata\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matches('/')) {\n                t.createTempBuffer();\n                t.advanceTransition(RCDATAEndTagOpen);\n            } else if (r.readFully() && r.matchesAsciiAlpha() && t.appropriateEndTagName() != null &&  !r.containsIgnoreCase(t.appropriateEndTagSeq())) {\n                // diverge from spec: got a start tag, but there's no appropriate end tag (</title>), so rather than\n                // consuming to EOF; break out here\n                t.tagPending = t.createTagPending(false).name(t.appropriateEndTagName());\n                t.emitTagPending();\n                t.transition(TagOpen); // straight into TagOpen, as we came from < and looks like we're on a start tag\n            } else {\n                t.emit('<');\n                t.transition(Rcdata);\n            }\n        }\n    },\n    RCDATAEndTagOpen {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                t.createTagPending(false);\n                t.tagPending.appendTagName(r.current());\n                t.dataBuffer.append(r.current());\n                t.advanceTransition(RCDATAEndTagName);\n            } else {\n                t.emit(\"</\");\n                t.transition(Rcdata);\n            }\n        }\n    },\n    RCDATAEndTagName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                String name = r.consumeTagName();\n                t.tagPending.appendTagName(name);\n                t.dataBuffer.append(name);\n                return;\n            }\n\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    if (t.isAppropriateEndTagToken())\n                        t.transition(BeforeAttributeName);\n                    else\n                        anythingElse(t, r);\n                    break;\n                case '/':\n                    if (t.isAppropriateEndTagToken())\n                        t.transition(SelfClosingStartTag);\n                    else\n                        anythingElse(t, r);\n                    break;\n                case '>':\n                    if (t.isAppropriateEndTagToken()) {\n                        t.emitTagPending();\n                        t.transition(Data);\n                    }\n                    else\n                        anythingElse(t, r);\n                    break;\n                default:\n                    anythingElse(t, r);\n            }\n        }\n\n        private void anythingElse(Tokeniser t, CharacterReader r) {\n            t.emit(\"</\");\n            t.emit(t.dataBuffer.value());\n            r.unconsume();\n            t.transition(Rcdata);\n        }\n    },\n    RawtextLessthanSign {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matches('/')) {\n                t.createTempBuffer();\n                t.advanceTransition(RawtextEndTagOpen);\n            } else {\n                t.emit('<');\n                t.transition(Rawtext);\n            }\n        }\n    },\n    RawtextEndTagOpen {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readEndTag(t, r, RawtextEndTagName, Rawtext);\n        }\n    },\n    RawtextEndTagName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            handleDataEndTag(t, r, Rawtext);\n        }\n    },\n    ScriptDataLessthanSign {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            switch (r.consume()) {\n                case '/':\n                    t.createTempBuffer();\n                    t.transition(ScriptDataEndTagOpen);\n                    break;\n                case '!':\n                    t.emit(\"<!\");\n                    t.transition(ScriptDataEscapeStart);\n                    break;\n                case eof:\n                    t.emit('<');\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default:\n                    t.emit('<');\n                    r.unconsume();\n                    t.transition(ScriptData);\n            }\n        }\n    },\n    ScriptDataEndTagOpen {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            readEndTag(t, r, ScriptDataEndTagName, ScriptData);\n        }\n    },\n    ScriptDataEndTagName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            handleDataEndTag(t, r, ScriptData);\n        }\n    },\n    ScriptDataEscapeStart {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matches('-')) {\n                t.emit('-');\n                t.advanceTransition(ScriptDataEscapeStartDash);\n            } else {\n                t.transition(ScriptData);\n            }\n        }\n    },\n    ScriptDataEscapeStartDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matches('-')) {\n                t.emit('-');\n                t.advanceTransition(ScriptDataEscapedDashDash);\n            } else {\n                t.transition(ScriptData);\n            }\n        }\n    },\n    ScriptDataEscaped {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.isEmpty()) {\n                t.eofError(this);\n                t.transition(Data);\n                return;\n            }\n\n            switch (r.current()) {\n                case '-':\n                    t.emit('-');\n                    t.advanceTransition(ScriptDataEscapedDash);\n                    break;\n                case '<':\n                    t.advanceTransition(ScriptDataEscapedLessthanSign);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    r.advance();\n                    t.emit(replacementChar);\n                    break;\n                default:\n                    String data = r.consumeToAny('-', '<', nullChar);\n                    t.emit(data);\n            }\n        }\n    },\n    ScriptDataEscapedDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.isEmpty()) {\n                t.eofError(this);\n                t.transition(Data);\n                return;\n            }\n\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.emit(c);\n                    t.transition(ScriptDataEscapedDashDash);\n                    break;\n                case '<':\n                    t.transition(ScriptDataEscapedLessthanSign);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.emit(replacementChar);\n                    t.transition(ScriptDataEscaped);\n                    break;\n                default:\n                    t.emit(c);\n                    t.transition(ScriptDataEscaped);\n            }\n        }\n    },\n    ScriptDataEscapedDashDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.isEmpty()) {\n                t.eofError(this);\n                t.transition(Data);\n                return;\n            }\n\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.emit(c);\n                    break;\n                case '<':\n                    t.transition(ScriptDataEscapedLessthanSign);\n                    break;\n                case '>':\n                    t.emit(c);\n                    t.transition(ScriptData);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.emit(replacementChar);\n                    t.transition(ScriptDataEscaped);\n                    break;\n                default:\n                    t.emit(c);\n                    t.transition(ScriptDataEscaped);\n            }\n        }\n    },\n    ScriptDataEscapedLessthanSign {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                t.createTempBuffer();\n                t.dataBuffer.append(r.current());\n                t.emit('<');\n                t.emit(r.current());\n                t.advanceTransition(ScriptDataDoubleEscapeStart);\n            } else if (r.matches('/')) {\n                t.createTempBuffer();\n                t.advanceTransition(ScriptDataEscapedEndTagOpen);\n            } else {\n                t.emit('<');\n                t.transition(ScriptDataEscaped);\n            }\n        }\n    },\n    ScriptDataEscapedEndTagOpen {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                t.createTagPending(false);\n                t.tagPending.appendTagName(r.current());\n                t.dataBuffer.append(r.current());\n                t.advanceTransition(ScriptDataEscapedEndTagName);\n            } else {\n                t.emit(\"</\");\n                t.transition(ScriptDataEscaped);\n            }\n        }\n    },\n    ScriptDataEscapedEndTagName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            handleDataEndTag(t, r, ScriptDataEscaped);\n        }\n    },\n    ScriptDataDoubleEscapeStart {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            handleDataDoubleEscapeTag(t, r, ScriptDataDoubleEscaped, ScriptDataEscaped);\n        }\n    },\n    ScriptDataDoubleEscaped {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.current();\n            switch (c) {\n                case '-':\n                    t.emit(c);\n                    t.advanceTransition(ScriptDataDoubleEscapedDash);\n                    break;\n                case '<':\n                    t.emit(c);\n                    t.advanceTransition(ScriptDataDoubleEscapedLessthanSign);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    r.advance();\n                    t.emit(replacementChar);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default:\n                    String data = r.consumeToAny('-', '<', nullChar);\n                    t.emit(data);\n            }\n        }\n    },\n    ScriptDataDoubleEscapedDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.emit(c);\n                    t.transition(ScriptDataDoubleEscapedDashDash);\n                    break;\n                case '<':\n                    t.emit(c);\n                    t.transition(ScriptDataDoubleEscapedLessthanSign);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.emit(replacementChar);\n                    t.transition(ScriptDataDoubleEscaped);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default:\n                    t.emit(c);\n                    t.transition(ScriptDataDoubleEscaped);\n            }\n        }\n    },\n    ScriptDataDoubleEscapedDashDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.emit(c);\n                    break;\n                case '<':\n                    t.emit(c);\n                    t.transition(ScriptDataDoubleEscapedLessthanSign);\n                    break;\n                case '>':\n                    t.emit(c);\n                    t.transition(ScriptData);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.emit(replacementChar);\n                    t.transition(ScriptDataDoubleEscaped);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default:\n                    t.emit(c);\n                    t.transition(ScriptDataDoubleEscaped);\n            }\n        }\n    },\n    ScriptDataDoubleEscapedLessthanSign {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matches('/')) {\n                t.emit('/');\n                t.createTempBuffer();\n                t.advanceTransition(ScriptDataDoubleEscapeEnd);\n            } else {\n                t.transition(ScriptDataDoubleEscaped);\n            }\n        }\n    },\n    ScriptDataDoubleEscapeEnd {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            handleDataDoubleEscapeTag(t,r, ScriptDataEscaped, ScriptDataDoubleEscaped);\n        }\n    },\n    BeforeAttributeName {\n        // from tagname <xxx\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break; // ignore whitespace\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case nullChar:\n                    r.unconsume();\n                    t.error(this);\n                    t.tagPending.newAttribute();\n                    t.transition(AttributeName);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                case '\"':\n                case '\\'':\n                case '=':\n                    t.error(this);\n                    t.tagPending.newAttribute();\n                    t.tagPending.appendAttributeName(c, r.pos()-1, r.pos());\n                    t.transition(AttributeName);\n                    break;\n                case '?': // Handle trailing ? in <?xml...?>\n                    if (t.tagPending instanceof Token.XmlDecl)\n                        break;\n                    // otherwise fall through to default\n                default: // A-Z, anything else\n                    t.tagPending.newAttribute();\n                    r.unconsume();\n                    t.transition(AttributeName);\n            }\n        }\n    },\n    AttributeName {\n        // from before attribute name\n        @Override void read(Tokeniser t, CharacterReader r) {\n            int pos = r.pos();\n            String name = r.consumeToAnySorted(attributeNameCharsSorted); // spec deviate - consume and emit nulls in one hit vs stepping\n            t.tagPending.appendAttributeName(name, pos, r.pos());\n\n            pos = r.pos();\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(AfterAttributeName);\n                    break;\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '=':\n                    t.transition(BeforeAttributeValue);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                case '\"':\n                case '\\'':\n                case '<':\n                    t.error(this);\n                    t.tagPending.appendAttributeName(c, pos, r.pos());\n                    break;\n                case '?':\n                    if (t.syntax == xml && t.tagPending instanceof Token.XmlDecl) {\n                        t.transition(AfterAttributeName);\n                        break;\n                    } // otherwise default - take it\n                default: // buffer underrun\n                    t.tagPending.appendAttributeName(c, pos, r.pos());\n            }\n        }\n    },\n    AfterAttributeName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    // ignore\n                    break;\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '=':\n                    t.transition(BeforeAttributeValue);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.tagPending.appendAttributeName(replacementChar, r.pos()-1, r.pos());\n                    t.transition(AttributeName);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                case '\"':\n                case '\\'':\n                case '<':\n                    t.error(this);\n                    t.tagPending.newAttribute();\n                    t.tagPending.appendAttributeName(c, r.pos()-1, r.pos());\n                    t.transition(AttributeName);\n                    break;\n                default: // A-Z, anything else\n                    t.tagPending.newAttribute();\n                    r.unconsume();\n                    t.transition(AttributeName);\n            }\n        }\n    },\n    BeforeAttributeValue {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    // ignore\n                    break;\n                case '\"':\n                    t.transition(AttributeValue_doubleQuoted);\n                    break;\n                case '&':\n                    r.unconsume();\n                    t.transition(AttributeValue_unquoted);\n                    break;\n                case '\\'':\n                    t.transition(AttributeValue_singleQuoted);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(replacementChar, r.pos()-1, r.pos());\n                    t.transition(AttributeValue_unquoted);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case '<':\n                case '=':\n                case '`':\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(c, r.pos()-1, r.pos());\n                    t.transition(AttributeValue_unquoted);\n                    break;\n                default:\n                    r.unconsume();\n                    t.transition(AttributeValue_unquoted);\n            }\n        }\n    },\n    AttributeValue_doubleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            int pos = r.pos();\n            String value = r.consumeAttributeQuoted(false);\n            if (value.length() > 0)\n                t.tagPending.appendAttributeValue(value, pos, r.pos());\n            else\n                t.tagPending.setEmptyAttributeValue();\n\n            pos = r.pos();\n            char c = r.consume();\n            switch (c) {\n                case '\"':\n                    t.transition(AfterAttributeValue_quoted);\n                    break;\n                case '&':\n                    int[] ref = t.consumeCharacterReference('\"', true);\n                    if (ref != null)\n                        t.tagPending.appendAttributeValue(ref, pos, r.pos());\n                    else\n                        t.tagPending.appendAttributeValue('&', pos, r.pos());\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(replacementChar, pos, r.pos());\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default: // hit end of buffer in first read, still in attribute\n                    t.tagPending.appendAttributeValue(c, pos, r.pos());\n            }\n        }\n    },\n    AttributeValue_singleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            int pos = r.pos();\n            String value = r.consumeAttributeQuoted(true);\n            if (value.length() > 0)\n                t.tagPending.appendAttributeValue(value, pos, r.pos());\n            else\n                t.tagPending.setEmptyAttributeValue();\n\n            pos = r.pos();\n            char c = r.consume();\n            switch (c) {\n                case '\\'':\n                    t.transition(AfterAttributeValue_quoted);\n                    break;\n                case '&':\n                    int[] ref = t.consumeCharacterReference('\\'', true);\n                    if (ref != null)\n                        t.tagPending.appendAttributeValue(ref, pos, r.pos());\n                    else\n                        t.tagPending.appendAttributeValue('&', pos, r.pos());\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(replacementChar, pos, r.pos());\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default: // hit end of buffer in first read, still in attribute\n                    t.tagPending.appendAttributeValue(c, pos, r.pos());\n            }\n        }\n    },\n    AttributeValue_unquoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            int pos = r.pos();\n            String value = r.consumeToAnySorted(attributeValueUnquoted);\n            if (value.length() > 0)\n                t.tagPending.appendAttributeValue(value, pos, r.pos());\n\n            pos = r.pos();\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeAttributeName);\n                    break;\n                case '&':\n                    int[] ref = t.consumeCharacterReference('>', true);\n                    if (ref != null)\n                        t.tagPending.appendAttributeValue(ref, pos, r.pos());\n                    else\n                        t.tagPending.appendAttributeValue('&', pos, r.pos());\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(replacementChar, pos, r.pos());\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                case '\"':\n                case '\\'':\n                case '<':\n                case '=':\n                case '`':\n                    t.error(this);\n                    t.tagPending.appendAttributeValue(c, pos, r.pos());\n                    break;\n                default: // hit end of buffer in first read, still in attribute\n                    t.tagPending.appendAttributeValue(c, pos, r.pos());\n            }\n\n        }\n    },\n    // CharacterReferenceInAttributeValue state handled inline\n    AfterAttributeValue_quoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeAttributeName);\n                    break;\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                case '?': // Handle trailing ? in <?xml...?>\n                    if (t.tagPending instanceof Token.XmlDecl)\n                        break;\n                    // otherwise fall through to default\n                default:\n                    r.unconsume();\n                    t.error(this);\n                    t.transition(BeforeAttributeName);\n            }\n\n        }\n    },\n    SelfClosingStartTag {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '>':\n                    t.tagPending.selfClosing = true;\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.transition(Data);\n                    break;\n                default:\n                    r.unconsume();\n                    t.error(this);\n                    t.transition(BeforeAttributeName);\n            }\n        }\n    },\n    BogusComment {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            // todo: handle bogus comment starting from eof. when does that trigger?\n            t.commentPending.append(r.consumeTo('>'));\n            // todo: replace nullChar with replaceChar\n            char next = r.current();\n            if (next == '>' || next == eof) {\n                r.consume();\n                t.emitCommentPending();\n                t.transition(Data);\n            }\n        }\n    },\n    MarkupDeclarationOpen { // from <!\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchConsume(\"--\")) {\n                t.createCommentPending();\n                t.transition(CommentStart);\n            } else if (r.matchConsumeIgnoreCase(\"DOCTYPE\")) {\n                t.transition(Doctype);\n            } else if (r.matchConsume(\"[CDATA[\")) {\n                // todo: should actually check current namespace, and only non-html allows cdata. until namespace\n                // is implemented properly, keep handling as cdata\n                //} else if (!t.currentNodeInHtmlNS() && r.matchConsume(\"[CDATA[\")) {\n                t.createTempBuffer();\n                t.transition(CdataSection);\n            } else {\n                if (t.syntax == xml && r.matchesAsciiAlpha()) {\n                    t.createXmlDeclPending(true);\n                    t.transition(TagName); // treat <!ENTITY as XML Declaration, with tag-like handling\n                } else {\n                    t.error(this);\n                    t.createBogusCommentPending();\n                    t.transition(BogusComment);\n                }\n            }\n        }\n    },\n    MarkupProcessingOpen { // From <? in syntax XML\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                t.createXmlDeclPending(false);\n                t.transition(TagName); // treat <?xml... as XML Declaration (processing instruction), with tag-like handling\n            } else {\n                t.error(this);\n                t.createBogusCommentPending();\n                t.commentPending.append('?'); // push the ? to the start of the comment\n                t.transition(BogusComment);\n            }\n        }\n    },\n    CommentStart {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.transition(CommentStartDash);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.commentPending.append(replacementChar);\n                    t.transition(Comment);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    r.unconsume();\n                    t.transition(Comment);\n            }\n        }\n    },\n    CommentStartDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.transition(CommentEnd);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.commentPending.append(replacementChar);\n                    t.transition(Comment);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.commentPending.append(c);\n                    t.transition(Comment);\n            }\n        }\n    },\n    Comment {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.current();\n            switch (c) {\n                case '-':\n                    t.advanceTransition(CommentEndDash);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    r.advance();\n                    t.commentPending.append(replacementChar);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.commentPending.append(r.consumeToAny('-', nullChar));\n            }\n        }\n    },\n    CommentEndDash {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.transition(CommentEnd);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.commentPending.append('-').append(replacementChar);\n                    t.transition(Comment);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.commentPending.append('-').append(c);\n                    t.transition(Comment);\n            }\n        }\n    },\n    CommentEnd {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '>':\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.commentPending.append(\"--\").append(replacementChar);\n                    t.transition(Comment);\n                    break;\n                case '!':\n                    t.transition(CommentEndBang);\n                    break;\n                case '-':\n                    t.commentPending.append('-');\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.commentPending.append(\"--\").append(c);\n                    t.transition(Comment);\n            }\n        }\n    },\n    CommentEndBang {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '-':\n                    t.commentPending.append(\"--!\");\n                    t.transition(CommentEndDash);\n                    break;\n                case '>':\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.commentPending.append(\"--!\").append(replacementChar);\n                    t.transition(Comment);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.emitCommentPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.commentPending.append(\"--!\").append(c);\n                    t.transition(Comment);\n            }\n        }\n    },\n    Doctype {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeDoctypeName);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    // note: fall through to > case\n                case '>': // catch invalid <!DOCTYPE>\n                    t.error(this);\n                    t.createDoctypePending();\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.transition(BeforeDoctypeName);\n            }\n        }\n    },\n    BeforeDoctypeName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                t.createDoctypePending();\n                t.transition(DoctypeName);\n                return;\n            }\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break; // ignore whitespace\n                case nullChar:\n                    t.error(this);\n                    t.createDoctypePending();\n                    t.doctypePending.name.append(replacementChar);\n                    t.transition(DoctypeName);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.createDoctypePending();\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.createDoctypePending();\n                    t.doctypePending.name.append(c);\n                    t.transition(DoctypeName);\n            }\n        }\n    },\n    DoctypeName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.matchesAsciiAlpha()) {\n                String name = r.consumeLetterSequence();\n                t.doctypePending.name.append(name);\n                return;\n            }\n            char c = r.consume();\n            switch (c) {\n                case '>':\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(AfterDoctypeName);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.doctypePending.name.append(replacementChar);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.doctypePending.name.append(c);\n            }\n        }\n    },\n    AfterDoctypeName {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            if (r.isEmpty()) {\n                t.eofError(this);\n                t.doctypePending.forceQuirks = true;\n                t.emitDoctypePending();\n                t.transition(Data);\n                return;\n            }\n            if (r.matchesAny('\\t', '\\n', '\\r', '\\f', ' '))\n                r.advance(); // ignore whitespace\n            else if (r.matches('>')) {\n                t.emitDoctypePending();\n                t.advanceTransition(Data);\n            } else if (r.matchConsumeIgnoreCase(DocumentType.PUBLIC_KEY)) {\n                t.doctypePending.pubSysKey = DocumentType.PUBLIC_KEY;\n                t.transition(AfterDoctypePublicKeyword);\n            } else if (r.matchConsumeIgnoreCase(DocumentType.SYSTEM_KEY)) {\n                t.doctypePending.pubSysKey = DocumentType.SYSTEM_KEY;\n                t.transition(AfterDoctypeSystemKeyword);\n            } else {\n                t.error(this);\n                t.doctypePending.forceQuirks = true;\n                t.advanceTransition(BogusDoctype);\n            }\n\n        }\n    },\n    AfterDoctypePublicKeyword {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeDoctypePublicIdentifier);\n                    break;\n                case '\"':\n                    t.error(this);\n                    // set public id to empty string\n                    t.transition(DoctypePublicIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    t.error(this);\n                    // set public id to empty string\n                    t.transition(DoctypePublicIdentifier_singleQuoted);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.transition(BogusDoctype);\n            }\n        }\n    },\n    BeforeDoctypePublicIdentifier {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break;\n                case '\"':\n                    // set public id to empty string\n                    t.transition(DoctypePublicIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    // set public id to empty string\n                    t.transition(DoctypePublicIdentifier_singleQuoted);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.transition(BogusDoctype);\n            }\n        }\n    },\n    DoctypePublicIdentifier_doubleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\"':\n                    t.transition(AfterDoctypePublicIdentifier);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.doctypePending.publicIdentifier.append(replacementChar);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.doctypePending.publicIdentifier.append(c);\n            }\n        }\n    },\n    DoctypePublicIdentifier_singleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\'':\n                    t.transition(AfterDoctypePublicIdentifier);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.doctypePending.publicIdentifier.append(replacementChar);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.doctypePending.publicIdentifier.append(c);\n            }\n        }\n    },\n    AfterDoctypePublicIdentifier {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BetweenDoctypePublicAndSystemIdentifiers);\n                    break;\n                case '>':\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case '\"':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_singleQuoted);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.transition(BogusDoctype);\n            }\n        }\n    },\n    BetweenDoctypePublicAndSystemIdentifiers {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break;\n                case '>':\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case '\"':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_singleQuoted);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.transition(BogusDoctype);\n            }\n        }\n    },\n    AfterDoctypeSystemKeyword {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeDoctypeSystemIdentifier);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case '\"':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    t.error(this);\n                    // system id empty\n                    t.transition(DoctypeSystemIdentifier_singleQuoted);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n            }\n        }\n    },\n    BeforeDoctypeSystemIdentifier {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break;\n                case '\"':\n                    // set system id to empty string\n                    t.transition(DoctypeSystemIdentifier_doubleQuoted);\n                    break;\n                case '\\'':\n                    // set public id to empty string\n                    t.transition(DoctypeSystemIdentifier_singleQuoted);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.transition(BogusDoctype);\n            }\n        }\n    },\n    DoctypeSystemIdentifier_doubleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\"':\n                    t.transition(AfterDoctypeSystemIdentifier);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.doctypePending.systemIdentifier.append(replacementChar);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.doctypePending.systemIdentifier.append(c);\n            }\n        }\n    },\n    DoctypeSystemIdentifier_singleQuoted {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\'':\n                    t.transition(AfterDoctypeSystemIdentifier);\n                    break;\n                case nullChar:\n                    t.error(this);\n                    t.doctypePending.systemIdentifier.append(replacementChar);\n                    break;\n                case '>':\n                    t.error(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.doctypePending.systemIdentifier.append(c);\n            }\n        }\n    },\n    AfterDoctypeSystemIdentifier {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    break;\n                case '>':\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.eofError(this);\n                    t.doctypePending.forceQuirks = true;\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.error(this);\n                    t.transition(BogusDoctype);\n                    // NOT force quirks\n            }\n        }\n    },\n    BogusDoctype {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            char c = r.consume();\n            switch (c) {\n                case '>':\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                case eof:\n                    t.emitDoctypePending();\n                    t.transition(Data);\n                    break;\n                default:\n                    // ignore char\n                    break;\n            }\n        }\n    },\n    CdataSection {\n        @Override void read(Tokeniser t, CharacterReader r) {\n            String data = r.consumeTo(\"]]>\");\n            t.dataBuffer.append(data);\n            if (r.matchConsume(\"]]>\") || r.isEmpty()) {\n                t.emit(new Token.CData(t.dataBuffer.value()));\n                t.transition(Data);\n            }// otherwise, buffer underrun, stay in data section\n        }\n    };\n\n\n    abstract void read(Tokeniser t, CharacterReader r);\n\n    static final char nullChar = '\\u0000';\n    // char searches. must be sorted, used in inSorted. MUST update TokeniserStateTest if more arrays are added.\n    static final char[] attributeNameCharsSorted = new char[]{'\\t', '\\n', '\\f', '\\r', ' ', '\"', '\\'', '/', '<', '=', '>', '?'};\n    static final char[] attributeValueUnquoted = new char[]{nullChar, '\\t', '\\n', '\\f', '\\r', ' ', '\"', '&', '\\'', '<', '=', '>', '`'};\n\n    private static final char replacementChar = Tokeniser.replacementChar;\n    private static final String replacementStr = String.valueOf(Tokeniser.replacementChar);\n    private static final char eof = CharacterReader.EOF;\n\n    /**\n     * Handles RawtextEndTagName, ScriptDataEndTagName, and ScriptDataEscapedEndTagName. Same body impl, just\n     * different else exit transitions.\n     */\n    private static void handleDataEndTag(Tokeniser t, CharacterReader r, TokeniserState elseTransition) {\n        if (r.matchesAsciiAlpha()) {\n            String name = r.consumeTagName();\n            t.tagPending.appendTagName(name);\n            t.dataBuffer.append(name);\n            return;\n        }\n\n        boolean needsExitTransition = false;\n        if (t.isAppropriateEndTagToken() && !r.isEmpty()) {\n            char c = r.consume();\n            switch (c) {\n                case '\\t':\n                case '\\n':\n                case '\\r':\n                case '\\f':\n                case ' ':\n                    t.transition(BeforeAttributeName);\n                    break;\n                case '/':\n                    t.transition(SelfClosingStartTag);\n                    break;\n                case '>':\n                    t.emitTagPending();\n                    t.transition(Data);\n                    break;\n                default:\n                    t.dataBuffer.append(c);\n                    needsExitTransition = true;\n            }\n        } else {\n            needsExitTransition = true;\n        }\n\n        if (needsExitTransition) {\n            t.emit(\"</\");\n            t.emit(t.dataBuffer.value());\n            t.transition(elseTransition);\n        }\n    }\n\n    private static void readRawData(Tokeniser t, CharacterReader r, TokeniserState current, TokeniserState advance) {\n        switch (r.current()) {\n            case '<':\n                t.advanceTransition(advance);\n                break;\n            case nullChar:\n                t.error(current);\n                r.advance();\n                t.emit(replacementChar);\n                break;\n            case eof:\n                t.emit(new Token.EOF());\n                break;\n            default:\n                String data = r.consumeRawData();\n                t.emit(data);\n                break;\n        }\n    }\n\n    private static void readCharRef(Tokeniser t, TokeniserState advance) {\n        int[] c = t.consumeCharacterReference(null, false);\n        if (c == null)\n            t.emit('&');\n        else\n            t.emit(c);\n        t.transition(advance);\n    }\n\n    private static void readEndTag(Tokeniser t, CharacterReader r, TokeniserState a, TokeniserState b) {\n        if (r.matchesAsciiAlpha()) {\n            t.createTagPending(false);\n            t.transition(a);\n        } else {\n            t.emit(\"</\");\n            t.transition(b);\n        }\n    }\n\n    private static void handleDataDoubleEscapeTag(Tokeniser t, CharacterReader r, TokeniserState primary, TokeniserState fallback) {\n        if (r.matchesAsciiAlpha()) {\n            String name = r.consumeLetterSequence();\n            t.dataBuffer.append(name);\n            t.emit(name);\n            return;\n        }\n\n        char c = r.consume();\n        switch (c) {\n            case '\\t':\n            case '\\n':\n            case '\\r':\n            case '\\f':\n            case ' ':\n            case '/':\n            case '>':\n                if (t.dataBuffer.value().equals(\"script\"))\n                    t.transition(primary);\n                else\n                    t.transition(fallback);\n                t.emit(c);\n                break;\n            default:\n                r.unconsume();\n                t.transition(fallback);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/TreeBuilder.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.Range;\nimport org.jsoup.select.NodeVisitor;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.Reader;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\n\n/**\n * @author Jonathan Hedley\n */\nabstract class TreeBuilder {\n    protected Parser parser;\n    CharacterReader reader;\n    Tokeniser tokeniser;\n    Document doc; // current doc we are building into\n    ArrayList<Element> stack; // the stack of open elements\n    String baseUri; // current base uri, for creating new elements\n    Token currentToken; // currentToken is used for error and source position tracking. Null at start of fragment parse\n    ParseSettings settings;\n    TagSet tagSet; // the tags we're using in this parse\n    @Nullable NodeVisitor nodeListener; // optional listener for node add / removes\n\n    private Token.StartTag start; // start tag to process\n    private final Token.EndTag end  = new Token.EndTag(this);\n    abstract ParseSettings defaultSettings();\n\n    boolean trackSourceRange;  // optionally tracks the source range of nodes and attributes\n\n    void initialiseParse(Reader input, String baseUri, Parser parser) {\n        Validate.notNullParam(input, \"input\");\n        Validate.notNullParam(baseUri, \"baseUri\");\n        Validate.notNull(parser);\n\n        doc = new Document(parser.defaultNamespace(), baseUri);\n        doc.parser(parser);\n        this.parser = parser;\n        settings = parser.settings();\n        reader = new CharacterReader(input);\n        trackSourceRange = parser.isTrackPosition();\n        reader.trackNewlines(parser.isTrackErrors() || trackSourceRange); // when tracking errors or source ranges, enable newline tracking for better legibility\n        if (parser.isTrackErrors()) parser.getErrors().clear();\n        tokeniser = new Tokeniser(this);\n        stack = new ArrayList<>(32);\n        tagSet = parser.tagSet();\n        start = new Token.StartTag(this);\n        currentToken = start; // init current token to the virtual start token.\n        this.baseUri = baseUri;\n        onNodeInserted(doc);\n    }\n\n    void completeParse() {\n        // tidy up - as the Parser and Treebuilder are retained in document for settings / fragments\n        if (reader == null) return;\n        reader.close();\n        reader = null;\n        tokeniser = null;\n        stack = null;\n    }\n\n    Document parse(Reader input, String baseUri, Parser parser) {\n        initialiseParse(input, baseUri, parser);\n        runParser();\n        return doc;\n    }\n\n    List<Node> parseFragment(Reader inputFragment, @Nullable Element context, String baseUri, Parser parser) {\n        initialiseParse(inputFragment, baseUri, parser);\n        initialiseParseFragment(context);\n        runParser();\n        return completeParseFragment();\n    }\n\n    void initialiseParseFragment(@Nullable Element context) {\n        // in Html, sets up context; no-op in XML\n    }\n\n    abstract List<Node> completeParseFragment();\n\n    /** Set the node listener, which will then get callbacks for node insert and removals. */\n    void nodeListener(NodeVisitor nodeListener) {\n        this.nodeListener = nodeListener;\n    }\n\n    /**\n     Create a new copy of this TreeBuilder\n     @return copy, ready for a new parse\n     */\n    abstract TreeBuilder newInstance();\n\n    void runParser() {\n        do {} while (stepParser()); // run until stepParser sees EOF\n        completeParse();\n    }\n\n    boolean stepParser() {\n        // if we have reached the end already, step by popping off the stack, to hit nodeRemoved callbacks:\n        if (currentToken.type == Token.TokenType.EOF) {\n            if (stack == null) {\n                return false;\n            } if (stack.isEmpty()) {\n                onNodeClosed(doc); // the root doc is not on the stack, so let this final step close it\n                stack = null;\n                return true;\n            }\n            pop();\n            return true;\n        }\n        final Token token = tokeniser.read();\n        currentToken = token;\n        process(token);\n        token.reset();\n        return true;\n    }\n\n    abstract boolean process(Token token);\n\n    boolean processStartTag(String name) {\n        // these are \"virtual\" start tags (auto-created by the treebuilder), so not tracking the start position\n        final Token.StartTag start = this.start;\n        if (currentToken == start) { // don't recycle an in-use token\n            return process(new Token.StartTag(this).name(name));\n        }\n        return process(start.reset().name(name));\n    }\n\n    boolean processStartTag(String name, Attributes attrs) {\n        final Token.StartTag start = this.start;\n        if (currentToken == start) { // don't recycle an in-use token\n            return process(new Token.StartTag(this).nameAttr(name, attrs));\n        }\n        start.reset();\n        start.nameAttr(name, attrs);\n        return process(start);\n    }\n\n    boolean processEndTag(String name) {\n        if (currentToken == end) { // don't recycle an in-use token\n            return process(new Token.EndTag(this).name(name));\n        }\n        return process(end.reset().name(name));\n    }\n\n    /**\n     Removes the last Element from the stack, hits onNodeClosed, and then returns it.\n     * @return\n     */\n    Element pop() {\n        int size = stack.size();\n        Element removed = stack.remove(size - 1);\n        onNodeClosed(removed);\n        return removed;\n    }\n\n    /**\n     Adds the specified Element to the end of the stack, and hits onNodeInserted.\n     * @param element\n     */\n    final void push(Element element) {\n        stack.add(element);\n        onNodeInserted(element);\n    }\n\n    /**\n     Ensures the stack respects {@link Parser#getMaxDepth()} by closing the deepest open elements until there is room for\n     a new insertion.\n     */\n    final void enforceStackDepthLimit() {\n        final int maxDepth = parser.getMaxDepth();\n        if (maxDepth == Integer.MAX_VALUE) return;\n        while (stack.size() >= maxDepth) {\n            Element trimmed = pop();\n            onStackPrunedForDepth(trimmed);\n        }\n    }\n\n    /**\n     Hook for the HTML Tree Builder that needs to clean up when an element is removed due to the depth limit\n     */\n    void onStackPrunedForDepth(Element element) {\n        // default no-op\n    }\n\n    /**\n     Default maximum depth for parsers using this tree builder.\n     */\n    int defaultMaxDepth() {\n        return 512;\n    }\n\n    /**\n     Get the current element (last on the stack). If all items have been removed, returns the document instead\n     (which might not actually be on the stack; use stack.size() == 0 to test if required.\n     @return the last element on the stack, if any; or the root document\n     */\n    Element currentElement() {\n        int size = stack.size();\n        return size > 0 ? stack.get(size-1) : doc;\n    }\n\n    /**\n     Checks if the Current Element's normal name equals the supplied name, in the HTML namespace.\n     @param normalName name to check\n     @return true if there is a current element on the stack, and its name equals the supplied\n     */\n    boolean currentElementIs(String normalName) {\n        if (stack.size() == 0)\n            return false;\n        Element current = currentElement();\n        return current != null && current.normalName().equals(normalName)\n            && current.tag().namespace().equals(NamespaceHtml);\n    }\n\n    /**\n     Checks if the Current Element's normal name equals the supplied name, in the specified namespace.\n     @param normalName name to check\n     @param namespace the namespace\n     @return true if there is a current element on the stack, and its name equals the supplied\n     */\n    boolean currentElementIs(String normalName, String namespace) {\n        if (stack.size() == 0)\n            return false;\n        Element current = currentElement();\n        return current != null && current.normalName().equals(normalName)\n            && current.tag().namespace().equals(namespace);\n    }\n\n    /**\n     * If the parser is tracking errors, add an error at the current position.\n     * @param msg error message\n     */\n    void error(String msg) {\n        error(msg, (Object[]) null);\n    }\n\n    /**\n     * If the parser is tracking errors, add an error at the current position.\n     * @param msg error message template\n     * @param args template arguments\n     */\n    void error(String msg, Object... args) {\n        ParseErrorList errors = parser.getErrors();\n        if (errors.canAddError())\n            errors.add(new ParseError(reader, msg, args));\n    }\n\n    Tag tagFor(String tagName, String normalName, String namespace, ParseSettings settings) {\n        return tagSet.valueOf(tagName, normalName, namespace, settings.preserveTagCase());\n    }\n\n    Tag tagFor(Token.Tag token) {\n        return tagSet.valueOf(token.name(), token.normalName, defaultNamespace(), settings.preserveTagCase());\n    }\n\n    /**\n     Gets the default namespace for this TreeBuilder\n     * @return the default namespace\n     */\n    String defaultNamespace() {\n        return NamespaceHtml;\n    }\n\n    TagSet defaultTagSet() {\n        return TagSet.Html();\n    }\n\n    /**\n     Called by implementing TreeBuilders when a node has been inserted. This implementation includes optionally tracking\n     the source range of the node.  @param node the node that was just inserted\n     */\n    void onNodeInserted(Node node) {\n        trackNodePosition(node, true);\n\n        if (nodeListener != null)\n            nodeListener.head(node, stack.size());\n    }\n\n    /**\n     Called by implementing TreeBuilders when a node is explicitly closed. This implementation includes optionally\n     tracking the closing source range of the node.  @param node the node being closed\n     */\n    void onNodeClosed(Node node) {\n        trackNodePosition(node, false);\n\n        if (nodeListener != null)\n            nodeListener.tail(node, stack.size());\n    }\n\n    void trackNodePosition(Node node, boolean isStart) {\n        if (!trackSourceRange) return;\n\n        final Token token = currentToken;\n        int startPos = token.startPos();\n        int endPos = token.endPos();\n\n        // handle implicit element open / closes.\n        if (node instanceof Element) {\n            final Element el = (Element) node;\n            if (token.isEOF()) {\n                if (el.endSourceRange().isTracked())\n                    return; // /body and /html are left on stack until EOF, don't reset them\n                startPos = endPos = reader.pos();\n            } else if (isStart) { // opening tag\n                if  (!token.isStartTag() || !el.normalName().equals(token.asStartTag().normalName)) {\n                    endPos = startPos;\n                }\n            } else { // closing tag\n                if (!el.tag().isEmpty() && !el.tag().isSelfClosing()) {\n                    if (!token.isEndTag() || !el.normalName().equals(token.asEndTag().normalName)) {\n                        endPos = startPos;\n                    }\n                }\n            }\n        }\n\n        Range.Position startPosition = new Range.Position\n            (startPos, reader.lineNumber(startPos), reader.columnNumber(startPos));\n        Range.Position endPosition = new Range.Position\n            (endPos, reader.lineNumber(endPos), reader.columnNumber(endPos));\n        Range range = new Range(startPosition, endPosition);\n        node.attributes().userData(isStart ? SharedConstants.RangeKey : SharedConstants.EndRangeKey, range);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/XmlTreeBuilder.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.CDataNode;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.DocumentType;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Entities;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.nodes.XmlDeclaration;\nimport org.jsoup.select.Elements;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.util.ArrayDeque;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.jsoup.parser.Parser.NamespaceXml;\n\n/**\n * Use the {@code XmlTreeBuilder} when you want to parse XML without any of the HTML DOM rules being applied to the\n * document.\n * <p>Usage example: {@code Document xmlDoc = Jsoup.parse(html, baseUrl, Parser.xmlParser());}</p>\n *\n * @author Jonathan Hedley\n */\npublic class XmlTreeBuilder extends TreeBuilder {\n    static final String XmlnsKey = \"xmlns\";\n    static final String XmlnsPrefix = \"xmlns:\";\n    private final ArrayDeque<HashMap<String, String>> namespacesStack = new ArrayDeque<>(); // stack of namespaces, prefix => urn\n\n    @Override ParseSettings defaultSettings() {\n        return ParseSettings.preserveCase;\n    }\n\n    @Override\n    protected void initialiseParse(Reader input, String baseUri, Parser parser) {\n        super.initialiseParse(input, baseUri, parser);\n        doc.outputSettings()\n            .syntax(Document.OutputSettings.Syntax.xml)\n            .escapeMode(Entities.EscapeMode.xhtml)\n            .prettyPrint(false); // as XML, we don't understand what whitespace is significant or not\n\n        namespacesStack.clear();\n        HashMap<String, String> ns = new HashMap<>();\n        ns.put(\"xml\", NamespaceXml);\n        ns.put(\"\", NamespaceXml);\n        namespacesStack.push(ns);\n    }\n\n    @Override\n    void initialiseParseFragment(@Nullable Element context) {\n        super.initialiseParseFragment(context);\n        if (context == null) return;\n\n        // transition to the tag's text state if available\n        TokeniserState textState = context.tag().textState();\n        if (textState != null) tokeniser.transition(textState);\n\n        // reconstitute the namespace stack by traversing the element and its parents (top down)\n        Elements chain = context.parents();\n        chain.add(0, context);\n        for (int i = chain.size() - 1; i >= 0; i--) {\n            Element el = chain.get(i);\n            HashMap<String, String> namespaces = new HashMap<>(namespacesStack.peek());\n            namespacesStack.push(namespaces);\n            if (el.attributesSize() > 0) {\n                processNamespaces(el.attributes(), namespaces);\n            }\n        }\n    }\n\n    Document parse(Reader input, String baseUri) {\n        return parse(input, baseUri, new Parser(this));\n    }\n\n    Document parse(String input, String baseUri) {\n        return parse(new StringReader(input), baseUri, new Parser(this));\n    }\n\n    @Override List<Node> completeParseFragment() {\n        return doc.childNodes();\n    }\n\n    @Override\n    XmlTreeBuilder newInstance() {\n        return new XmlTreeBuilder();\n    }\n\n    @Override public String defaultNamespace() {\n        return NamespaceXml;\n    }\n\n    @Override\n    TagSet defaultTagSet() {\n        return new TagSet(); // an empty tagset\n    }\n\n    @Override\n    int defaultMaxDepth() {\n        return Integer.MAX_VALUE;\n    }\n\n    @Override\n    protected boolean process(Token token) {\n        currentToken = token;\n\n        // start tag, end tag, doctype, xmldecl, comment, character, eof\n        switch (token.type) {\n            case StartTag:\n                insertElementFor(token.asStartTag());\n                break;\n            case EndTag:\n                popStackToClose(token.asEndTag());\n                break;\n            case Comment:\n                insertCommentFor(token.asComment());\n                break;\n            case Character:\n                insertCharacterFor(token.asCharacter());\n                break;\n            case Doctype:\n                insertDoctypeFor(token.asDoctype());\n                break;\n            case XmlDecl:\n                insertXmlDeclarationFor(token.asXmlDecl());\n                break;\n            case EOF: // could put some normalisation here if desired\n                break;\n            default:\n                Validate.fail(\"Unexpected token type: \" + token.type);\n        }\n        return true;\n    }\n\n    void insertElementFor(Token.StartTag startTag) {\n        // handle namespace for tag\n        HashMap<String, String> namespaces = new HashMap<>(namespacesStack.peek());\n        namespacesStack.push(namespaces);\n\n        Attributes attributes = startTag.attributes;\n        if (attributes != null) {\n            settings.normalizeAttributes(attributes);\n            attributes.deduplicate(settings);\n            processNamespaces(attributes, namespaces);\n            applyNamespacesToAttributes(attributes, namespaces);\n        }\n\n        enforceStackDepthLimit();\n\n        String tagName = startTag.tagName.value();\n        String ns = resolveNamespace(tagName, namespaces);\n        Tag tag = tagFor(tagName, startTag.normalName, ns, settings);\n        Element el = new Element(tag, null, attributes);\n        currentElement().appendChild(el);\n        push(el);\n\n        if (startTag.isSelfClosing()) {\n            tag.setSeenSelfClose();\n            pop(); // push & pop ensures onNodeInserted & onNodeClosed\n        } else if (tag.isEmpty()) {\n            pop(); // custom defined void tag\n        } else {\n            TokeniserState textState = tag.textState();\n            if (textState != null) tokeniser.transition(textState);\n        }\n    }\n\n    private static void processNamespaces(Attributes attributes, HashMap<String, String> namespaces) {\n        // process attributes for namespaces (xmlns, xmlns:)\n        for (Attribute attr : attributes) {\n            String key = attr.getKey();\n            String value = attr.getValue();\n            if (key.equals(XmlnsKey)) {\n                namespaces.put(\"\", value); // new default for this level\n            } else if (key.startsWith(XmlnsPrefix)) {\n                String nsPrefix = key.substring(XmlnsPrefix.length());\n                namespaces.put(nsPrefix, value);\n            }\n        }\n    }\n\n    private static void applyNamespacesToAttributes(Attributes attributes, HashMap<String, String> namespaces) {\n        // second pass, apply namespace to attributes. Collects them first then adds (as userData is an attribute)\n        Map<String, String> attrPrefix = new HashMap<>();\n        for (Attribute attr: attributes) {\n            String prefix = attr.prefix();\n            if (!prefix.isEmpty()) {\n                if (prefix.equals(XmlnsKey)) continue;\n                String ns = namespaces.get(prefix);\n                if (ns != null) attrPrefix.put(SharedConstants.XmlnsAttr + prefix, ns);\n            }\n        }\n        for (Map.Entry<String, String> entry : attrPrefix.entrySet())\n            attributes.userData(entry.getKey(), entry.getValue());\n    }\n\n    private static String resolveNamespace(String tagName, HashMap<String, String> namespaces) {\n        String ns = namespaces.get(\"\");\n        int pos = tagName.indexOf(':');\n        if (pos > 0) {\n            String prefix = tagName.substring(0, pos);\n            if (namespaces.containsKey(prefix))\n                ns = namespaces.get(prefix);\n        }\n        return ns;\n    }\n\n    void insertLeafNode(LeafNode node) {\n        currentElement().appendChild(node);\n        onNodeInserted(node);\n    }\n\n    void insertCommentFor(Token.Comment commentToken) {\n        Comment comment = new Comment(commentToken.getData());\n        insertLeafNode(comment);\n    }\n\n    void insertCharacterFor(Token.Character token) {\n        final String data = token.getData();\n        LeafNode node;\n        if      (token.isCData())                       node = new CDataNode(data);\n        else if (currentElement().tag().is(Tag.Data))   node = new DataNode(data);\n        else                                            node = new TextNode(data);\n        insertLeafNode(node);\n    }\n\n    void insertDoctypeFor(Token.Doctype token) {\n        DocumentType doctypeNode = new DocumentType(settings.normalizeTag(token.getName()), token.getPublicIdentifier(), token.getSystemIdentifier());\n        doctypeNode.setPubSysKey(token.getPubSysKey());\n        insertLeafNode(doctypeNode);\n    }\n\n    void insertXmlDeclarationFor(Token.XmlDecl token) {\n        XmlDeclaration decl = new XmlDeclaration(token.name(), token.isDeclaration);\n        if (token.attributes != null) decl.attributes().addAll(token.attributes);\n        insertLeafNode(decl);\n    }\n\n    @Override\n    Element pop() {\n        namespacesStack.pop();\n        return super.pop();\n    }\n\n    /**\n     * If the stack contains an element with this tag's name, pop up the stack to remove the first occurrence. If not\n     * found, skips.\n     *\n     * @param endTag tag to close\n     */\n    protected void popStackToClose(Token.EndTag endTag) {\n        // like in HtmlTreeBuilder - don't scan up forever for very (artificially) deeply nested stacks\n        String elName = settings.normalizeTag(endTag.name());\n        Element firstFound = null;\n\n        final int bottom = stack.size() - 1;\n        final int upper = bottom >= maxQueueDepth ? bottom - maxQueueDepth : 0;\n\n        for (int pos = stack.size() -1; pos >= upper; pos--) {\n            Element next = stack.get(pos);\n            if (next.nodeName().equals(elName)) {\n                firstFound = next;\n                break;\n            }\n        }\n        if (firstFound == null)\n            return; // not found, skip\n\n        for (int pos = stack.size() -1; pos >= 0; pos--) {\n            Element next = pop();\n            if (next == firstFound) {\n                break;\n            }\n        }\n    }\n    private static final int maxQueueDepth = 256; // an arbitrary tension point between real XML and crafted pain\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/parser/package-info.java",
    "content": "/**\n Contains the HTML parser, tag specifications, and HTML tokeniser.\n */\n@NullMarked\npackage org.jsoup.parser;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java/org/jsoup/safety/Cleaner.java",
    "content": "package org.jsoup.safety;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.parser.ParseErrorList;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.select.NodeVisitor;\n\nimport java.util.List;\n\nimport static org.jsoup.internal.SharedConstants.DummyUri;\n\n/**\n The {@link Safelist}-based HTML cleaner. Use to ensure that end-user provided HTML contains only the elements and attributes\n that you are expecting; no junk, and no cross-site scripting attacks!\n <p>\n The HTML cleaner parses the input as HTML and then runs it through a safelist, so the output HTML can only contain\n HTML that is allowed by the safelist.\n </p>\n <p>\n It is assumed that the input HTML is a body fragment; the clean methods only pull from the source's body, and the\n canned safelists only allow body-contained tags.\n </p>\n <p>\n Rather than interacting directly with a Cleaner object, generally see the {@code clean} methods in {@link org.jsoup.Jsoup}.\n </p>\n <p>\n A Cleaner may be reused across multiple documents and shared across concurrent threads once its {@link Safelist} has\n been configured. The cleaner uses the supplied safelist directly, so later safelist changes affect later cleaning\n calls. If you need a variant of an existing configuration, use {@link Safelist#Safelist(Safelist)} to make a copy.\n </p>\n */\npublic class Cleaner {\n    private final Safelist safelist;\n\n    /**\n     Create a new cleaner, that sanitizes documents using the supplied safelist.\n     @param safelist safe-list to clean with\n     */\n    public Cleaner(Safelist safelist) {\n        Validate.notNull(safelist);\n        this.safelist = safelist;\n    }\n\n    /**\n     Creates a new, clean document, from the original dirty document, containing only elements allowed by the safelist.\n     The original document is not modified. Only elements from the dirty document's <code>body</code> are used. The\n     OutputSettings of the original document are cloned into the clean document.\n     @param dirtyDocument Untrusted base document to clean.\n     @return cleaned document.\n     */\n    public Document clean(Document dirtyDocument) {\n        Validate.notNull(dirtyDocument);\n\n        Document clean = Document.createShell(dirtyDocument.baseUri());\n        copySafeNodes(dirtyDocument.body(), clean.body());\n        clean.outputSettings(dirtyDocument.outputSettings().clone());\n\n        return clean;\n    }\n\n    /**\n     Determines if the input document's <b>body</b> is valid, against the safelist. It is considered valid if all the\n     tags and attributes in the input HTML are allowed by the safelist, and that there is no content in the\n     <code>head</code>.\n     <p>\n     This method is intended to be used in a user interface as a validator for user input. Note that regardless of the\n     output of this method, the input document <b>must always</b> be normalized using a method such as\n     {@link #clean(Document)}, and the result of that method used to store or serialize the document before later reuse\n     such as presentation to end users. This ensures that enforced attributes are set correctly, and that any\n     differences between how a given browser and how jsoup parses the input HTML are normalized.\n     </p>\n     <p>Example:\n     <pre>{@code\n     Document inputDoc = Jsoup.parse(inputHtml);\n     Cleaner cleaner = new Cleaner(Safelist.relaxed());\n     boolean isValid = cleaner.isValid(inputDoc);\n     Document normalizedDoc = cleaner.clean(inputDoc);\n     }</pre>\n     </p>\n     @param dirtyDocument document to test\n     @return true if no tags or attributes need to be removed; false if they do\n     */\n    public boolean isValid(Document dirtyDocument) {\n        Validate.notNull(dirtyDocument);\n\n        Document clean = Document.createShell(dirtyDocument.baseUri());\n        int numDiscarded = copySafeNodes(dirtyDocument.body(), clean.body());\n        return numDiscarded == 0\n            && dirtyDocument.head().childNodes().isEmpty(); // because we only look at the body, but we start from a shell, make sure there's nothing in the head\n    }\n\n    /**\n     Determines if the input document's <b>body HTML</b> is valid, against the safelist. It is considered valid if all\n     the tags and attributes in the input HTML are allowed by the safelist.\n     <p>\n     This method is intended to be used in a user interface as a validator for user input. Note that regardless of the\n     output of this method, the input document <b>must always</b> be normalized using a method such as\n     {@link #clean(Document)}, and the result of that method used to store or serialize the document before later reuse\n     such as presentation to end users. This ensures that enforced attributes are set correctly, and that any\n     differences between how a given browser and how jsoup parses the input HTML are normalized.\n     </p>\n     <p>Example:\n     <pre>{@code\n     Document inputDoc = Jsoup.parse(inputHtml);\n     Cleaner cleaner = new Cleaner(Safelist.relaxed());\n     boolean isValid = cleaner.isValidBodyHtml(inputHtml);\n     Document normalizedDoc = cleaner.clean(inputDoc);\n     }</pre>\n     </p>\n     @param bodyHtml HTML fragment to test\n     @return true if no tags or attributes need to be removed; false if they do\n     */\n    public boolean isValidBodyHtml(String bodyHtml) {\n        String baseUri = (safelist.preserveRelativeLinks()) ? DummyUri : \"\"; // fake base URI to allow relative URLs to remain valid\n        Document clean = Document.createShell(baseUri);\n        Document dirty = Document.createShell(baseUri);\n        ParseErrorList errorList = ParseErrorList.tracking(1);\n        List<Node> nodes = Parser.parseFragment(bodyHtml, dirty.body(), baseUri, errorList);\n        dirty.body().insertChildren(0, nodes);\n        int numDiscarded = copySafeNodes(dirty.body(), clean.body());\n        return numDiscarded == 0 && errorList.isEmpty();\n    }\n\n    /**\n     Iterates the input and copies trusted nodes (tags, attributes, text) into the destination.\n     */\n    private final class CleaningVisitor implements NodeVisitor {\n        private int numDiscarded = 0;\n        private final Element root;\n        private Element destination; // current element to append nodes to\n\n        private CleaningVisitor(Element root, Element destination) {\n            this.root = root;\n            this.destination = destination;\n        }\n\n        @Override public void head(Node source, int depth) {\n            if (source instanceof Element) {\n                Element sourceEl = (Element) source;\n\n                if (safelist.isSafeTag(sourceEl.normalName())) { // safe, clone and copy safe attrs\n                    ElementMeta meta = createSafeElement(sourceEl);\n                    Element destChild = meta.el;\n                    destination.appendChild(destChild);\n\n                    numDiscarded += meta.numAttribsDiscarded;\n                    destination = destChild;\n                } else if (source != root) { // not a safe tag, so don't add. don't count root against discarded.\n                    numDiscarded++;\n                }\n            } else if (source instanceof TextNode) {\n                TextNode sourceText = (TextNode) source;\n                TextNode destText = new TextNode(sourceText.getWholeText());\n                destination.appendChild(destText);\n            } else if (source instanceof DataNode && safelist.isSafeTag(source.parent().normalName())) {\n                DataNode sourceData = (DataNode) source;\n                DataNode destData = new DataNode(sourceData.getWholeData());\n                destination.appendChild(destData);\n            } else { // else, we don't care about comments, xml proc instructions, etc\n                numDiscarded++;\n            }\n        }\n\n        @Override public void tail(Node source, int depth) {\n            if (source instanceof Element && safelist.isSafeTag(source.normalName())) {\n                destination = destination.parent(); // would have descended, so pop destination stack\n            }\n        }\n    }\n\n    private int copySafeNodes(Element source, Element dest) {\n        CleaningVisitor cleaningVisitor = new CleaningVisitor(source, dest);\n        cleaningVisitor.traverse(source);\n        return cleaningVisitor.numDiscarded;\n    }\n\n    private ElementMeta createSafeElement(Element sourceEl) {\n        Element dest = sourceEl.shallowClone(); // reuses tag, clones attributes and preserves any user data\n        String sourceTag = sourceEl.tagName();\n        Attributes destAttrs = dest.attributes();\n        dest.clearAttributes(); // clear all non-internal attributes, ready for safe copy\n\n        int numDiscarded = 0;\n        Attributes sourceAttrs = sourceEl.attributes();\n        for (Attribute sourceAttr : sourceAttrs) {\n            if (safelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)) { // will keep this attr\n                String key = sourceAttr.getKey();\n                String value = sourceAttr.getValue();\n\n                if (safelist.shouldAbsUrl(sourceTag, key)) { // configured to make absolute urls for this key (href)\n                    value = sourceEl.absUrl(key);\n                    if (value.isEmpty()) // could not be made abs; leave as-is to allow custom unknown protocols\n                        value = sourceAttr.getValue();\n                }\n                destAttrs.put(key, value);\n            } else\n                numDiscarded++;\n        }\n\n\n        Attributes enforcedAttrs = safelist.getEnforcedAttributes(sourceTag);\n        // special case for <a href rel=nofollow>, only apply to external links:\n        if (sourceEl.nameIs(\"a\") && enforcedAttrs.get(\"rel\").equals(\"nofollow\")) {\n            String href = sourceEl.absUrl(\"href\");\n            String sourceBase = sourceEl.baseUri();\n            if (!href.isEmpty() && !sourceBase.isEmpty() && href.startsWith(sourceBase)) { // same site, so don't set the nofollow\n                enforcedAttrs.remove(\"rel\");\n            }\n        }\n\n        // apply enforced attributes case-insensitively, so a preserved-case source attr is canonicalized to the enforced key\n        for (Attribute enforcedAttr : enforcedAttrs) {\n            destAttrs.removeIgnoreCase(enforcedAttr.getKey());\n            destAttrs.put(enforcedAttr.getKey(), enforcedAttr.getValue());\n        }\n        dest.attributes().addAll(destAttrs); // re-attach, if removed in clear\n        return new ElementMeta(dest, numDiscarded);\n    }\n\n    private static class ElementMeta {\n        Element el;\n        int numAttribsDiscarded;\n\n        ElementMeta(Element el, int numAttribsDiscarded) {\n            this.el = el;\n            this.numAttribsDiscarded = numAttribsDiscarded;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/safety/Safelist.java",
    "content": "package org.jsoup.safety;\n\n/*\n    Thank you to Ryan Grove (wonko.com) for the Ruby HTML cleaner http://github.com/rgrove/sanitize/, which inspired\n    this safe-list configuration, and the initial defaults.\n */\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Element;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport static org.jsoup.internal.Normalizer.lowerCase;\n\n\n/**\n Safelists define what HTML (elements and attributes) to allow through a {@link Cleaner}. Everything else is removed.\n <p>\n Start with one of the defaults:\n </p>\n <ul>\n <li>{@link #none}\n <li>{@link #simpleText}\n <li>{@link #basic}\n <li>{@link #basicWithImages}\n <li>{@link #relaxed}\n </ul>\n <p>\n If you need to allow more through (please be careful!), tweak a base safelist with:\n </p>\n <ul>\n <li>{@link #addTags(String... tagNames)}\n <li>{@link #addAttributes(String tagName, String... attributes)}\n <li>{@link #addEnforcedAttribute(String tagName, String attribute, String value)}\n <li>{@link #addProtocols(String tagName, String attribute, String... protocols)}\n </ul>\n <p>\n You can remove any setting from an existing safelist with:\n </p>\n <ul>\n <li>{@link #removeTags(String... tagNames)}\n <li>{@link #removeAttributes(String tagName, String... attributes)}\n <li>{@link #removeEnforcedAttribute(String tagName, String attribute)}\n <li>{@link #removeProtocols(String tagName, String attribute, String... removeProtocols)}\n </ul>\n\n <p>\n The {@link Cleaner} and these safelists assume that you want to clean a <code>body</code> fragment of HTML (to add user\n supplied HTML into a templated page), and not to clean a full HTML document. If the latter is the case, you could wrap\n the templated document HTML around the cleaned body HTML.\n </p>\n <p>\n Safelists are mutable. A {@link Cleaner} uses the supplied safelist directly, so later changes affect later cleaning\n calls. If you want to share a safelist across threads, finish configuring it first and do not mutate it while it is in\n use. To build a variant from an existing configuration, use {@link #Safelist(Safelist)} to make a copy.\n </p>\n <p>\n If you are going to extend a safelist, please be very careful. Make sure you understand what attributes may lead to\n XSS attack vectors. URL attributes are particularly vulnerable and require careful validation. See \n the <a href=\"https://owasp.org/www-community/xss-filter-evasion-cheatsheet\">XSS Filter Evasion Cheat Sheet</a> for some\n XSS attack examples (that jsoup will safeguard against with the default Cleaner and Safelist configuration).\n </p>\n */\npublic class Safelist {\n    private static final String All = \":all\";\n    private static final TagName AllTag = TagName.valueOf(All);\n    private final Set<TagName> tagNames; // tags allowed, lower case. e.g. [p, br, span]\n    private final Map<TagName, Set<AttributeKey>> attributes; // tag -> attribute[]. allowed attributes [href] for a tag.\n    private final Map<TagName, Map<AttributeKey, AttributeValue>> enforcedAttributes; // always set these attribute values\n    private final Map<TagName, Map<AttributeKey, Set<Protocol>>> protocols; // allowed URL protocols for attributes\n    private boolean preserveRelativeLinks; // option to preserve relative links\n\n    /**\n     This safelist allows only text nodes: any HTML Element or any Node other than a TextNode will be removed.\n     <p>\n     Note that the output of {@link org.jsoup.Jsoup#clean(String, Safelist)} is still <b>HTML</b> even when using\n     this Safelist, and so any HTML entities in the output will be appropriately escaped. If you want plain text, not\n     HTML, you should use a text method such as {@link Element#text()} instead, after cleaning the document.\n     </p>\n     <p>Example:</p>\n     <pre>{@code\n     String sourceBodyHtml = \"<p>5 is &lt; 6.</p>\";\n     String html = Jsoup.clean(sourceBodyHtml, Safelist.none());\n\n     Cleaner cleaner = new Cleaner(Safelist.none());\n     String text = cleaner.clean(Jsoup.parse(sourceBodyHtml)).text();\n\n     // html is: 5 is &lt; 6.\n     // text is: 5 is < 6.\n     }</pre>\n\n     @return safelist\n     */\n    public static Safelist none() {\n        return new Safelist();\n    }\n\n    /**\n     This safelist allows only simple text formatting: <code>b, em, i, strong, u</code>. All other HTML (tags and\n     attributes) will be removed.\n\n     @return safelist\n     */\n    public static Safelist simpleText() {\n        return new Safelist()\n                .addTags(\"b\", \"em\", \"i\", \"strong\", \"u\")\n                ;\n    }\n\n    /**\n     <p>\n     This safelist allows a fuller range of text nodes: <code>a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li,\n     ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul</code>, and appropriate attributes.\n     </p>\n     <p>\n     Links (<code>a</code> elements) can point to <code>http, https, ftp, mailto</code>, and have an enforced\n     <code>rel=nofollow</code> attribute if they link offsite (as indicated by the specified base URI).\n     </p>\n     <p>\n     Does not allow images.\n     </p>\n\n     @return safelist\n     */\n    public static Safelist basic() {\n        return new Safelist()\n                .addTags(\n                        \"a\", \"b\", \"blockquote\", \"br\", \"cite\", \"code\", \"dd\", \"dl\", \"dt\", \"em\",\n                        \"i\", \"li\", \"ol\", \"p\", \"pre\", \"q\", \"small\", \"span\", \"strike\", \"strong\", \"sub\",\n                        \"sup\", \"u\", \"ul\")\n\n                .addAttributes(\"a\", \"href\")\n                .addAttributes(\"blockquote\", \"cite\")\n                .addAttributes(\"q\", \"cite\")\n\n                .addProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\", \"mailto\")\n                .addProtocols(\"blockquote\", \"cite\", \"http\", \"https\")\n                .addProtocols(\"cite\", \"cite\", \"http\", \"https\")\n\n                .addEnforcedAttribute(\"a\", \"rel\", \"nofollow\") // has special handling for external links, in Cleaner\n                ;\n\n    }\n\n    /**\n     This safelist allows the same text tags as {@link #basic}, and also allows <code>img</code> tags, with appropriate\n     attributes, with <code>src</code> pointing to <code>http</code> or <code>https</code>.\n\n     @return safelist\n     */\n    public static Safelist basicWithImages() {\n        return basic()\n                .addTags(\"img\")\n                .addAttributes(\"img\", \"align\", \"alt\", \"height\", \"src\", \"title\", \"width\")\n                .addProtocols(\"img\", \"src\", \"http\", \"https\")\n                ;\n    }\n\n    /**\n     This safelist allows a full range of text and structural body HTML: <code>a, b, blockquote, br, caption, cite,\n     code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub,\n     sup, table, tbody, td, tfoot, th, thead, tr, u, ul</code>\n     <p>\n     Links do not have an enforced <code>rel=nofollow</code> attribute, but you can add that if desired.\n     </p>\n\n     @return safelist\n     */\n    public static Safelist relaxed() {\n        return new Safelist()\n                .addTags(\n                        \"a\", \"b\", \"blockquote\", \"br\", \"caption\", \"cite\", \"code\", \"col\",\n                        \"colgroup\", \"dd\", \"div\", \"dl\", \"dt\", \"em\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n                        \"i\", \"img\", \"li\", \"ol\", \"p\", \"pre\", \"q\", \"small\", \"span\", \"strike\", \"strong\",\n                        \"sub\", \"sup\", \"table\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"tr\", \"u\",\n                        \"ul\")\n\n                .addAttributes(\"a\", \"href\", \"title\")\n                .addAttributes(\"blockquote\", \"cite\")\n                .addAttributes(\"col\", \"span\", \"width\")\n                .addAttributes(\"colgroup\", \"span\", \"width\")\n                .addAttributes(\"img\", \"align\", \"alt\", \"height\", \"src\", \"title\", \"width\")\n                .addAttributes(\"ol\", \"start\", \"type\")\n                .addAttributes(\"q\", \"cite\")\n                .addAttributes(\"table\", \"summary\", \"width\")\n                .addAttributes(\"td\", \"abbr\", \"axis\", \"colspan\", \"rowspan\", \"width\")\n                .addAttributes(\n                        \"th\", \"abbr\", \"axis\", \"colspan\", \"rowspan\", \"scope\",\n                        \"width\")\n                .addAttributes(\"ul\", \"type\")\n\n                .addProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\", \"mailto\")\n                .addProtocols(\"blockquote\", \"cite\", \"http\", \"https\")\n                .addProtocols(\"cite\", \"cite\", \"http\", \"https\")\n                .addProtocols(\"img\", \"src\", \"http\", \"https\")\n                .addProtocols(\"q\", \"cite\", \"http\", \"https\")\n                ;\n    }\n\n    /**\n     Create a new, empty safelist. Generally it will be better to start with a default prepared safelist instead.\n\n     @see #basic()\n     @see #basicWithImages()\n     @see #simpleText()\n     @see #relaxed()\n     */\n    public Safelist() {\n        tagNames = new HashSet<>();\n        attributes = new HashMap<>();\n        enforcedAttributes = new HashMap<>();\n        protocols = new HashMap<>();\n        preserveRelativeLinks = false;\n    }\n\n    /**\n     Deep copy an existing Safelist to a new Safelist.\n     @param copy the Safelist to copy\n     */\n    public Safelist(Safelist copy) {\n        this();\n        tagNames.addAll(copy.tagNames);\n        for (Map.Entry<TagName, Set<AttributeKey>> copyTagAttributes : copy.attributes.entrySet()) {\n            attributes.put(copyTagAttributes.getKey(), new HashSet<>(copyTagAttributes.getValue()));\n        }\n        for (Map.Entry<TagName, Map<AttributeKey, AttributeValue>> enforcedEntry : copy.enforcedAttributes.entrySet()) {\n            enforcedAttributes.put(enforcedEntry.getKey(), new HashMap<>(enforcedEntry.getValue()));\n        }\n        for (Map.Entry<TagName, Map<AttributeKey, Set<Protocol>>> protocolsEntry : copy.protocols.entrySet()) {\n            Map<AttributeKey, Set<Protocol>> attributeProtocolsCopy = new HashMap<>();\n            for (Map.Entry<AttributeKey, Set<Protocol>> attributeProtocols : protocolsEntry.getValue().entrySet()) {\n                attributeProtocolsCopy.put(attributeProtocols.getKey(), new HashSet<>(attributeProtocols.getValue()));\n            }\n            protocols.put(protocolsEntry.getKey(), attributeProtocolsCopy);\n        }\n        preserveRelativeLinks = copy.preserveRelativeLinks;\n    }\n\n    /**\n     Add a list of allowed elements to a safelist. (If a tag is not allowed, it will be removed from the HTML.)\n\n     @param tags tag names to allow\n     @return this (for chaining)\n     */\n    public Safelist addTags(String... tags) {\n        Validate.notNull(tags);\n\n        for (String tagName : tags) {\n            Validate.notEmpty(tagName);\n            Validate.isFalse(tagName.equalsIgnoreCase(\"noscript\"),\n                \"noscript is unsupported in Safelists, due to incompatibilities between parsers with and without script-mode enabled\");\n            tagNames.add(TagName.valueOf(tagName));\n        }\n        return this;\n    }\n\n    /**\n     Remove a list of allowed elements from a safelist. (If a tag is not allowed, it will be removed from the HTML.)\n\n     @param tags tag names to disallow\n     @return this (for chaining)\n     */\n    public Safelist removeTags(String... tags) {\n        Validate.notNull(tags);\n\n        for(String tag: tags) {\n            Validate.notEmpty(tag);\n            TagName tagName = TagName.valueOf(tag);\n\n            if(tagNames.remove(tagName)) { // Only look in sub-maps if tag was allowed\n                attributes.remove(tagName);\n                enforcedAttributes.remove(tagName);\n                protocols.remove(tagName);\n            }\n        }\n        return this;\n    }\n\n    /**\n     Add a list of allowed attributes to a tag. (If an attribute is not allowed on an element, it will be removed.)\n     <p>\n     E.g.: <code>addAttributes(\"a\", \"href\", \"class\")</code> allows <code>href</code> and <code>class</code> attributes\n     on <code>a</code> tags.\n     </p>\n     <p>\n     To make an attribute valid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g.\n     <code>addAttributes(\":all\", \"class\")</code>.\n     </p>\n\n     @param tag  The tag the attributes are for. The tag will be added to the allowed tag list if necessary.\n     @param attributes List of valid attributes for the tag\n     @return this (for chaining)\n     */\n    public Safelist addAttributes(String tag, String... attributes) {\n        Validate.notEmpty(tag);\n        Validate.notNull(attributes);\n        Validate.isTrue(attributes.length > 0, \"No attribute names supplied.\");\n\n        addTags(tag);\n        TagName tagName = TagName.valueOf(tag);\n        Set<AttributeKey> attributeSet = new HashSet<>();\n        for (String key : attributes) {\n            Validate.notEmpty(key);\n            attributeSet.add(AttributeKey.valueOf(key));\n        }\n        Set<AttributeKey> currentSet = this.attributes.computeIfAbsent(tagName, k -> new HashSet<>());\n        currentSet.addAll(attributeSet);\n        return this;\n    }\n\n    /**\n     Remove a list of allowed attributes from a tag. (If an attribute is not allowed on an element, it will be removed.)\n     <p>\n     E.g.: <code>removeAttributes(\"a\", \"href\", \"class\")</code> disallows <code>href</code> and <code>class</code>\n     attributes on <code>a</code> tags.\n     </p>\n     <p>\n     To make an attribute invalid for <b>all tags</b>, use the pseudo tag <code>:all</code>, e.g.\n     <code>removeAttributes(\":all\", \"class\")</code>.\n     </p>\n\n     @param tag  The tag the attributes are for.\n     @param attributes List of invalid attributes for the tag\n     @return this (for chaining)\n     */\n    public Safelist removeAttributes(String tag, String... attributes) {\n        Validate.notEmpty(tag);\n        Validate.notNull(attributes);\n        Validate.isTrue(attributes.length > 0, \"No attribute names supplied.\");\n\n        TagName tagName = TagName.valueOf(tag);\n        Set<AttributeKey> attributeSet = new HashSet<>();\n        for (String key : attributes) {\n            Validate.notEmpty(key);\n            attributeSet.add(AttributeKey.valueOf(key));\n        }\n        if(tagNames.contains(tagName) && this.attributes.containsKey(tagName)) { // Only look in sub-maps if tag was allowed\n            Set<AttributeKey> currentSet = this.attributes.get(tagName);\n            currentSet.removeAll(attributeSet);\n\n            if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag\n                this.attributes.remove(tagName);\n        }\n        if(tag.equals(All)) { // Attribute needs to be removed from all individually set tags\n            Iterator<Map.Entry<TagName, Set<AttributeKey>>> it = this.attributes.entrySet().iterator();\n            while (it.hasNext()) {\n                Map.Entry<TagName, Set<AttributeKey>> entry = it.next();\n                Set<AttributeKey> currentSet = entry.getValue();\n                currentSet.removeAll(attributeSet);\n                if(currentSet.isEmpty()) // Remove tag from attribute map if no attributes are allowed for tag\n                    it.remove();\n            }\n        }\n        return this;\n    }\n\n    /**\n     Add an enforced attribute to a tag. An enforced attribute will always be added to the element. If the element\n     already has the attribute set, it will be overridden with this value.\n     <p>\n     E.g.: <code>addEnforcedAttribute(\"a\", \"rel\", \"nofollow\")</code> will make all <code>a</code> tags output as\n     <code>&lt;a href=\"...\" rel=\"nofollow\"&gt;</code>\n     </p>\n\n     @param tag   The tag the enforced attribute is for. The tag will be added to the allowed tag list if necessary.\n     @param attribute   The attribute name\n     @param value The enforced attribute value\n     @return this (for chaining)\n     */\n    public Safelist addEnforcedAttribute(String tag, String attribute, String value) {\n        Validate.notEmpty(tag);\n        Validate.notEmpty(attribute);\n        Validate.notEmpty(value);\n\n        TagName tagName = TagName.valueOf(tag);\n        tagNames.add(tagName);\n        AttributeKey attrKey = AttributeKey.valueOf(attribute);\n        AttributeValue attrVal = AttributeValue.valueOf(value);\n\n        Map<AttributeKey, AttributeValue> attrMap = enforcedAttributes.computeIfAbsent(tagName, k -> new HashMap<>());\n        attrMap.put(attrKey, attrVal);\n        return this;\n    }\n\n    /**\n     Remove a previously configured enforced attribute from a tag.\n\n     @param tag   The tag the enforced attribute is for.\n     @param attribute   The attribute name\n     @return this (for chaining)\n     */\n    public Safelist removeEnforcedAttribute(String tag, String attribute) {\n        Validate.notEmpty(tag);\n        Validate.notEmpty(attribute);\n\n        TagName tagName = TagName.valueOf(tag);\n        if(tagNames.contains(tagName) && enforcedAttributes.containsKey(tagName)) {\n            AttributeKey attrKey = AttributeKey.valueOf(attribute);\n            Map<AttributeKey, AttributeValue> attrMap = enforcedAttributes.get(tagName);\n            attrMap.remove(attrKey);\n\n            if(attrMap.isEmpty()) // Remove tag from enforced attribute map if no enforced attributes are present\n                enforcedAttributes.remove(tagName);\n        }\n        return this;\n    }\n\n    /**\n     * Configure this Safelist to preserve relative links in an element's URL attribute, or convert them to absolute\n     * links. By default, this is <b>false</b>: URLs will be  made absolute (e.g. start with an allowed protocol, like\n     * e.g. {@code http://}.\n     *\n     * @param preserve {@code true} to allow relative links, {@code false} (default) to deny\n     * @return this Safelist, for chaining.\n     * @see #addProtocols\n     */\n    public Safelist preserveRelativeLinks(boolean preserve) {\n        preserveRelativeLinks = preserve;\n        return this;\n    }\n\n    /**\n     * Get the current setting for preserving relative links.\n     * @return {@code true} if relative links are preserved, {@code false} if they are converted to absolute.\n     */\n    public boolean preserveRelativeLinks() {\n        return preserveRelativeLinks;\n    }\n\n    /**\n     Add allowed URL protocols for an element's URL attribute. This restricts the possible values of the attribute to\n     URLs with the defined protocol.\n     <p>\n     E.g.: <code>addProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\")</code>\n     </p>\n     <p>\n     To allow a link to an in-page URL anchor (i.e. <code>&lt;a href=\"#anchor\"&gt;</code>, add a <code>#</code>:<br>\n     E.g.: <code>addProtocols(\"a\", \"href\", \"#\")</code>\n     </p>\n\n     @param tag       Tag the URL protocol is for\n     @param attribute       Attribute name\n     @param protocols List of valid protocols\n     @return this, for chaining\n     */\n    public Safelist addProtocols(String tag, String attribute, String... protocols) {\n        Validate.notEmpty(tag);\n        Validate.notEmpty(attribute);\n        Validate.notNull(protocols);\n\n        TagName tagName = TagName.valueOf(tag);\n        AttributeKey attrKey = AttributeKey.valueOf(attribute);\n        Map<AttributeKey, Set<Protocol>> attrMap = this.protocols.computeIfAbsent(tagName, k -> new HashMap<>());\n        Set<Protocol> protSet = attrMap.computeIfAbsent(attrKey, k -> new HashSet<>());\n\n        for (String protocol : protocols) {\n            Validate.notEmpty(protocol);\n            Protocol prot = Protocol.valueOf(protocol);\n            protSet.add(prot);\n        }\n        return this;\n    }\n\n    /**\n     Remove allowed URL protocols for an element's URL attribute. If you remove all protocols for an attribute, that\n     attribute will allow any protocol.\n     <p>\n     E.g.: <code>removeProtocols(\"a\", \"href\", \"ftp\")</code>\n     </p>\n\n     @param tag Tag the URL protocol is for\n     @param attribute Attribute name\n     @param removeProtocols List of invalid protocols\n     @return this, for chaining\n     */\n    public Safelist removeProtocols(String tag, String attribute, String... removeProtocols) {\n        Validate.notEmpty(tag);\n        Validate.notEmpty(attribute);\n        Validate.notNull(removeProtocols);\n\n        TagName tagName = TagName.valueOf(tag);\n        AttributeKey attr = AttributeKey.valueOf(attribute);\n\n        // make sure that what we're removing actually exists; otherwise can open the tag to any data and that can\n        // be surprising\n        Validate.isTrue(protocols.containsKey(tagName), \"Cannot remove a protocol that is not set.\");\n        Map<AttributeKey, Set<Protocol>> tagProtocols = protocols.get(tagName);\n        Validate.isTrue(tagProtocols.containsKey(attr), \"Cannot remove a protocol that is not set.\");\n\n        Set<Protocol> attrProtocols = tagProtocols.get(attr);\n        for (String protocol : removeProtocols) {\n            Validate.notEmpty(protocol);\n            attrProtocols.remove(Protocol.valueOf(protocol));\n        }\n\n        if (attrProtocols.isEmpty()) { // Remove protocol set if empty\n            tagProtocols.remove(attr);\n            if (tagProtocols.isEmpty()) // Remove entry for tag if empty\n                protocols.remove(tagName);\n        }\n        return this;\n    }\n\n    /**\n     * Test if the supplied tag is allowed by this safelist.\n     * @param tag test tag\n     * @return true if allowed\n     */\n    public boolean isSafeTag(String tag) {\n        return tagNames.contains(TagName.valueOf(tag));\n    }\n\n    /**\n     * Test if the supplied attribute is allowed by this safelist for this tag.\n     * <p>This method does not modify the input element or attribute.</p>\n     * @param tagName tag to consider allowing the attribute in\n     * @param el element under test, to confirm protocol\n     * @param attr attribute under test\n     * @return true if allowed\n     */\n    public boolean isSafeAttribute(String tagName, Element el, Attribute attr) {\n        TagName tag = TagName.valueOf(tagName);\n        AttributeKey key = AttributeKey.valueOf(attr.getKey());\n\n        Set<AttributeKey> okSet = attributes.get(tag);\n        if (okSet != null && okSet.contains(key)) {\n            if (protocols.containsKey(tag)) {\n                Map<AttributeKey, Set<Protocol>> attrProts = protocols.get(tag);\n                // ok if not defined protocol; otherwise test\n                return !attrProts.containsKey(key) || isSafeProtocol(getProtocolValue(el, attr), attrProts.get(key));\n            } else { // attribute found, no protocols defined, so OK\n                return true;\n            }\n        }\n        Map<AttributeKey, AttributeValue> enforcedSet = enforcedAttributes.get(tag);\n        if (enforcedSet != null && enforcedSet.containsKey(key)) {\n            // enforced attr key was LCed via AttributeKey.valueOf(attr.getKey()),\n            // if the input already has that exact value, treat it as safe\n            return enforcedSet.get(key).equals(AttributeValue.valueOf(attr.getValue()));\n        }\n        // no attributes defined for tag, try :all tag\n        return !tagName.equals(All) && isSafeAttribute(All, el, attr);\n    }\n\n    private String getProtocolValue(Element el, Attribute attr) {\n        String value = el.absUrl(attr.getKey());\n        if (value.isEmpty())\n            value = attr.getValue(); // if it could not be made abs, run as-is to allow custom unknown protocols\n        return value;\n    }\n\n    private boolean isSafeProtocol(String value, Set<Protocol> protocols) {\n        for (Protocol protocol : protocols) {\n            String prot = protocol.toString();\n\n            if (prot.equals(\"#\")) { // allows anchor links\n                if (isValidAnchor(value)) {\n                    return true;\n                } else {\n                    continue;\n                }\n            }\n\n            prot += \":\";\n\n            if (lowerCase(value).startsWith(prot)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     Check if a URL attribute should be normalized to an absolute URL in the cleaned output. Uses the configured\n     protocols for that tag+attribute pair, falling back to {@code :all} only if the tag does not define the\n     attribute.\n     */\n    boolean shouldAbsUrl(String tagName, String attrKey) {\n        if (preserveRelativeLinks) return false;\n        return shouldAbsUrl(TagName.valueOf(tagName), AttributeKey.valueOf(attrKey));\n    }\n\n    private boolean shouldAbsUrl(TagName tag, AttributeKey key) {\n        Set<AttributeKey> allowedAttrs = attributes.get(tag);\n        if (allowedAttrs != null && allowedAttrs.contains(key)) {\n            Map<AttributeKey, Set<Protocol>> protocolsByAttr = protocols.get(tag);\n            return protocolsByAttr != null && protocolsByAttr.containsKey(key);\n        }\n\n        Map<AttributeKey, AttributeValue> enforcedAttrs = enforcedAttributes.get(tag);\n        if (enforcedAttrs != null && enforcedAttrs.containsKey(key)) return false;\n\n        return !tag.equals(AllTag) && shouldAbsUrl(AllTag, key);\n    }\n\n    private static boolean isValidAnchor(String value) {\n        return value.startsWith(\"#\") && !value.matches(\".*\\\\s.*\");\n    }\n\n    /**\n     Gets the Attributes that should be enforced for a given tag\n     * @param tagName the tag\n     * @return the attributes that will be enforced; empty if none are set for the given tag\n     */\n    public Attributes getEnforcedAttributes(String tagName) {\n        Attributes attrs = new Attributes();\n        TagName tag = TagName.valueOf(tagName);\n        if (enforcedAttributes.containsKey(tag)) {\n            Map<AttributeKey, AttributeValue> keyVals = enforcedAttributes.get(tag);\n            for (Map.Entry<AttributeKey, AttributeValue> entry : keyVals.entrySet()) {\n                attrs.put(entry.getKey().toString(), entry.getValue().toString());\n            }\n        }\n        return attrs;\n    }\n    \n    // named types for config. All just hold strings, but here for my sanity.\n\n    static class TagName extends TypedValue {\n        TagName(String value) {\n            super(value);\n        }\n\n        static TagName valueOf(String value) {\n            return new TagName(Normalizer.lowerCase(value));\n        }\n    }\n\n    static class AttributeKey extends TypedValue {\n        AttributeKey(String value) {\n            super(value);\n        }\n\n        static AttributeKey valueOf(String value) {\n            return new AttributeKey(Normalizer.lowerCase(value));\n        }\n    }\n\n    static class AttributeValue extends TypedValue {\n        AttributeValue(String value) {\n            super(value);\n        }\n\n        static AttributeValue valueOf(String value) {\n            return new AttributeValue(value);\n        }\n    }\n\n    static class Protocol extends TypedValue {\n        Protocol(String value) {\n            super(value);\n        }\n\n        static Protocol valueOf(String value) {\n            return new Protocol(value);\n        }\n    }\n\n    abstract static class TypedValue {\n        private final String value;\n\n        TypedValue(String value) {\n            Validate.notNull(value);\n            this.value = value;\n        }\n\n        @Override\n        public int hashCode() {\n            return value.hashCode();\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (this == obj) return true;\n            if (obj == null || getClass() != obj.getClass()) return false;\n            TypedValue other = (TypedValue) obj;\n            return Objects.equals(value, other.value);\n        }\n\n        @Override\n        public String toString() {\n            return value;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/safety/package-info.java",
    "content": "/**\n Contains the jsoup HTML cleaner, and safelist definitions.\n */\n@NullMarked\npackage org.jsoup.safety;\n\nimport org.jspecify.annotations.NullMarked;"
  },
  {
    "path": "src/main/java/org/jsoup/select/Collector.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static java.util.stream.Collectors.toCollection;\n\n/**\n * Collects a list of elements that match the supplied criteria.\n *\n * @author Jonathan Hedley\n */\npublic class Collector {\n\n    private Collector() {}\n\n    /**\n     Build a list of elements, by visiting the root and every descendant of root, and testing it against the Evaluator.\n     @param eval Evaluator to test elements against\n     @param root root of tree to descend\n     @return list of matches; empty if none\n     */\n    public static Elements collect(Evaluator eval, Element root) {\n        Stream<Element> stream = eval.wantsNodes() ?\n            streamNodes(eval, root, Element.class) :\n            stream(eval, root);\n        Elements els = stream.collect(toCollection(Elements::new));\n        eval.reset(); // drops any held memos\n        return els;\n    }\n\n    /**\n     Obtain a Stream of elements by visiting the root and every descendant of root and testing it against the evaluator.\n\n     @param evaluator Evaluator to test elements against\n     @param root root of tree to descend\n     @return A {@link Stream} of matches\n     @since 1.19.1\n     */\n    public static Stream<Element> stream(Evaluator evaluator, Element root) {\n        evaluator.reset();\n        return root.stream().filter(evaluator.asPredicate(root));\n    }\n\n    /**\n     Obtain a Stream of nodes, of the specified type, by visiting the root and every descendant of root and testing it\n     against the evaluator.\n\n     @param evaluator Evaluator to test elements against\n     @param root root of tree to descend\n     @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc)\n     @param <T> the type of node to collect\n     @return A {@link Stream} of matches\n     @since 1.21.1\n     */\n    public static <T extends Node> Stream<T> streamNodes(Evaluator evaluator, Element root, Class<T> type) {\n        evaluator.reset();\n        return root.nodeStream(type).filter(evaluator.asNodePredicate(root));\n    }\n\n    /**\n     Finds the first Element that matches the Evaluator that descends from the root, and stops the query once that first\n     match is found.\n     @param eval Evaluator to test elements against\n     @param root root of tree to descend\n     @return the first match; {@code null} if none\n     */\n    public static @Nullable Element findFirst(Evaluator eval, Element root) {\n        Element el = stream(eval, root).findFirst().orElse(null);\n        eval.reset();\n        return el;\n    }\n\n    /**\n     Finds the first Node that matches the Evaluator that descends from the root, and stops the query once that first\n     match is found.\n\n     @param eval Evaluator to test elements against\n     @param root root of tree to descend\n     @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc)\n     @return the first match; {@code null} if none\n     @since 1.21.1\n     */\n    public static <T extends Node> @Nullable T findFirstNode(Evaluator eval, Element root, Class<T> type) {\n        T node = streamNodes(eval, root, type).findFirst().orElse(null);\n        eval.reset();\n        return node;\n    }\n\n    /**\n     Build a list of nodes that match the supplied criteria, by visiting the root and every descendant of root, and\n     testing it against the Evaluator.\n\n     @param evaluator Evaluator to test elements against\n     @param root root of tree to descend\n     @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc)\n     @param <T> the type of node to collect\n     @return list of matches; empty if none\n     */\n    public static <T extends Node> Nodes<T> collectNodes(Evaluator evaluator, Element root, Class<T> type) {\n        return streamNodes(evaluator, root, type).collect(toCollection(Nodes::new));\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/CombiningEvaluator.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * Base combining (and, or) evaluator.\n */\npublic abstract class CombiningEvaluator extends Evaluator {\n    final ArrayList<Evaluator> evaluators; // maintain original order so that #toString() is sensible\n    final List<Evaluator> sortedEvaluators; // cost ascending order\n    int num = 0;\n    int cost = 0;\n    boolean wantsNodes;\n\n    CombiningEvaluator() {\n        super();\n        evaluators = new ArrayList<>();\n        sortedEvaluators = new ArrayList<>();\n    }\n\n    CombiningEvaluator(Collection<Evaluator> evaluators) {\n        this();\n        this.evaluators.addAll(evaluators);\n        updateEvaluators();\n    }\n\n    public void add(Evaluator e) {\n        evaluators.add(e);\n        updateEvaluators();\n    }\n\n    @Override protected void reset() {\n        for (Evaluator evaluator : evaluators) {\n            evaluator.reset();\n        }\n        super.reset();\n    }\n\n    @Override protected int cost() {\n        return cost;\n    }\n\n    @Override\n    boolean wantsNodes() {\n        return wantsNodes;\n    }\n\n    void updateEvaluators() {\n        // used so we don't need to bash on size() for every match test\n        num = evaluators.size();\n\n        // sort the evaluators by lowest cost first, to optimize the evaluation order\n        cost = 0;\n        for (Evaluator evaluator : evaluators) {\n            cost += evaluator.cost();\n        }\n        sortedEvaluators.clear();\n        sortedEvaluators.addAll(evaluators);\n        sortedEvaluators.sort(Comparator.comparingInt(Evaluator::cost));\n\n        // any want nodes?\n        for (Evaluator evaluator : evaluators) {\n            if (evaluator.wantsNodes()) {\n                wantsNodes = true;\n                break;\n            }\n        }\n    }\n\n    public static final class And extends CombiningEvaluator {\n        public And(Collection<Evaluator> evaluators) {\n            super(evaluators);\n        }\n\n        And(Evaluator... evaluators) {\n            this(Arrays.asList(evaluators));\n        }\n\n        @Override\n        public boolean matches(Element root, Element el) {\n            for (int i = 0; i < num; i++) {\n                Evaluator eval = sortedEvaluators.get(i);\n                if (!eval.matches(root, el))\n                    return false;\n            }\n            return true;\n        }\n\n        @Override\n        public boolean matches(Element root, LeafNode leaf) {\n            for (int i = 0; i < num; i++) {\n                Evaluator eval = sortedEvaluators.get(i);\n                if (!eval.matches(root, leaf))\n                    return false;\n            }\n            return true;\n        }\n\n        @Override\n        public String toString() {\n            return StringUtil.join(evaluators, \"\");\n        }\n    }\n\n    public static final class Or extends CombiningEvaluator {\n        /**\n         * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.\n         * @param evaluators initial OR clause (these are wrapped into an AND evaluator).\n         */\n        public Or(Collection<Evaluator> evaluators) {\n            super();\n            if (num > 1)\n                this.evaluators.add(new And(evaluators));\n            else // 0 or 1\n                this.evaluators.addAll(evaluators);\n            updateEvaluators();\n        }\n\n        Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); }\n\n        Or() {\n            super();\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            for (int i = 0; i < num; i++) {\n                Evaluator eval = sortedEvaluators.get(i);\n                if (eval.matches(root, element))\n                    return true;\n            }\n            return false;\n        }\n\n        @Override\n        public boolean matches(Element root, LeafNode leaf) {\n            for (int i = 0; i < num; i++) {\n                Evaluator eval = sortedEvaluators.get(i);\n                if (eval.matches(root, leaf))\n                    return true;\n            }\n            return false;\n        }\n\n        @Override\n        public String toString() {\n            return StringUtil.join(evaluators, \", \");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/Elements.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.FormElement;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n/**\n A list of {@link Element}s, with methods that act on every element in the list.\n <p>To get an {@code Elements} object, use the {@link Element#select(String)} method.</p>\n <p>Methods that {@link #set(int, Element) set}, {@link #remove(int) remove}, or {@link #replaceAll(UnaryOperator)\n replace} Elements in the list will also act on the underlying {@link org.jsoup.nodes.Document DOM}.</p>\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class Elements extends Nodes<Element> {\n    public Elements() {\n    }\n\n    public Elements(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public Elements(Collection<Element> elements) {\n        super(elements);\n    }\n\n    public Elements(List<Element> elements) {\n        super(elements);\n    }\n\n    public Elements(Element... elements) {\n    \tsuper(Arrays.asList(elements));\n    }\n\n    /**\n     * Creates a deep copy of these elements.\n     * @return a deep copy\n     */\n    @Override\n    public Elements clone() {\n        Elements clone = new Elements(size());\n        for (Element e : this)\n            clone.add(e.clone());\n        return clone;\n    }\n\n    /**\n     Convenience method to get the Elements as a plain ArrayList. This allows modification to the list of elements\n     without modifying the source Document. I.e. whereas calling {@code elements.remove(0)} will remove the element from\n     both the Elements and the DOM, {@code elements.asList().remove(0)} will remove the element from the list only.\n     <p>Each Element is still the same DOM connected Element.</p>\n\n     @return a new ArrayList containing the elements in this list\n     @since 1.19.2\n     @see #Elements(List)\n     */\n    @Override\n    public ArrayList<Element> asList() {\n        return new ArrayList<>(this);\n    }\n\n    // attribute methods\n    /**\n     Get an attribute value from the first matched element that has the attribute.\n     @param attributeKey The attribute key.\n     @return The attribute value from the first matched element that has the attribute. If no elements were matched (isEmpty() == true),\n     or if the no elements have the attribute, returns empty string.\n     @see #hasAttr(String)\n     */\n    public String attr(String attributeKey) {\n        for (Element element : this) {\n            if (element.hasAttr(attributeKey))\n                return element.attr(attributeKey);\n        }\n        return \"\";\n    }\n\n    /**\n     Checks if any of the matched elements have this attribute defined.\n     @param attributeKey attribute key\n     @return true if any of the elements have the attribute; false if none do.\n     */\n    public boolean hasAttr(String attributeKey) {\n        for (Element element : this) {\n            if (element.hasAttr(attributeKey))\n                return true;\n        }\n        return false;\n    }\n\n    /**\n     * Get the attribute value for each of the matched elements. If an element does not have this attribute, no value is\n     * included in the result set for that element.\n     * @param attributeKey the attribute name to return values for. You can add the {@code abs:} prefix to the key to\n     * get absolute URLs from relative URLs, e.g.: {@code doc.select(\"a\").eachAttr(\"abs:href\")} .\n     * @return a list of each element's attribute value for the attribute\n     */\n    public List<String> eachAttr(String attributeKey) {\n        List<String> attrs = new ArrayList<>(size());\n        for (Element element : this) {\n            if (element.hasAttr(attributeKey))\n                attrs.add(element.attr(attributeKey));\n        }\n        return attrs;\n    }\n\n    /**\n     * Set an attribute on all matched elements.\n     * @param attributeKey attribute key\n     * @param attributeValue attribute value\n     * @return this\n     */\n    public Elements attr(String attributeKey, String attributeValue) {\n        for (Element element : this) {\n            element.attr(attributeKey, attributeValue);\n        }\n        return this;\n    }\n\n    /**\n     * Remove an attribute from every matched element.\n     * @param attributeKey The attribute to remove.\n     * @return this (for chaining)\n     */\n    public Elements removeAttr(String attributeKey) {\n        for (Element element : this) {\n            element.removeAttr(attributeKey);\n        }\n        return this;\n    }\n\n    /**\n     Add the class name to every matched element's {@code class} attribute.\n     @param className class name to add\n     @return this\n     */\n    public Elements addClass(String className) {\n        for (Element element : this) {\n            element.addClass(className);\n        }\n        return this;\n    }\n\n    /**\n     Remove the class name from every matched element's {@code class} attribute, if present.\n     @param className class name to remove\n     @return this\n     */\n    public Elements removeClass(String className) {\n        for (Element element : this) {\n            element.removeClass(className);\n        }\n        return this;\n    }\n\n    /**\n     Toggle the class name on every matched element's {@code class} attribute.\n     @param className class name to add if missing, or remove if present, from every element.\n     @return this\n     */\n    public Elements toggleClass(String className) {\n        for (Element element : this) {\n            element.toggleClass(className);\n        }\n        return this;\n    }\n\n    /**\n     Determine if any of the matched elements have this class name set in their {@code class} attribute.\n     @param className class name to check for\n     @return true if any do, false if none do\n     */\n    public boolean hasClass(String className) {\n        for (Element element : this) {\n            if (element.hasClass(className))\n                return true;\n        }\n        return false;\n    }\n    \n    /**\n     * Get the form element's value of the first matched element.\n     * @return The form element's value, or empty if not set.\n     * @see Element#val()\n     */\n    public String val() {\n        if (size() > 0)\n            //noinspection ConstantConditions\n            return first().val(); // first() != null as size() > 0\n        else\n            return \"\";\n    }\n    \n    /**\n     * Set the form element's value in each of the matched elements.\n     * @param value The value to set into each matched element\n     * @return this (for chaining)\n     */\n    public Elements val(String value) {\n        for (Element element : this)\n            element.val(value);\n        return this;\n    }\n    \n    /**\n     * Get the combined text of all the matched elements.\n     * <p>\n     * Note that it is possible to get repeats if the matched elements contain both parent elements and their own\n     * children, as the Element.text() method returns the combined text of a parent and all its children.\n     * @return string of all text: unescaped and no HTML.\n     * @see Element#text()\n     * @see #eachText()\n     */\n    public String text() {\n        return stream()\n            .map(Element::text)\n            .collect(StringUtil.joining(\" \"));\n    }\n\n    /**\n     Test if any matched Element has any text content, that is not just whitespace.\n     @return true if any element has non-blank text content.\n     @see Element#hasText()\n     */\n    public boolean hasText() {\n        for (Element element: this) {\n            if (element.hasText())\n                return true;\n        }\n        return false;\n    }\n\n    /**\n     * Get the text content of each of the matched elements. If an element has no text, then it is not included in the\n     * result.\n     * @return A list of each matched element's text content.\n     * @see Element#text()\n     * @see Element#hasText()\n     * @see #text()\n     */\n    public List<String> eachText() {\n        ArrayList<String> texts = new ArrayList<>(size());\n        for (Element el: this) {\n            if (el.hasText())\n                texts.add(el.text());\n        }\n        return texts;\n    }\n    \n    /**\n     * Get the combined inner HTML of all matched elements.\n     * @return string of all element's inner HTML.\n     * @see #text()\n     * @see #outerHtml()\n     */\n    public String html() {\n        return stream()\n            .map(Element::html)\n            .collect(StringUtil.joining(\"\\n\"));\n    }\n\n    /**\n     * Update (rename) the tag name of each matched element. For example, to change each {@code <i>} to a {@code <em>}, do\n     * {@code doc.select(\"i\").tagName(\"em\");}\n     *\n     * @param tagName the new tag name\n     * @return this, for chaining\n     * @see Element#tagName(String)\n     */\n    public Elements tagName(String tagName) {\n        for (Element element : this) {\n            element.tagName(tagName);\n        }\n        return this;\n    }\n    \n    /**\n     * Set the inner HTML of each matched element.\n     * @param html HTML to parse and set into each matched element.\n     * @return this, for chaining\n     * @see Element#html(String)\n     */\n    public Elements html(String html) {\n        for (Element element : this) {\n            element.html(html);\n        }\n        return this;\n    }\n    \n    /**\n     * Add the supplied HTML to the start of each matched element's inner HTML.\n     * @param html HTML to add inside each element, before the existing HTML\n     * @return this, for chaining\n     * @see Element#prepend(String)\n     */\n    public Elements prepend(String html) {\n        for (Element element : this) {\n            element.prepend(html);\n        }\n        return this;\n    }\n    \n    /**\n     * Add the supplied HTML to the end of each matched element's inner HTML.\n     * @param html HTML to add inside each element, after the existing HTML\n     * @return this, for chaining\n     * @see Element#append(String)\n     */\n    public Elements append(String html) {\n        for (Element element : this) {\n            element.append(html);\n        }\n        return this;\n    }\n\n    /**\n     Insert the supplied HTML before each matched element's outer HTML.\n\n     @param html HTML to insert before each element\n     @return this, for chaining\n     @see Element#before(String)\n     */\n    @Override\n    public Elements before(String html) {\n        super.before(html);\n        return this;\n    }\n\n    /**\n     Insert the supplied HTML after each matched element's outer HTML.\n\n     @param html HTML to insert after each element\n     @return this, for chaining\n     @see Element#after(String)\n     */\n    @Override\n    public Elements after(String html) {\n        super.after(html);\n        return this;\n    }\n\n    /**\n     Wrap the supplied HTML around each matched elements. For example, with HTML\n     {@code <p><b>This</b> is <b>Jsoup</b></p>},\n     <code>doc.select(\"b\").wrap(\"&lt;i&gt;&lt;/i&gt;\");</code>\n     becomes {@code <p><i><b>This</b></i> is <i><b>jsoup</b></i></p>}\n\n     @param html HTML to wrap around each element, e.g. {@code <div class=\"head\"></div>}. Can be arbitrarily deep.\n     @return this (for chaining)\n     @see Element#wrap\n     */\n    @Override\n    public Elements wrap(String html) {\n        super.wrap(html);\n        return this;\n    }\n\n    /**\n     * Removes the matched elements from the DOM, and moves their children up into their parents. This has the effect of\n     * dropping the elements but keeping their children.\n     * <p>\n     * This is useful for e.g removing unwanted formatting elements but keeping their contents.\n     * </p>\n     * \n     * E.g. with HTML: <p>{@code <div><font>One</font> <font><a href=\"/\">Two</a></font></div>}</p>\n     * <p>{@code doc.select(\"font\").unwrap();}</p>\n     * <p>HTML = {@code <div>One <a href=\"/\">Two</a></div>}</p>\n     *\n     * @return this (for chaining)\n     * @see Node#unwrap\n     */\n    public Elements unwrap() {\n        for (Element element : this) {\n            element.unwrap();\n        }\n        return this;\n    }\n\n    /**\n     * Empty (remove all child nodes from) each matched element. This is similar to setting the inner HTML of each\n     * element to nothing.\n     * <p>\n     * E.g. HTML: {@code <div><p>Hello <b>there</b></p> <p>now</p></div>}<br>\n     * <code>doc.select(\"p\").empty();</code><br>\n     * HTML = {@code <div><p></p> <p></p></div>}\n     * @return this, for chaining\n     * @see Element#empty()\n     * @see #remove()\n     */\n    public Elements empty() {\n        for (Element element : this) {\n            element.empty();\n        }\n        return this;\n    }\n\n    /**\n     * Remove each matched element from the DOM. This is similar to setting the outer HTML of each element to nothing.\n     * <p>The elements will still be retained in this list, in case further processing of them is desired.</p>\n     * <p>\n     * E.g. HTML: {@code <div><p>Hello</p> <p>there</p> <img /></div>}<br>\n     * <code>doc.select(\"p\").remove();</code><br>\n     * HTML = {@code <div> <img /></div>}\n     * <p>\n     * Note that this method should not be used to clean user-submitted HTML; rather, use {@link org.jsoup.safety.Cleaner} to clean HTML.\n     * @return this, for chaining\n     * @see Element#empty()\n     * @see #empty()\n     * @see #clear()\n     */\n    @Override\n    public Elements remove() {\n        super.remove();\n        return this;\n    }\n    \n    // filters\n    \n    /**\n     * Find matching elements within this element list.\n     * @param query A {@link Selector} query\n     * @return the filtered list of elements, or an empty list if none match.\n     */\n    public Elements select(String query) {\n        return Selector.select(query, this);\n    }\n\n    /**\n     Find the first Element that matches the {@link Selector} CSS query within this element list.\n     <p>This is effectively the same as calling {@code elements.select(query).first()}, but is more efficient as query\n     execution stops on the first hit.</p>\n\n     @param cssQuery a {@link Selector} query\n     @return the first matching element, or <b>{@code null}</b> if there is no match.\n     @see #expectFirst(String)\n     @since 1.19.1\n     */\n    public @Nullable Element selectFirst(String cssQuery) {\n        return Selector.selectFirst(cssQuery, this);\n    }\n\n    /**\n     Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}.\n\n     @param cssQuery a {@link Selector} query\n     @return the first matching element\n     @throws IllegalArgumentException if no match is found\n     @since 1.19.1\n     */\n    public Element expectFirst(String cssQuery) {\n        return Validate.expectNotNull(\n            Selector.selectFirst(cssQuery, this),\n            \"No elements matched the query '%s' in the elements.\", cssQuery\n        );\n    }\n\n    /**\n     * Remove elements from this list that match the {@link Selector} query.\n     * <p>\n     * E.g. HTML: {@code <div class=logo>One</div> <div>Two</div>}<br>\n     * <code>Elements divs = doc.select(\"div\").not(\".logo\");</code><br>\n     * Result: {@code divs: [<div>Two</div>]}\n     * <p>\n     * @param query the selector query whose results should be removed from these elements\n     * @return a new elements list that contains only the filtered results\n     */\n    public Elements not(String query) {\n        Elements out = Selector.select(query, this);\n        return Selector.filterOut(this, out);\n    }\n    \n    /**\n     * Get the <i>nth</i> matched element as an Elements object.\n     * <p>\n     * See also {@link #get(int)} to retrieve an Element.\n     * @param index the (zero-based) index of the element in the list to retain\n     * @return Elements containing only the specified element, or, if that element did not exist, an empty list.\n     */\n    public Elements eq(int index) {\n        return size() > index ? new Elements(get(index)) : new Elements();\n    }\n    \n    /**\n     * Test if any of the matched elements match the supplied query.\n     * @param query A selector\n     * @return true if at least one element in the list matches the query.\n     */\n    public boolean is(String query) {\n        Evaluator eval = Selector.evaluatorOf(query);\n        for (Element e : this) {\n            if (e.is(eval))\n                return true;\n        }\n        return false;\n    }\n\n    /**\n     * Get the immediate next element sibling of each element in this list.\n     * @return next element siblings.\n     */\n    public Elements next() {\n        return siblings(null, true, false);\n    }\n\n    /**\n     * Get the immediate next element sibling of each element in this list, filtered by the query.\n     * @param query CSS query to match siblings against\n     * @return next element siblings.\n     */\n    public Elements next(String query) {\n        return siblings(query, true, false);\n    }\n\n    /**\n     * Get each of the following element siblings of each element in this list.\n     * @return all following element siblings.\n     */\n    public Elements nextAll() {\n        return siblings(null, true, true);\n    }\n\n    /**\n     * Get each of the following element siblings of each element in this list, that match the query.\n     * @param query CSS query to match siblings against\n     * @return all following element siblings.\n     */\n    public Elements nextAll(String query) {\n        return siblings(query, true, true);\n    }\n\n    /**\n     * Get the immediate previous element sibling of each element in this list.\n     * @return previous element siblings.\n     */\n    public Elements prev() {\n        return siblings(null, false, false);\n    }\n\n    /**\n     * Get the immediate previous element sibling of each element in this list, filtered by the query.\n     * @param query CSS query to match siblings against\n     * @return previous element siblings.\n     */\n    public Elements prev(String query) {\n        return siblings(query, false, false);\n    }\n\n    /**\n     * Get each of the previous element siblings of each element in this list.\n     * @return all previous element siblings.\n     */\n    public Elements prevAll() {\n        return siblings(null, false, true);\n    }\n\n    /**\n     * Get each of the previous element siblings of each element in this list, that match the query.\n     * @param query CSS query to match siblings against\n     * @return all previous element siblings.\n     */\n    public Elements prevAll(String query) {\n        return siblings(query, false, true);\n    }\n\n    private Elements siblings(@Nullable String query, boolean next, boolean all) {\n        Elements els = new Elements();\n        Evaluator eval = query != null? Selector.evaluatorOf(query) : null;\n        for (Element e : this) {\n            do {\n                Element sib = next ? e.nextElementSibling() : e.previousElementSibling();\n                if (sib == null) break;\n                if (eval == null || sib.is(eval)) els.add(sib);\n                e = sib;\n            } while (all);\n        }\n        return els;\n    }\n\n    /**\n     * Get all of the parents and ancestor elements of the matched elements.\n     * @return all of the parents and ancestor elements of the matched elements\n     */\n    public Elements parents() {\n        HashSet<Element> combo = new LinkedHashSet<>();\n        for (Element e: this) {\n            combo.addAll(e.parents());\n        }\n        return new Elements(combo);\n    }\n\n    // list-like methods\n    /**\n     Get the first matched element.\n     @return The first matched element, or <code>null</code> if contents is empty.\n     */\n    @Override\n    public @Nullable Element first() {\n        return super.first();\n    }\n\n    /**\n     Get the last matched element.\n     @return The last matched element, or <code>null</code> if contents is empty.\n     */\n    @Override\n    public @Nullable Element last() {\n        return super.last();\n    }\n\n    /**\n     * Perform a depth-first traversal on each of the selected elements.\n     * @param nodeVisitor the visitor callbacks to perform on each node\n     * @return this, for chaining\n     */\n    public Elements traverse(NodeVisitor nodeVisitor) {\n        NodeTraversor.traverse(nodeVisitor, this);\n        return this;\n    }\n\n    /**\n     * Perform a depth-first filtering on each of the selected elements.\n     * @param nodeFilter the filter callbacks to perform on each node\n     * @return this, for chaining\n     */\n    public Elements filter(NodeFilter nodeFilter) {\n        NodeTraversor.filter(nodeFilter, this);\n        return this;\n    }\n\n    /**\n     * Get the {@link FormElement} forms from the selected elements, if any.\n     * @return a list of {@link FormElement}s pulled from the matched elements. The list will be empty if the elements contain\n     * no forms.\n     */\n    public List<FormElement> forms() {\n        ArrayList<FormElement> forms = new ArrayList<>();\n        for (Element el: this)\n            if (el instanceof FormElement)\n                forms.add((FormElement) el);\n        return forms;\n    }\n\n    /**\n     * Get {@link Comment} nodes that are direct child nodes of the selected elements.\n     * @return Comment nodes, or an empty list if none.\n     */\n    public List<Comment> comments() {\n        return childNodesOfType(Comment.class);\n    }\n\n    /**\n     * Get {@link TextNode} nodes that are direct child nodes of the selected elements.\n     * @return TextNode nodes, or an empty list if none.\n     */\n    public List<TextNode> textNodes() {\n        return childNodesOfType(TextNode.class);\n    }\n\n    /**\n     * Get {@link DataNode} nodes that are direct child nodes of the selected elements. DataNode nodes contain the\n     * content of tags such as {@code script}, {@code style} etc and are distinct from {@link TextNode}s.\n     * @return Comment nodes, or an empty list if none.\n     */\n    public List<DataNode> dataNodes() {\n        return childNodesOfType(DataNode.class);\n    }\n\n    private <T extends Node> List<T> childNodesOfType(Class<T> tClass) {\n        ArrayList<T> nodes = new ArrayList<>();\n        for (Element el: this) {\n            for (int i = 0; i < el.childNodeSize(); i++) {\n                Node node = el.childNode(i);\n                if (tClass.isInstance(node))\n                    nodes.add(tClass.cast(node));\n            }\n        }\n        return nodes;\n    }\n\n    // list methods that update the DOM:\n\n    /**\n     Replace the Element at the specified index in this list, and in the DOM.\n\n     @param index index of the element to replace\n     @param element element to be stored at the specified position\n     @return the old Element at this index\n     @since 1.17.1\n     */\n    @Override\n    public Element set(int index, Element element) {\n        return super.set(index, element);\n    }\n\n    /**\n     Remove the Element at the specified index in this ist, and from the DOM.\n\n     @param index the index of the element to be removed\n     @return the old element at this index\n     @see #deselect(int)\n     @since 1.17.1\n     */\n    @Override\n    public Element remove(int index) {\n        return super.remove(index);\n    }\n\n\n    /**\n     Remove the Element at the specified index in this list, but not from the DOM.\n\n     @param index the index of the element to be removed\n     @return the old element at this index\n     @see #remove(int)\n     @since 1.19.2\n     */\n    @Override\n    public Element deselect(int index) {\n        return super.deselect(index);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/Evaluator.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.DocumentType;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.PseudoTextElement;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.nodes.XmlDeclaration;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.helper.Regex;\n\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.regex.Pattern;\n\nimport static org.jsoup.internal.Normalizer.lowerCase;\nimport static org.jsoup.internal.Normalizer.normalize;\nimport static org.jsoup.internal.StringUtil.normaliseWhitespace;\n\n\n/**\n An Evaluator tests if an element (or a node) meets the selector's requirements. Obtain an evaluator for a given CSS selector\n with {@link Selector#evaluatorOf(String css)}. If you are executing the same selector on many elements (or documents), it\n can be more efficient to compile and reuse an Evaluator than to reparse the selector on each invocation of select().\n <p>Evaluators are thread-safe and may be used concurrently across multiple documents.</p>\n */\npublic abstract class Evaluator {\n    protected Evaluator() {\n    }\n\n    /**\n     Provides a Predicate for this Evaluator, matching the test Element.\n     * @param root the root Element, for match evaluation\n     * @return a predicate that accepts an Element to test for matches with this Evaluator\n     * @since 1.17.1\n     */\n    public Predicate<Element> asPredicate(Element root) {\n        return element -> matches(root, element);\n    }\n\n    Predicate<Node> asNodePredicate(Element root) {\n        return node -> matches(root, node);\n    }\n\n    /**\n     * Test if the element meets the evaluator's requirements.\n     *\n     * @param root    Root of the matching subtree\n     * @param element tested element\n     * @return Returns <tt>true</tt> if the requirements are met or\n     * <tt>false</tt> otherwise\n     */\n    public abstract boolean matches(Element root, Element element);\n\n    final boolean matches(Element root, Node node) {\n        if (node instanceof Element) {\n            return matches(root, (Element) node);\n        } else if (node instanceof LeafNode && wantsNodes()) {\n            return matches(root, (LeafNode) node);\n        }\n        return false;\n    }\n\n    boolean matches(Element root, LeafNode leafNode) {\n        return false;\n    }\n\n    boolean wantsNodes() {\n        return false;\n    }\n\n    /**\n     Reset any internal state in this Evaluator before executing a new Collector evaluation.\n     */\n    protected void reset() {\n    }\n\n    /**\n     A relative evaluator cost function. During evaluation, Evaluators are sorted by ascending cost as an optimization.\n     * @return the relative cost of this Evaluator\n     */\n    protected int cost() {\n        return 5; // a nominal default cost\n    }\n\n    /**\n     * Evaluator for tag name\n     */\n    public static final class Tag extends Evaluator {\n        private final String tagName;\n\n        public Tag(String tagName) {\n            this.tagName = tagName;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return (element.nameIs(tagName));\n        }\n\n        @Override protected int cost() {\n            return 1;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"%s\", tagName);\n        }\n    }\n\n    /**\n     * Evaluator for tag name that starts with prefix; used for ns|*\n     */\n    public static final class TagStartsWith extends Evaluator {\n        private final String tagName;\n\n        public TagStartsWith(String tagName) {\n            this.tagName = tagName;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return (element.normalName().startsWith(tagName));\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"%s|*\", tagName);\n        }\n    }\n\n\n    /**\n     * Evaluator for tag name that ends with suffix; used for *|el\n     */\n    public static final class TagEndsWith extends Evaluator {\n        private final String tagName;\n\n        public TagEndsWith(String tagName) {\n            this.tagName = tagName;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return (element.normalName().endsWith(tagName));\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"*|%s\", tagName);\n        }\n    }\n\n    /**\n     * Evaluator for element id\n     */\n    public static final class Id extends Evaluator {\n        private final String id;\n\n        public Id(String id) {\n            this.id = id;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return (id.equals(element.id()));\n        }\n\n        @Override protected int cost() {\n            return 2;\n        }\n        @Override\n        public String toString() {\n            return String.format(\"#%s\", id);\n        }\n    }\n\n    /**\n     * Evaluator for element class\n     */\n    public static final class Class extends Evaluator {\n        private final String className;\n\n        public Class(String className) {\n            this.className = className;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return (element.hasClass(className));\n        }\n\n        @Override protected int cost() {\n            return 8; // does whitespace scanning; more than .contains()\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\".%s\", className);\n        }\n\n    }\n\n    /**\n     * Evaluator for attribute name matching\n     */\n    public static final class Attribute extends Evaluator {\n        private final String key;\n\n        public Attribute(String key) {\n            this.key = key;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key);\n        }\n\n        @Override protected int cost() {\n            return 2;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s]\", key);\n        }\n    }\n\n    /**\n     * Evaluator for attribute name prefix matching\n     */\n    public static final class AttributeStarting extends Evaluator {\n        private final String keyPrefix;\n\n        public AttributeStarting(String keyPrefix) {\n            Validate.notNull(keyPrefix); // OK to be empty - will find elements with any attributes\n            this.keyPrefix = lowerCase(keyPrefix);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            List<org.jsoup.nodes.Attribute> values = element.attributes().asList();\n            for (org.jsoup.nodes.Attribute attribute : values) {\n                if (lowerCase(attribute.getKey()).startsWith(keyPrefix))\n                    return true;\n            }\n            return false;\n        }\n\n        @Override protected int cost() {\n            return 6;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[^%s]\", keyPrefix);\n        }\n\n    }\n\n    /**\n     * Evaluator for attribute name/value matching\n     */\n    public static final class AttributeWithValue extends AttributeKeyPair {\n        public AttributeWithValue(String key, String value) {\n            super(key, value);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key) && value.equalsIgnoreCase(element.attr(key));\n        }\n\n        @Override protected int cost() {\n            return 3;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s=%s]\", key, value);\n        }\n\n    }\n\n    /**\n     * Evaluator for attribute name != value matching\n     */\n    public static final class AttributeWithValueNot extends AttributeKeyPair {\n        public AttributeWithValueNot(String key, String value) {\n            super(key, value);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return !value.equalsIgnoreCase(element.attr(key));\n        }\n\n        @Override protected int cost() {\n            return 3;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s!=%s]\", key, value);\n        }\n\n    }\n\n    /**\n     * Evaluator for attribute name/value matching (value prefix)\n     */\n    public static final class AttributeWithValueStarting extends AttributeKeyPair {\n        public AttributeWithValueStarting(String key, String value) {\n            super(key, value);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key) && lowerCase(element.attr(key)).startsWith(value); // value is lower case already\n        }\n\n        @Override protected int cost() {\n            return 4;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s^=%s]\", key, value);\n        }\n    }\n\n    /**\n     * Evaluator for attribute name/value matching (value ending)\n     */\n    public static final class AttributeWithValueEnding extends AttributeKeyPair {\n        public AttributeWithValueEnding(String key, String value) {\n            super(key, value);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key) && lowerCase(element.attr(key)).endsWith(value); // value is lower case\n        }\n\n        @Override protected int cost() {\n            return 4;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s$=%s]\", key, value);\n        }\n    }\n\n    /**\n     * Evaluator for attribute name/value matching (value containing)\n     */\n    public static final class AttributeWithValueContaining extends AttributeKeyPair {\n        public AttributeWithValueContaining(String key, String value) {\n            super(key, value);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key) && lowerCase(element.attr(key)).contains(value); // value is lower case\n        }\n\n        @Override protected int cost() {\n            return 6;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s*=%s]\", key, value);\n        }\n\n    }\n\n    /**\n     * Evaluator for attribute name/value matching (value regex matching)\n     */\n    public static final class AttributeWithValueMatching extends Evaluator {\n        final String key;\n        final Regex pattern;\n\n        public AttributeWithValueMatching(String key, Regex pattern) {\n            this.key = normalize(key);\n            this.pattern = pattern;\n        }\n\n        public AttributeWithValueMatching(String key, Pattern pattern) {\n            this(key, Regex.fromPattern(pattern)); // api compat\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.hasAttr(key) && pattern.matcher(element.attr(key)).find();\n        }\n\n        @Override protected int cost() {\n            return 8;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"[%s~=%s]\", key, pattern.toString());\n        }\n\n    }\n\n    /**\n     * Abstract evaluator for attribute name/value matching\n     */\n    public abstract static class AttributeKeyPair extends Evaluator {\n        final String key;\n        final String value;\n\n        public AttributeKeyPair(String key, String value) {\n            Validate.notEmpty(key);\n            Validate.notNull(value);\n\n            this.key = normalize(key);\n            boolean quoted = value.startsWith(\"'\") && value.endsWith(\"'\")\n                || value.startsWith(\"\\\"\") && value.endsWith(\"\\\"\");\n            if (quoted) {\n                Validate.isTrue(value.length() > 1, \"Quoted value must have content\");\n                value = value.substring(1, value.length() - 1);\n            }\n\n            this.value = lowerCase(value); // case-insensitive match\n        }\n\n        /**\n         @deprecated since 1.22.1, use {@link #AttributeKeyPair(String, String)}; the previous trimQuoted parameter is no longer used.\n         This constructor will be removed in jsoup 1.24.1.\n         */\n        @Deprecated\n        public AttributeKeyPair(String key, String value, boolean ignored) {\n            this(key, value);\n        }\n\n\n    }\n\n    /**\n     * Evaluator for any / all element matching\n     */\n    public static final class AllElements extends Evaluator {\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return true;\n        }\n\n        @Override protected int cost() {\n            return 10;\n        }\n\n        @Override\n        public String toString() {\n            return \"*\";\n        }\n    }\n\n    /**\n     * Evaluator for matching by sibling index number (e {@literal <} idx)\n     */\n    public static final class IndexLessThan extends IndexEvaluator {\n        public IndexLessThan(int index) {\n            super(index);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return root != element && element.elementSiblingIndex() < index;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":lt(%d)\", index);\n        }\n\n    }\n\n    /**\n     * Evaluator for matching by sibling index number (e {@literal >} idx)\n     */\n    public static final class IndexGreaterThan extends IndexEvaluator {\n        public IndexGreaterThan(int index) {\n            super(index);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.elementSiblingIndex() > index;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":gt(%d)\", index);\n        }\n\n    }\n\n    /**\n     * Evaluator for matching by sibling index number (e = idx)\n     */\n    public static final class IndexEquals extends IndexEvaluator {\n        public IndexEquals(int index) {\n            super(index);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.elementSiblingIndex() == index;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":eq(%d)\", index);\n        }\n\n    }\n\n    /**\n     * Evaluator for matching the last sibling (css :last-child)\n     */\n    public static final class IsLastChild extends Evaluator {\n\t\t@Override\n\t\tpublic boolean matches(Element root, Element element) {\n\t\t\tfinal Element p = element.parent();\n\t\t\treturn p != null && !(p instanceof Document) && element == p.lastElementChild();\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \":last-child\";\n\t\t}\n    }\n\n    public static final class IsFirstOfType extends IsNthOfType {\n\t\tpublic IsFirstOfType() {\n\t\t\tsuper(0,1);\n\t\t}\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \":first-of-type\";\n\t\t}\n    }\n\n    public static final class IsLastOfType extends IsNthLastOfType {\n\t\tpublic IsLastOfType() {\n\t\t\tsuper(0,1);\n\t\t}\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \":last-of-type\";\n\t\t}\n    }\n\n\n    public static abstract class CssNthEvaluator extends Evaluator {\n        /** Step */\n        protected final int a;\n        /** Offset */\n        protected final int b;\n\n        public CssNthEvaluator(int step, int offset) {\n            this.a = step;\n            this.b = offset;\n        }\n\n        public CssNthEvaluator(int offset) {\n            this(0, offset);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            final Element p = element.parent();\n            if (p == null || (p instanceof Document)) return false;\n\n            final int pos = calculatePosition(root, element);\n            if (a == 0) return pos == b;\n\n            return (pos - b) * a >= 0 && (pos - b) % a == 0;\n        }\n\n        @Override\n        public String toString() {\n            String format =\n                (a == 0) ? \":%s(%3$d)\"    // only offset (b)\n                : (b == 0) ? \":%s(%2$dn)\" // only step (a)\n                : \":%s(%2$dn%3$+d)\";      // step, offset\n            return String.format(format, getPseudoClass(), a, b);\n        }\n\n        protected abstract String getPseudoClass();\n\n        protected abstract int calculatePosition(Element root, Element element);\n    }\n\n\n    /**\n     * css-compatible Evaluator for :eq (css :nth-child)\n     *\n     * @see IndexEquals\n     */\n    public static final class IsNthChild extends CssNthEvaluator {\n        public IsNthChild(int step, int offset) {\n            super(step, offset);\n        }\n\n        @Override\n        protected int calculatePosition(Element root, Element element) {\n            return element.elementSiblingIndex() + 1;\n        }\n\n        @Override\n        protected String getPseudoClass() {\n            return \"nth-child\";\n        }\n    }\n\n    /**\n     * css pseudo class :nth-last-child)\n     *\n     * @see IndexEquals\n     */\n    public static final class IsNthLastChild extends CssNthEvaluator {\n        public IsNthLastChild(int step, int offset) {\n            super(step, offset);\n        }\n\n        @Override\n        protected int calculatePosition(Element root, Element element) {\n    \t    if (element.parent() == null) return 0;\n        \treturn element.parent().childrenSize() - element.elementSiblingIndex();\n        }\n\n\t\t@Override\n\t\tprotected String getPseudoClass() {\n\t\t\treturn \"nth-last-child\";\n\t\t}\n    }\n\n    /**\n     * css pseudo class nth-of-type\n     *\n     */\n    public static class IsNthOfType extends CssNthEvaluator {\n        public IsNthOfType(int step, int offset) {\n            super(step, offset);\n        }\n\n        @Override protected int calculatePosition(Element root, Element element) {\n            Element parent = element.parent();\n            if (parent == null)\n                return 0;\n\n            int pos = 0;\n            final int size = parent.childNodeSize();\n            for (int i = 0; i < size; i++) {\n                Node node = parent.childNode(i);\n                if (node.normalName().equals(element.normalName())) pos++;\n                if (node == element) break;\n            }\n            return pos;\n        }\n\n        @Override\n        protected String getPseudoClass() {\n            return \"nth-of-type\";\n        }\n    }\n\n    public static class IsNthLastOfType extends CssNthEvaluator {\n        public IsNthLastOfType(int step, int offset) {\n            super(step, offset);\n        }\n\n        @Override\n        protected int calculatePosition(Element root, Element element) {\n            Element parent = element.parent();\n            if (parent == null)\n                return 0;\n\n            int pos = 0;\n            Element next = element;\n            while (next != null) {\n                if (next.normalName().equals(element.normalName()))\n                    pos++;\n                next = next.nextElementSibling();\n            }\n            return pos;\n        }\n\n        @Override\n        protected String getPseudoClass() {\n            return \"nth-last-of-type\";\n        }\n    }\n\n    /**\n     * Evaluator for matching the first sibling (css :first-child)\n     */\n    public static final class IsFirstChild extends Evaluator {\n    \t@Override\n    \tpublic boolean matches(Element root, Element element) {\n    \t\tfinal Element p = element.parent();\n    \t\treturn p != null && !(p instanceof Document) && element == p.firstElementChild();\n    \t}\n\n    \t@Override\n    \tpublic String toString() {\n    \t\treturn \":first-child\";\n    \t}\n    }\n\n    /**\n     * css3 pseudo-class :root\n     * @see <a href=\"http://www.w3.org/TR/selectors/#root-pseudo\">:root selector</a>\n     *\n     */\n    public static final class IsRoot extends Evaluator {\n    \t@Override\n    \tpublic boolean matches(Element root, Element element) {\n    \t\tfinal Element r = root instanceof Document ? root.firstElementChild() : root;\n    \t\treturn element == r;\n    \t}\n\n        @Override protected int cost() {\n            return 1;\n        }\n\n    \t@Override\n    \tpublic String toString() {\n    \t\treturn \":root\";\n    \t}\n    }\n\n    public static final class IsOnlyChild extends Evaluator {\n\t\t@Override\n\t\tpublic boolean matches(Element root, Element element) {\n\t\t\tfinal Element p = element.parent();\n\t\t\treturn p!=null && !(p instanceof Document) && element.siblingElements().isEmpty();\n\t\t}\n    \t@Override\n    \tpublic String toString() {\n    \t\treturn \":only-child\";\n    \t}\n    }\n\n    public static final class IsOnlyOfType extends Evaluator {\n\t\t@Override\n\t\tpublic boolean matches(Element root, Element element) {\n\t\t\tfinal Element p = element.parent();\n\t\t\tif (p==null || p instanceof Document) return false;\n\n\t\t\tint pos = 0;\n            Element next = p.firstElementChild();\n            while (next != null) {\n                if (next.normalName().equals(element.normalName()))\n                    pos++;\n                if (pos > 1)\n                    break;\n                next = next.nextElementSibling();\n            }\n        \treturn pos == 1;\n\t\t}\n    \t@Override\n    \tpublic String toString() {\n    \t\treturn \":only-of-type\";\n    \t}\n    }\n\n    public static final class IsEmpty extends Evaluator {\n        @Override\n        public boolean matches(Element root, Element el) {\n            for (Node n = el.firstChild(); n != null; n = n.nextSibling()) {\n                if (n instanceof TextNode) {\n                    if (!((TextNode) n).isBlank())\n                        return false; // non-blank text: not empty\n                } else if (!(n instanceof Comment || n instanceof XmlDeclaration || n instanceof DocumentType))\n                    return false; // non \"blank\" element: not empty\n            }\n            return true;\n        }\n\n        @Override\n        public String toString() {\n            return \":empty\";\n        }\n    }\n\n    /**\n     * Abstract evaluator for sibling index matching\n     *\n     * @author ant\n     */\n    public abstract static class IndexEvaluator extends Evaluator {\n        final int index;\n\n        public IndexEvaluator(int index) {\n            this.index = index;\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (and its descendants) text\n     */\n    public static final class ContainsText extends Evaluator {\n        private final String searchText;\n\n        public ContainsText(String searchText) {\n            this.searchText = lowerCase(normaliseWhitespace(searchText));\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return lowerCase(element.text()).contains(searchText);\n        }\n\n        @Override protected int cost() {\n            return 10;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":contains(%s)\", searchText);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (and its descendants) wholeText. Neither the input nor the element text is\n     * normalized. <code>:containsWholeText()</code>\n     * @since 1.15.1.\n     */\n    public static final class ContainsWholeText extends Evaluator {\n        private final String searchText;\n\n        public ContainsWholeText(String searchText) {\n            this.searchText = searchText;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.wholeText().contains(searchText);\n        }\n\n        @Override protected int cost() {\n            return 10;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":containsWholeText(%s)\", searchText);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (but <b>not</b> its descendants) wholeText. Neither the input nor the element text is\n     * normalized. <code>:containsWholeOwnText()</code>\n     * @since 1.15.1.\n     */\n    public static final class ContainsWholeOwnText extends Evaluator {\n        private final String searchText;\n\n        public ContainsWholeOwnText(String searchText) {\n            this.searchText = searchText;\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return element.wholeOwnText().contains(searchText);\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":containsWholeOwnText(%s)\", searchText);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (and its descendants) data\n     */\n    public static final class ContainsData extends Evaluator {\n        private final String searchText;\n\n        public ContainsData(String searchText) {\n            this.searchText = lowerCase(searchText);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return lowerCase(element.data()).contains(searchText); // not whitespace normalized\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":containsData(%s)\", searchText);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element's own text\n     */\n    public static final class ContainsOwnText extends Evaluator {\n        private final String searchText;\n\n        public ContainsOwnText(String searchText) {\n            this.searchText = lowerCase(normaliseWhitespace(searchText));\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return lowerCase(element.ownText()).contains(searchText);\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":containsOwn(%s)\", searchText);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (and its descendants) text with regex\n     */\n    public static final class Matches extends Evaluator {\n        private final Regex pattern;\n\n        public Matches(Regex pattern) {\n            this.pattern = pattern;\n        }\n\n        public Matches(Pattern pattern) {\n            this(Regex.fromPattern(pattern));\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return pattern.matcher(element.text()).find();\n        }\n\n        @Override protected int cost() {\n            return 8;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":matches(%s)\", pattern);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element's own text with regex\n     */\n    public static final class MatchesOwn extends Evaluator {\n        private final Regex pattern;\n\n        public MatchesOwn(Regex pattern) {\n            this.pattern = pattern;\n        }\n\n        public MatchesOwn(Pattern pattern) {\n            this(Regex.fromPattern(pattern));\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return pattern.matcher(element.ownText()).find();\n        }\n\n        @Override protected int cost() {\n            return 7;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":matchesOwn(%s)\", pattern);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element (and its descendants) whole text with regex.\n     * @since 1.15.1.\n     */\n    public static final class MatchesWholeText extends Evaluator {\n        private final Regex pattern;\n\n        public MatchesWholeText(Regex pattern) {\n            this.pattern = pattern;\n        }\n\n        public MatchesWholeText(Pattern pattern) {\n            this.pattern = Regex.fromPattern(pattern);\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            return pattern.matcher(element.wholeText()).find();\n        }\n\n        @Override protected int cost() {\n            return 8;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":matchesWholeText(%s)\", pattern);\n        }\n    }\n\n    /**\n     * Evaluator for matching Element's own whole text with regex.\n     * @since 1.15.1.\n     */\n    public static final class MatchesWholeOwnText extends Evaluator {\n        private final Regex pattern;\n\n        public MatchesWholeOwnText(Regex pattern) {\n            this.pattern = pattern;\n        }\n\n        public MatchesWholeOwnText(Pattern pattern) {\n            this(Regex.fromPattern(pattern));\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            Regex.Matcher m = pattern.matcher(element.wholeOwnText());\n            return m.find();\n        }\n\n        @Override protected int cost() {\n            return 7;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":matchesWholeOwnText(%s)\", pattern);\n        }\n    }\n\n    /**\n     @deprecated This selector is deprecated and will be removed in jsoup 1.24.1. Migrate to <code>::textnode</code> using the <code>Element#selectNodes()</code> method instead.\n     */\n    @Deprecated\n    public static final class MatchText extends Evaluator {\n        private static boolean loggedError = false;\n\n        public MatchText() {\n            // log a deprecated error on first use; users typically won't directly construct this Evaluator and so won't otherwise get deprecation warnings\n            if (!loggedError) {\n                loggedError = true;\n                System.err.println(\"WARNING: :matchText selector is deprecated and will be removed in jsoup 1.24.1. Use Element#selectNodes(String, Class) with selector ::textnode and class TextNode instead.\");\n            }\n        }\n\n        @Override\n        public boolean matches(Element root, Element element) {\n            if (element instanceof PseudoTextElement)\n                return true;\n\n            List<TextNode> textNodes = element.textNodes();\n            for (TextNode textNode : textNodes) {\n                PseudoTextElement pel = new PseudoTextElement(\n                    org.jsoup.parser.Tag.valueOf(element.tagName(), element.tag().namespace(), ParseSettings.preserveCase), element.baseUri(), element.attributes());\n                textNode.replaceWith(pel);\n                pel.appendChild(textNode);\n            }\n            return false;\n        }\n\n        @Override protected int cost() {\n            return -1; // forces first evaluation, which prepares the DOM for later evaluator matches\n        }\n\n        @Override\n        public String toString() {\n            return \":matchText\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/NodeEvaluator.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.helper.Regex;\n\nimport static org.jsoup.internal.Normalizer.lowerCase;\nimport static org.jsoup.internal.StringUtil.normaliseWhitespace;\n\nabstract class NodeEvaluator extends Evaluator {\n\n    @Override\n    public boolean matches(Element root, Element element) {\n        return evaluateMatch(element);\n    }\n\n    @Override boolean matches(Element root, LeafNode leaf) {\n        return evaluateMatch(leaf);\n    }\n\n    abstract boolean evaluateMatch(Node node);\n    \n    @Override boolean wantsNodes() {\n        return true;\n    }\n\n    static class InstanceType extends NodeEvaluator {\n        final java.lang.Class<? extends Node> type;\n        final String selector;\n\n        InstanceType(java.lang.Class<? extends Node> type, String selector) {\n            super();\n            this.type = type;\n            this.selector = \"::\" + selector;\n        }\n\n        @Override\n        boolean evaluateMatch(Node node) {\n            return type.isInstance(node);\n        }\n\n        @Override\n        protected int cost() {\n            return 1;\n        }\n\n        @Override\n        public String toString() {\n            return selector;\n        }\n    }\n\n    static class ContainsValue extends NodeEvaluator {\n        private final String searchText;\n\n        public ContainsValue(String searchText) {\n            this.searchText = lowerCase(normaliseWhitespace(searchText));\n        }\n\n        @Override\n        boolean evaluateMatch(Node node) {\n            return lowerCase(node.nodeValue()).contains(searchText);\n        }\n\n        @Override\n        protected int cost() {\n            return 6;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":contains(%s)\", searchText);\n        }\n    }\n\n    /**\n     Matches nodes with no value or only whitespace.\n     */\n    static class BlankValue extends NodeEvaluator {\n\n        @Override\n        boolean evaluateMatch(Node node) {\n            return StringUtil.isBlank(node.nodeValue());\n        }\n\n        @Override\n        protected int cost() {\n            return 4;\n        }\n\n        @Override\n        public String toString() {\n            return \":blank\";\n        }\n    }\n\n    static class MatchesValue extends NodeEvaluator {\n        private final Regex pattern;\n\n        protected MatchesValue(Regex pattern) {\n            this.pattern = pattern;\n        }\n\n        @Override\n        boolean evaluateMatch(Node node) {\n            return pattern.matcher(node.nodeValue()).find();\n        }\n\n        @Override\n        protected int cost() {\n            return 8;\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":matches(%s)\", pattern);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/NodeFilter.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.nodes.Node;\n\n/**\n A controllable Node visitor interface. Execute via {@link #traverse(Node)}.\n <p>\n This interface provides two methods, {@code head} and {@code tail}. The head method is called when a node is first seen,\n and the tail method when all that node's children have been visited.\n </p>\n <p>\n For each visited node, the resulting action may be:\n <ul>\n <li>continue ({@link FilterResult#CONTINUE}),</li>\n <li>skip all children ({@link FilterResult#SKIP_CHILDREN}),</li>\n <li>skip node entirely ({@link FilterResult#SKIP_ENTIRELY}),</li>\n <li>remove the subtree ({@link FilterResult#REMOVE}),</li>\n <li>interrupt the iteration and return ({@link FilterResult#STOP}).</li>\n </ul>\n The difference between {@link FilterResult#SKIP_CHILDREN} and {@link FilterResult#SKIP_ENTIRELY} is that the first\n will invoke {@link NodeFilter#tail(Node, int)} on the node, while the latter will not.\n Within {@link NodeFilter#tail(Node, int)}, both are equivalent to {@link FilterResult#CONTINUE}.\n </p>\n */\npublic interface NodeFilter {\n    /**\n     Traversal action.\n     */\n    enum FilterResult {\n        /** Continue processing the tree */\n        CONTINUE,\n        /** Skip the child nodes, but do call {@link NodeFilter#tail(Node, int)} next. */\n        SKIP_CHILDREN,\n        /** Skip the subtree, and do not call {@link NodeFilter#tail(Node, int)}. */\n        SKIP_ENTIRELY,\n        /** Remove the node and its children */\n        REMOVE,\n        /** Stop processing */\n        STOP\n    }\n\n    /**\n     * Callback for when a node is first visited.\n     * @param node the node being visited.\n     * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1.\n     * @return Traversal action\n     */\n    FilterResult head(Node node, int depth);\n\n    /**\n     * Callback for when a node is last visited, after all of its descendants have been visited.\n     * <p>This method has a default implementation to return {@link FilterResult#CONTINUE}.</p>\n     * @param node the node being visited.\n     * @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node of that will have depth 1.\n     * @return Traversal action\n     */\n    default FilterResult tail(Node node, int depth) {\n        return FilterResult.CONTINUE;\n    }\n\n    /**\n     Run a depth-first controlled traverse of the root and all of its descendants.\n     @param root the initial node point to traverse.\n     @since 1.21.1\n     */\n    default void traverse(Node root) {\n        NodeTraversor.filter(this, root);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/NodeTraversor.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.select.NodeFilter.FilterResult;\n\n/**\n A depth-first node traversor. Use to walk through all nodes under and including the specified root node, in document\n order. The {@link NodeVisitor#head(Node, int)} and {@link NodeVisitor#tail(Node, int)} methods will be called for\n each node.\n <p>During the <code>head()</code> visit, DOM structural changes around the node currently being visited are\n supported, including {@link Node#replaceWith(Node)} and {@link Node#remove()}. See\n {@link NodeVisitor#head(Node, int) head()} for the traversal contract after mutation. Other non-structural node\n changes are also supported.</p>\n <p>DOM structural changes to the current node are not supported during the <code>tail()</code> visit.</p>\n */\npublic class NodeTraversor {\n    // cursor state\n    private static final byte VisitHead = 0;\n    private static final byte AfterHead = 1;\n    private static final byte VisitTail = 2;\n\n    /**\n     Run a depth-first traverse of the root and all of its descendants.\n     @param visitor Node visitor.\n     @param root the initial node point to traverse.\n     @see NodeVisitor#traverse(Node root)\n     */\n    public static void traverse(NodeVisitor visitor, Node root) {\n        Validate.notNull(visitor);\n        Validate.notNull(root);\n        Node node = root;\n        final Node rootNext = root.nextSibling(); // don't traverse siblings beyond the original root\n        int depth = 0;\n        byte state = VisitHead;\n\n        while (true) {\n            if (state == VisitHead) {\n                // snapshot the current cursor position so we can recover if head() structurally changes it:\n                Node parent   = node.parentNode();\n                Node next     = node.nextSibling();\n                int  sibIndex = parent != null ? node.siblingIndex() : 0;\n\n                visitor.head(node, depth);\n\n                // any structural changes?\n                if (parent != null && node.parentNode() != parent) { // removed / replaced / moved\n                    Node occupant = sibIndex < parent.childNodeSize() ? parent.childNode(sibIndex) : null;\n                    // ^^ the node now at this node's former position\n                    Node boundary = depth == 0 ? rootNext : next;   // don't advance beyond this node when resuming\n                    if (occupant != null && occupant != boundary) {\n                        node = occupant;\n                        state = AfterHead;                          // continue from that slot without re-heading it\n                    } else if (depth == 0) {                        // root detached or replaced\n                        break;\n                    } else if (next != null && next.parentNode() == parent) {\n                        node = next;                                // old slot is empty or shifted to the original next, visit\n                    } else {                                        // removed last child; tail the parent next\n                        node = parent;\n                        depth--;\n                        state = VisitTail;\n                    }\n                } else {\n                    state = AfterHead;\n                }\n                continue;                                           // next loop handles the updated node/state\n            }\n\n            if (state == AfterHead && node.childNodeSize() > 0) { // descend into current children\n                node = node.childNode(0);\n                depth++;\n                state = VisitHead;\n                continue;\n            }\n\n            visitor.tail(node, depth);\n\n            Node next = node.nextSibling();\n            if (depth == 0) {\n                if (next == null || next == rootNext) break; // done with the original root range\n                node = next;\n                state = VisitHead;\n            } else if (next != null) { // traverse siblings\n                node = next;\n                state = VisitHead;\n            } else {                // no siblings left, ascend\n                node = node.parentNode();\n                depth--;\n                state = VisitTail;\n            }\n        }\n    }\n\n    /**\n     Run a depth-first traversal of each Element.\n     @param visitor Node visitor.\n     @param elements Elements to traverse.\n     */\n    public static void traverse(NodeVisitor visitor, Elements elements) {\n        Validate.notNull(visitor);\n        Validate.notNull(elements);\n        for (Element el : elements)\n            traverse(visitor, el);\n    }\n\n    /**\n     Run a depth-first filtered traversal of the root and all of its descendants.\n     @param filter NodeFilter visitor.\n     @param root the root node point to traverse.\n     @return The filter result of the root node, or {@link FilterResult#STOP}.\n\n     @see NodeFilter\n     */\n    public static FilterResult filter(NodeFilter filter, Node root) {\n        Node node = root;\n        int depth = 0;\n\n        while (node != null) {\n            FilterResult result = filter.head(node, depth);\n            if (result == FilterResult.STOP)\n                return result;\n            // Descend into child nodes:\n            if (result == FilterResult.CONTINUE && node.childNodeSize() > 0) {\n                node = node.childNode(0);\n                ++depth;\n                continue;\n            }\n            // No siblings, move upwards:\n            while (true) {\n                assert node != null; // depth > 0, so has parent\n                if (!(node.nextSibling() == null && depth > 0)) break;\n                // 'tail' current node:\n                if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) {\n                    result = filter.tail(node, depth);\n                    if (result == FilterResult.STOP)\n                        return result;\n                }\n                Node prev = node; // In case we need to remove it below.\n                node = node.parentNode();\n                depth--;\n                if (result == FilterResult.REMOVE)\n                    prev.remove(); // Remove AFTER finding parent.\n                result = FilterResult.CONTINUE; // Parent was not pruned.\n            }\n            // 'tail' current node, then proceed with siblings:\n            if (result == FilterResult.CONTINUE || result == FilterResult.SKIP_CHILDREN) {\n                result = filter.tail(node, depth);\n                if (result == FilterResult.STOP)\n                    return result;\n            }\n            if (node == root)\n                return result;\n            Node prev = node; // In case we need to remove it below.\n            node = node.nextSibling();\n            if (result == FilterResult.REMOVE)\n                prev.remove(); // Remove AFTER finding sibling.\n        }\n        // root == null?\n        return FilterResult.CONTINUE;\n    }\n\n    /**\n     Run a depth-first filtered traversal of each Element.\n     @param filter NodeFilter visitor.\n     @see NodeFilter\n     */\n    public static void filter(NodeFilter filter, Elements elements) {\n        Validate.notNull(filter);\n        Validate.notNull(elements);\n        for (Element el : elements)\n            if (filter(filter, el) == FilterResult.STOP)\n                break;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/NodeVisitor.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\n\n/**\n Node visitor interface, used to walk the DOM and visit each node. Execute via {@link #traverse(Node)} or\n {@link Node#traverse(NodeVisitor)}. The traversal is depth-first.\n <p>\n This interface provides two methods, {@link #head} and {@link #tail}. The head method is called when a node is first\n seen, and the tail method when all that node's children have been visited. As an example, {@code head} can be used to\n emit a start tag for a node, and {@code tail} to emit the end tag. The {@code tail} method defaults to a no-op, so\n this interface can be used as a {@link FunctionalInterface}, with {@code head} as its single abstract method.\n </p>\n <p><b>Example:</b></p>\n <pre><code>\n doc.body().traverse((node, depth) -&gt; {\n     switch (node) {\n         case Element el     -&gt; print(el.tag() + \": \" + el.ownText());\n         case DataNode data  -&gt; print(\"Data: \" + data.getWholeData());\n         default             -&gt; print(node.nodeName() + \" at depth \" + depth);\n     }\n });\n </code></pre>\n */\n@FunctionalInterface\npublic interface NodeVisitor {\n    /**\n     Callback for when a node is first visited.\n     <p>The node may be modified (for example via {@link Node#attr(String)}), removed with\n     {@link Node#remove()}, or replaced with {@link Node#replaceWith(Node)}. If the node is an\n     {@link Element}, you may cast it and access those methods.</p>\n     <p>Traversal uses a forward cursor. After {@code head()} completes:</p>\n     <ul>\n     <li>If the current node is still attached, traversal continues into its current children and then its following\n     siblings. Nodes inserted before the current node are not visited.</li>\n     <li>If the current node was detached and another node now occupies its former sibling position, the node now at\n     that position is not passed to {@code head()} again. Traversal continues from there: its children are visited,\n     then the node is passed to {@link #tail(Node, int)}, then later siblings are visited.</li>\n     <li>If the current node was detached and no node occupies its former sibling position, the current node is not\n     passed to {@code tail()}, and traversal resumes at the node that originally followed it.</li>\n     </ul>\n     <p>Traversal never advances outside the original root subtree. If the traversal root is detached during\n     {@code head()}, traversal stops at the original root boundary.</p>\n\n     @param node the node being visited.\n     @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node\n     of that will have depth 1.\n     */\n    void head(Node node, int depth);\n\n    /**\n     Callback for when a node is last visited, after all of its descendants have been visited.\n     <p>This method defaults to a no-op.</p>\n     <p>The node passed to {@code tail()} is the node at the current traversal position when the subtree completes.\n     If {@code head()} replaced the original node, this may be the replacement node instead.</p>\n     <p>Structural changes to the current node are not supported during {@code tail()}.</p>\n\n     @param node the node being visited.\n     @param depth the depth of the node, relative to the root node. E.g., the root node has depth 0, and a child node\n     of that will have depth 1.\n     */\n    default void tail(Node node, int depth) {\n        // no-op by default, to allow just specifying the head() method\n    }\n\n    /**\n     Run a depth-first traverse of the root and all of its descendants.\n     @param root the initial node point to traverse.\n     @since 1.21.1\n     */\n    default void traverse(Node root) {\n        NodeTraversor.traverse(this, root);\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/Nodes.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.function.UnaryOperator;\n\n/**\n A list of {@link Node} objects, with methods that act on every node in the list.\n <p>Methods that {@link #set(int, T) set}, {@link #remove(int) remove}, or\n {@link #replaceAll(UnaryOperator)  replace} nodes in the list will also act on the underlying\n {@link org.jsoup.nodes.Document DOM}.</p>\n\n <p>If there are other bulk methods (perhaps from Elements) that would be useful here, please <a\n href=\"https://jsoup.org/discussion\">provide feedback</a>.</p>\n\n @see Element#selectNodes(String)\n @see Element#selectNodes(String, Class)\n @since 1.21.1 */\npublic class Nodes<T extends Node> extends ArrayList<T> {\n    public Nodes() {\n    }\n\n    public Nodes(int initialCapacity) {\n        super(initialCapacity);\n    }\n\n    public Nodes(Collection<T> nodes) {\n        super(nodes);\n    }\n\n    public Nodes(List<T> nodes) {\n        super(nodes);\n    }\n\n    @SafeVarargs\n    public Nodes(T... nodes) {\n        super(Arrays.asList(nodes));\n    }\n\n    /**\n     * Creates a deep copy of these nodes.\n     * @return a deep copy\n     */\n    @Override\n    public Nodes<T> clone() {\n        Nodes<T> clone = new Nodes<>(size());\n        for (T node : this)\n            clone.add((T) node.clone());\n        return clone;\n    }\n\n    /**\n     Convenience method to get the Nodes as a plain ArrayList. This allows modification to the list of nodes\n     without modifying the source Document. I.e. whereas calling {@code nodes.remove(0)} will remove the nodes from\n     both the Nodes and the DOM, {@code nodes.asList().remove(0)} will remove the node from the list only.\n     <p>Each Node is still the same DOM connected Node.</p>\n\n     @return a new ArrayList containing the nodes in this list\n     @see #Nodes(List)\n     */\n    public ArrayList<T> asList() {\n        return new ArrayList<>(this);\n    }\n\n    /**\n     Remove each matched node from the DOM.\n     <p>The nodes will still be retained in this list, in case further processing of them is desired.</p>\n     <p>\n     E.g. HTML: {@code <div><p>Hello</p> <p>there</p> <img></div>}<br>\n     <code>doc.select(\"p\").remove();</code><br>\n     HTML = {@code <div> <img></div>}\n     <p>\n     Note that this method should not be used to clean user-submitted HTML; rather, use {@link org.jsoup.safety.Cleaner}\n     to clean HTML.\n\n     @return this, for chaining\n     @see Element#empty()\n     @see Elements#empty()\n     @see #clear()\n     */\n    public Nodes<T> remove() {\n        for (T node : this) {\n            node.remove();\n        }\n        return this;\n    }\n\n    /**\n     Get the combined outer HTML of all matched nodes.\n\n     @return string of all node's outer HTML.\n     @see Elements#text()\n     @see Elements#html()\n     */\n    public String outerHtml() {\n        return stream()\n            .map(Node::outerHtml)\n            .collect(StringUtil.joining(\"\\n\"));\n    }\n\n    /**\n     Get the combined outer HTML of all matched nodes. Alias of {@link #outerHtml()}.\n\n     @return string of all the node's outer HTML.\n     @see Elements#text()\n     @see #outerHtml()\n     */\n    @Override\n    public String toString() {\n        return outerHtml();\n    }\n\n    /**\n     Insert the supplied HTML before each matched node's outer HTML.\n\n     @param html HTML to insert before each node\n     @return this, for chaining\n     @see Element#before(String)\n     */\n    public Nodes<T> before(String html) {\n        for (T node : this) {\n            node.before(html);\n        }\n        return this;\n    }\n\n    /**\n     Insert the supplied HTML after each matched nodes's outer HTML.\n\n     @param html HTML to insert after each node\n     @return this, for chaining\n     @see Element#after(String)\n     */\n    public Nodes<T> after(String html) {\n        for (T node : this) {\n            node.after(html);\n        }\n        return this;\n    }\n\n    /**\n     Wrap the supplied HTML around each matched node. For example, with HTML\n     {@code <p><b>This</b> is <b>Jsoup</b></p>},\n     <code>doc.select(\"b\").wrap(\"&lt;i&gt;&lt;/i&gt;\");</code>\n     becomes {@code <p><i><b>This</b></i> is <i><b>jsoup</b></i></p>}\n     @param html HTML to wrap around each node, e.g. {@code <div class=\"head\"></div>}. Can be arbitrarily deep.\n     @return this (for chaining)\n     @see Element#wrap\n     */\n    public Nodes<T> wrap(String html) {\n        Validate.notEmpty(html);\n        for (T node : this) {\n            node.wrap(html);\n        }\n        return this;\n    }\n\n    // list-like methods\n    /**\n     Get the first matched element.\n     @return The first matched element, or <code>null</code> if contents is empty.\n     */\n    public @Nullable T first() {\n        return isEmpty() ? null : get(0);\n    }\n\n    /**\n     Get the last matched element.\n     @return The last matched element, or <code>null</code> if contents is empty.\n     */\n    public @Nullable T last() {\n        return isEmpty() ? null : get(size() - 1);\n    }\n\n    // ArrayList<T> methods that update the DOM:\n\n    /**\n     Replace the node at the specified index in this list, and in the DOM.\n\n     @param index index of the node to replace\n     @param node node to be stored at the specified position\n     @return the old Node at this index\n     */\n    @Override\n    public T set(int index, T node) {\n        Validate.notNull(node);\n        T old = super.set(index, node);\n        old.replaceWith(node);\n        return old;\n    }\n\n    /**\n     Remove the node at the specified index in this list, and from the DOM.\n\n     @param index the index of the node to be removed\n     @return the old node at this index\n     @see #deselect(int)\n     */\n    @Override\n    public T remove(int index) {\n        T old = super.remove(index);\n        old.remove();\n        return old;\n    }\n\n    /**\n     Remove the specified node from this list, and from the DOM.\n\n     @param o node to be removed from this list, if present\n     @return if this list contained the Node\n     @see #deselect(Object)\n     */\n    @Override\n    public boolean remove(Object o) {\n        int index = super.indexOf(o);\n        if (index == -1) {\n            return false;\n        } else {\n            remove(index);\n            return true;\n        }\n    }\n\n    /**\n     Remove the node at the specified index in this list, but not from the DOM.\n\n     @param index the index of the node to be removed\n     @return the old node at this index\n     @see #remove(int)\n     */\n    public T deselect(int index) {\n        return super.remove(index);\n    }\n\n    /**\n     Remove the specified node from this list, but not from the DOM.\n\n     @param o node to be removed from this list, if present\n     @return if this list contained the Node\n     @see #remove(Object)\n     */\n    public boolean deselect(Object o) {\n        return super.remove(o);\n    }\n\n    /**\n     Removes all the nodes from this list, and each of them from the DOM.\n\n     @see #deselectAll()\n     */\n    @Override\n    public void clear() {\n        remove();\n        super.clear();\n    }\n\n    /**\n     Like {@link #clear()}, removes all the nodes from this list, but not from the DOM.\n\n     @see #clear()\n     */\n    public void deselectAll() {\n        super.clear();\n    }\n\n    /**\n     Removes from this list, and from the DOM, each of the nodes that are contained in the specified collection and are\n     in this list.\n\n     @param c collection containing nodes to be removed from this list\n     @return {@code true} if nodes were removed from this list\n     */\n    @Override\n    public boolean removeAll(Collection<?> c) {\n        boolean anyRemoved = false;\n        for (Object o : c) {\n            anyRemoved |= this.remove(o);\n        }\n        return anyRemoved;\n    }\n\n    /**\n     Retain in this list, and in the DOM, only the nodes that are in the specified collection and are in this list. In\n     other words, remove nodes from this list and the DOM any item that is in this list but not in the specified\n     collection.\n\n     @param toRemove collection containing nodes to be retained in this list\n     @return {@code true} if nodes were removed from this list\n     @since 1.17.1\n     */\n    @Override\n    public boolean retainAll(Collection<?> toRemove) {\n        boolean anyRemoved = false;\n        for (Iterator<T> it = this.iterator(); it.hasNext(); ) {\n            T el = it.next();\n            if (!toRemove.contains(el)) {\n                it.remove();\n                anyRemoved = true;\n            }\n        }\n        return anyRemoved;\n    }\n\n    /**\n     Remove from the list, and from the DOM, all nodes in this list that mach the given predicate.\n\n     @param filter a predicate which returns {@code true} for nodes to be removed\n     @return {@code true} if nodes were removed from this list\n     */\n    @Override\n    public boolean removeIf(Predicate<? super T> filter) {\n        boolean anyRemoved = false;\n        for (Iterator<T> it = this.iterator(); it.hasNext(); ) {\n            T node = it.next();\n            if (filter.test(node)) {\n                it.remove();\n                anyRemoved = true;\n            }\n        }\n        return anyRemoved;\n    }\n\n    /**\n     Replace each node in this list with the result of the operator, and update the DOM.\n\n     @param operator the operator to apply to each node\n     */\n    @Override\n    public void replaceAll(UnaryOperator<T> operator) {\n        for (int i = 0; i < this.size(); i++) {\n            this.set(i, operator.apply(this.get(i)));\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/QueryParser.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Regex;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.CDataNode;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.parser.TokenQueue;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.function.Function;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.jsoup.select.StructuralEvaluator.ImmediateParentRun;\nimport static org.jsoup.internal.Normalizer.normalize;\n\n/**\n * Parses a CSS selector into an Evaluator tree.\n */\npublic class QueryParser implements AutoCloseable {\n    private final static char[] Combinators = {'>', '+', '~'}; // ' ' is also a combinator, but found implicitly\n    private final static String[] AttributeEvals = new String[]{\"=\", \"!=\", \"^=\", \"$=\", \"*=\", \"~=\"};\n    private final static char[] SequenceEnders = {',', ')'};\n\n    private final TokenQueue tq;\n    private final String query;\n    private boolean inNodeContext; // ::comment:contains should act on node value, vs element text\n\n    /**\n     * Create a new QueryParser.\n     * @param query CSS query\n     */\n    private QueryParser(String query) {\n        Validate.notEmpty(query);\n        query = query.trim();\n        this.query = query;\n        this.tq = new TokenQueue(query);\n    }\n\n    /**\n     Parse a CSS query into an Evaluator. If you are evaluating the same query repeatedly, it may be more efficient to\n     parse it once and reuse the Evaluator.\n\n     @param query CSS query\n     @return Evaluator\n     @see Selector selector query syntax\n     @throws Selector.SelectorParseException if the CSS query is invalid\n     */\n    public static Evaluator parse(String query) {\n        try (QueryParser p = new QueryParser(query)) {\n            return p.parse();\n        } catch (IllegalArgumentException e) {\n            throw new Selector.SelectorParseException(e.getMessage());\n        }\n    }\n\n    /**\n     Parse the query. We use this simplified expression of the grammar:\n     <pre>\n     SelectorGroup   ::= Selector (',' Selector)*\n     Selector        ::= [ Combinator ] SimpleSequence ( Combinator SimpleSequence )*\n     SimpleSequence  ::= [ TypeSelector ] ( ID | Class | Attribute | Pseudo )*\n     Pseudo           ::= ':' Name [ '(' SelectorGroup ')' ]\n     Combinator      ::= S+         // descendant (whitespace)\n     | '>'       // child\n     | '+'       // adjacent sibling\n     | '~'       // general sibling\n     </pre>\n\n     See <a href=\"https://www.w3.org/TR/selectors-4/#grammar\">selectors-4</a> for the real thing\n     */\n    Evaluator parse() {\n        Evaluator eval = parseSelectorGroup();\n        tq.consumeWhitespace();\n        if (!tq.isEmpty())\n            throw new Selector.SelectorParseException(\"Could not parse query '%s': unexpected token at '%s'\", query, tq.remainder());\n        return eval;\n    }\n\n    Evaluator parseSelectorGroup() {\n        // SelectorGroup. Into an Or if > 1 Selector\n        Evaluator left = parseSelector();\n        while (tq.matchChomp(',')) {\n            Evaluator right = parseSelector();\n            left = or(left, right);\n        }\n        return left;\n    }\n\n    Evaluator parseSelector() {\n        // Selector ::= [ Combinator ] SimpleSequence ( Combinator SimpleSequence )*\n        tq.consumeWhitespace();\n\n        Evaluator left;\n        if (tq.matchesAny(Combinators)) {\n            // e.g. query is \"> div\"; left side is root element\n            left = new StructuralEvaluator.Root();\n        } else {\n            left = parseSimpleSequence();\n        }\n\n        while (true) {\n            char combinator = 0;\n            if (tq.consumeWhitespace())\n                combinator = ' ';            // maybe descendant?\n            if (tq.matchesAny(Combinators)) // no, explicit\n                combinator = tq.consume();\n            else if (tq.matchesAny(SequenceEnders)) // , - space after simple like \"foo , bar\"; ) - close of :has()\n                break;\n\n            if (combinator != 0) {\n                Evaluator right = parseSimpleSequence();\n                left = combinator(left, combinator, right);\n            } else {\n                break;\n            }\n        }\n        return left;\n    }\n\n    Evaluator parseSimpleSequence() {\n        // SimpleSequence ::= TypeSelector? ( Hash | Class | Pseudo )*\n        Evaluator left = null;\n        tq.consumeWhitespace();\n\n        // one optional type selector\n        if (tq.matchesWord() || tq.matches(\"*|\"))\n            left = byTag();\n        else if (tq.matchChomp('*'))\n            left = new Evaluator.AllElements();\n\n        // zero or more subclasses (#, ., [)\n        while(true) {\n            Evaluator right = parseSubclass();\n            if (right != null) {\n                left = and(left, right);\n            }\n            else break; // no more simple tokens\n        }\n\n        if (left == null)\n            throw new Selector.SelectorParseException(\"Could not parse query '%s': unexpected token at '%s'\", query, tq.remainder());\n        return left;\n    }\n\n    static Evaluator combinator(Evaluator left, char combinator, Evaluator right) {\n        switch (combinator) {\n            case '>':\n                ImmediateParentRun run = left instanceof ImmediateParentRun ?\n                    (ImmediateParentRun) left : new ImmediateParentRun(left);\n                run.add(right);\n                return run;\n            case ' ':\n                return and(new StructuralEvaluator.Ancestor(left), right);\n            case '+':\n                return and(new StructuralEvaluator.ImmediatePreviousSibling(left), right);\n            case '~':\n                return and(new StructuralEvaluator.PreviousSibling(left), right);\n            default:\n                throw new Selector.SelectorParseException(\"Unknown combinator '%s'\", combinator);\n        }\n    }\n\n    @Nullable Evaluator parseSubclass() {\n        //  Subclass: ID | Class | Attribute | Pseudo\n        if      (tq.matchChomp('#'))    return byId();\n        else if (tq.matchChomp('.'))    return byClass();\n        else if (tq.matches('['))       return byAttribute();\n        else if (tq.matchChomp(\"::\"))   return parseNodeSelector(); // ::comment etc\n        else if (tq.matchChomp(':'))    return parsePseudoSelector();\n        else                            return null;\n    }\n\n    /** Merge two evals into an Or. */\n    static Evaluator or(Evaluator left, Evaluator right) {\n        if (left instanceof CombiningEvaluator.Or) {\n            ((CombiningEvaluator.Or) left).add(right);\n            return left;\n        }\n        return new CombiningEvaluator.Or(left, right);\n    }\n\n    /** Merge two evals into an And. */\n    static Evaluator and(@Nullable Evaluator left, Evaluator right) {\n        if (left == null) return right;\n        if (left instanceof CombiningEvaluator.And) {\n            ((CombiningEvaluator.And) left).add(right);\n            return left;\n        }\n        return new CombiningEvaluator.And(left, right);\n    }\n\n    private Evaluator parsePseudoSelector() {\n        final String pseudo = tq.consumeCssIdentifier();\n        switch (pseudo) {\n            case \"lt\":\n                return new Evaluator.IndexLessThan(consumeIndex());\n            case \"gt\":\n                return new Evaluator.IndexGreaterThan(consumeIndex());\n            case \"eq\":\n                return new Evaluator.IndexEquals(consumeIndex());\n            case \"has\":\n                return has();\n            case \"is\":\n                return is();\n            case \"contains\":\n                return contains(false);\n            case \"containsOwn\":\n                return contains(true);\n            case \"containsWholeText\":\n                return containsWholeText(false);\n            case \"containsWholeOwnText\":\n                return containsWholeText(true);\n            case \"containsData\":\n                return containsData();\n            case \"matches\":\n                return matches(false);\n            case \"matchesOwn\":\n                return matches(true);\n            case \"matchesWholeText\":\n                return matchesWholeText(false);\n            case \"matchesWholeOwnText\":\n                return matchesWholeText(true);\n            case \"not\":\n                return not();\n            case \"nth-child\":\n                return cssNthChild(false, false);\n            case \"nth-last-child\":\n                return cssNthChild(true, false);\n            case \"nth-of-type\":\n                return cssNthChild(false, true);\n            case \"nth-last-of-type\":\n                return cssNthChild(true, true);\n            case \"first-child\":\n                return new Evaluator.IsFirstChild();\n            case \"last-child\":\n                return new Evaluator.IsLastChild();\n            case \"first-of-type\":\n                return new Evaluator.IsFirstOfType();\n            case \"last-of-type\":\n                return new Evaluator.IsLastOfType();\n            case \"only-child\":\n                return new Evaluator.IsOnlyChild();\n            case \"only-of-type\":\n                return new Evaluator.IsOnlyOfType();\n            case \"empty\":\n                return new Evaluator.IsEmpty();\n            case \"blank\":\n                return new NodeEvaluator.BlankValue();\n            case \"root\":\n                return new Evaluator.IsRoot();\n            case \"matchText\":\n                return new Evaluator.MatchText();\n            default:\n                throw new Selector.SelectorParseException(\"Could not parse query '%s': unexpected token at '%s'\", query, tq.remainder());\n        }\n    }\n\n    // ::comment etc\n    private Evaluator parseNodeSelector() {\n        final String pseudo = tq.consumeCssIdentifier();\n        inNodeContext = true;  // Enter node context\n\n        Evaluator left;\n        switch (pseudo) {\n            case \"node\":\n                left = new NodeEvaluator.InstanceType(Node.class, pseudo);\n                break;\n            case \"leafnode\":\n                left = new NodeEvaluator.InstanceType(LeafNode.class, pseudo);\n                break;\n            case \"text\":\n                left = new NodeEvaluator.InstanceType(TextNode.class, pseudo);\n                break;\n            case \"comment\":\n                left = new NodeEvaluator.InstanceType(Comment.class, pseudo);\n                break;\n            case \"data\":\n                left = new NodeEvaluator.InstanceType(DataNode.class, pseudo);\n                break;\n            case \"cdata\":\n                left = new NodeEvaluator.InstanceType(CDataNode.class, pseudo);\n                break;\n            default:\n                throw new Selector.SelectorParseException(\n                    \"Could not parse query '%s': unknown node type '::%s'\", query, pseudo);\n        }\n\n        // Handle following subclasses in node context (like ::comment:contains())\n        Evaluator right;\n        while ((right = parseSubclass()) != null) {\n            left = and(left, right);\n        }\n\n        inNodeContext = false;\n        return left;\n    }\n\n    private Evaluator byId() {\n        String id = tq.consumeCssIdentifier();\n        Validate.notEmpty(id);\n        return new Evaluator.Id(id);\n    }\n\n    private Evaluator byClass() {\n        String className = tq.consumeCssIdentifier();\n        Validate.notEmpty(className);\n        return new Evaluator.Class(className.trim());\n    }\n\n    private Evaluator byTag() {\n        // todo - these aren't dealing perfectly with case sensitivity. For case sensitive parsers, we should also make\n        // the tag in the selector case-sensitive (and also attribute names). But for now, normalize (lower-case) for\n        // consistency - both the selector and the element tag\n        String tagName = normalize(tq.consumeElementSelector());\n        Validate.notEmpty(tagName);\n\n        // namespaces:\n        if (tagName.startsWith(\"*|\")) { // namespaces: wildcard match equals(tagName) or ending in \":\"+tagName\n            String plainTag = tagName.substring(2); // strip *|\n            return new CombiningEvaluator.Or(\n                new Evaluator.Tag(plainTag),\n                new Evaluator.TagEndsWith(\":\" + plainTag)\n            );\n        } else if (tagName.endsWith(\"|*\")) { // ns|*\n            String ns = tagName.substring(0, tagName.length() - 2) + \":\"; // strip |*, to ns:\n            return new Evaluator.TagStartsWith(ns);\n        } else if (tagName.contains(\"|\")) { // flip \"abc|def\" to \"abc:def\"\n            tagName = tagName.replace(\"|\", \":\");\n        }\n\n        return new Evaluator.Tag(tagName);\n    }\n\n    private Evaluator byAttribute() {\n        try (TokenQueue cq = new TokenQueue(tq.chompBalanced('[', ']'))) {\n            return evaluatorForAttribute(cq);\n        }\n    }\n\n    private Evaluator evaluatorForAttribute(TokenQueue cq) {\n        String key = cq.consumeToAny(AttributeEvals); // eq, not, start, end, contain, match, (no val)\n        key = normalize(key);\n        Validate.notEmpty(key);\n        Validate.isFalse(key.equals(\"abs:\"), \"Absolute attribute key must have a name\");\n        cq.consumeWhitespace();\n        final Evaluator eval;\n\n        if (cq.isEmpty()) {\n            if (key.startsWith(\"^\"))\n                eval = new Evaluator.AttributeStarting(key.substring(1));\n            else if (key.equals(\"*\")) // any attribute\n                eval = new Evaluator.AttributeStarting(\"\");\n            else\n                eval = new Evaluator.Attribute(key);\n        } else {\n            if (cq.matchChomp('='))\n                eval = new Evaluator.AttributeWithValue(key, cq.remainder());\n            else if (cq.matchChomp(\"!=\"))\n                eval = new Evaluator.AttributeWithValueNot(key, cq.remainder());\n            else if (cq.matchChomp(\"^=\"))\n                eval = new Evaluator.AttributeWithValueStarting(key, cq.remainder());\n            else if (cq.matchChomp(\"$=\"))\n                eval = new Evaluator.AttributeWithValueEnding(key, cq.remainder());\n            else if (cq.matchChomp(\"*=\"))\n                eval = new Evaluator.AttributeWithValueContaining(key, cq.remainder());\n            else if (cq.matchChomp(\"~=\"))\n                eval = new Evaluator.AttributeWithValueMatching(key, Regex.compile(cq.remainder()));\n            else\n                throw new Selector.SelectorParseException(\n                    \"Could not parse attribute query '%s': unexpected token at '%s'\", query, cq.remainder());\n        }\n        return eval;\n    }\n\n    //pseudo selectors :first-child, :last-child, :nth-child, ...\n    private static final Pattern NthStepOffset = Pattern.compile(\"(([+-])?(\\\\d+)?)n(\\\\s*([+-])?\\\\s*\\\\d+)?\", Pattern.CASE_INSENSITIVE);\n    private static final Pattern NthOffset = Pattern.compile(\"([+-])?(\\\\d+)\");\n\n    private Evaluator cssNthChild(boolean last, boolean ofType) {\n        String arg = normalize(consumeParens()); // arg is like \"odd\", or \"-n+2\", within nth-child(odd)\n        final int step, offset;\n        if (\"odd\".equals(arg)) {\n            step = 2;\n            offset = 1;\n        } else if (\"even\".equals(arg)) {\n            step = 2;\n            offset = 0;\n        } else {\n            Matcher stepOffsetM, stepM;\n            if ((stepOffsetM = NthStepOffset.matcher(arg)).matches()) {\n                if (stepOffsetM.group(3) != null) // has digits, like 3n+2 or -3n+2\n                    step = Integer.parseInt(stepOffsetM.group(1).replaceFirst(\"^\\\\+\", \"\"));\n                else // no digits, might be like n+2, or -n+2. if group(2) == \"-\", it’s -1;\n                    step = \"-\".equals(stepOffsetM.group(2)) ? -1 : 1;\n                offset =\n                    stepOffsetM.group(4) != null ? Integer.parseInt(stepOffsetM.group(4).replaceFirst(\"^\\\\+\", \"\")) : 0;\n            } else if ((stepM = NthOffset.matcher(arg)).matches()) {\n                step = 0;\n                offset = Integer.parseInt(stepM.group().replaceFirst(\"^\\\\+\", \"\"));\n            } else {\n                throw new Selector.SelectorParseException(\"Could not parse nth-index '%s': unexpected format\", arg);\n            }\n        }\n\n        return ofType\n            ? (last ? new Evaluator.IsNthLastOfType(step, offset) : new Evaluator.IsNthOfType(step, offset))\n            : (last ? new Evaluator.IsNthLastChild(step, offset) : new Evaluator.IsNthChild(step, offset));\n    }\n\n    private String consumeParens() {\n        return tq.chompBalanced('(', ')');\n    }\n\n    private int consumeIndex() {\n        String index = consumeParens().trim();\n        Validate.isTrue(StringUtil.isNumeric(index), \"Index must be numeric\");\n        return Integer.parseInt(index);\n    }\n\n    // pseudo selector :has(el)\n    private Evaluator has() {\n        return parseNested(StructuralEvaluator.Has::new, \":has() must have a selector\");\n    }\n\n    // pseudo selector :is()\n    private Evaluator is() {\n        return parseNested(StructuralEvaluator.Is::new, \":is() must have a selector\");\n    }\n\n    private Evaluator parseNested(Function<Evaluator, Evaluator> func, String err) {\n        Validate.isTrue(tq.matchChomp('('), err);\n        Evaluator eval = parseSelectorGroup();\n        Validate.isTrue(tq.matchChomp(')'), err);\n        return func.apply(eval);\n    }\n\n    // pseudo selector :contains(text), containsOwn(text)\n    private Evaluator contains(boolean own) {\n        String query = own ? \":containsOwn\" : \":contains\";\n        String searchText = TokenQueue.unescape(consumeParens());\n        Validate.notEmpty(searchText, query + \"(text) query must not be empty\");\n\n        if (inNodeContext)\n            return new NodeEvaluator.ContainsValue(searchText);\n\n        return own\n            ? new Evaluator.ContainsOwnText(searchText)\n            : new Evaluator.ContainsText(searchText);\n    }\n\n    private Evaluator containsWholeText(boolean own) {\n        String query = own ? \":containsWholeOwnText\" : \":containsWholeText\";\n        String searchText = TokenQueue.unescape(consumeParens());\n        Validate.notEmpty(searchText, query + \"(text) query must not be empty\");\n        return own\n            ? new Evaluator.ContainsWholeOwnText(searchText)\n            : new Evaluator.ContainsWholeText(searchText);\n    }\n\n    // pseudo selector :containsData(data)\n    private Evaluator containsData() {\n        String searchText = TokenQueue.unescape(consumeParens());\n        Validate.notEmpty(searchText, \":containsData(text) query must not be empty\");\n        return new Evaluator.ContainsData(searchText);\n    }\n\n    // :matches(regex), matchesOwn(regex)\n    private Evaluator matches(boolean own) {\n        String query = own ? \":matchesOwn\" : \":matches\";\n        String regex = consumeParens(); // don't unescape, as regex bits will be escaped\n        Validate.notEmpty(regex, query + \"(regex) query must not be empty\");\n        Regex pattern = Regex.compile(regex);\n\n        if (inNodeContext)\n            return new NodeEvaluator.MatchesValue(pattern);\n\n        return own\n            ? new Evaluator.MatchesOwn(pattern)\n            : new Evaluator.Matches(pattern);\n    }\n\n    // :matches(regex), matchesOwn(regex)\n    private Evaluator matchesWholeText(boolean own) {\n        String query = own ? \":matchesWholeOwnText\" : \":matchesWholeText\";\n        String regex = consumeParens(); // don't unescape, as regex bits will be escaped\n        Validate.notEmpty(regex, query + \"(regex) query must not be empty\");\n\n        Regex pattern = Regex.compile(regex);\n        return own\n            ? new Evaluator.MatchesWholeOwnText(pattern)\n            : new Evaluator.MatchesWholeText(pattern);\n    }\n\n    // :not(selector)\n    private Evaluator not() {\n        String subQuery = consumeParens();\n        Validate.notEmpty(subQuery, \":not(selector) subselect must not be empty\");\n\n        return new StructuralEvaluator.Not(parse(subQuery));\n    }\n\n    @Override\n    public String toString() {\n        return query;\n    }\n\n    @Override\n    public void close() {\n        tq.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/Selector.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.helper.Validate;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.parser.TokenQueue;\nimport org.jspecify.annotations.Nullable;\n\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.stream.Stream;\n\n/**\n CSS element selector, that finds elements matching a query.\n\n <h2>Selector syntax</h2>\n <p>\n A selector is a chain of simple selectors, separated by combinators. Selectors are <b>case-insensitive</b> (including\n against elements, attributes, and attribute values).\n </p>\n <p>\n The universal selector {@code *} is implicit when no element selector is supplied (i.e. {@code .header} and\n {@code *.header} are equivalent).\n </p>\n\n <p>You can easily test different selectors using the <a href=\"https://try.jsoup.org/?utm_source=jsoup&amp;utm_medium=javadoc\">Try jsoup online playground</a>.\n\n <style>table.syntax tr td {vertical-align: top; padding-right: 2em; padding-top:0.5em; padding-bottom:0.5em; }\n table.syntax tr:hover{background-color: #eee;} table.syntax {border-spacing: 0px 0px;}</style>\n\n <table summary=\"\" class=\"syntax\"><colgroup><col span=\"1\" style=\"width: 20%;\"><col span=\"1\" style=\"width: 40%;\"><col span=\"1\" style=\"width: 40%;\"></colgroup>\n <tr><th align=\"left\">Pattern</th><th align=\"left\">Matches</th><th align=\"left\">Example</th></tr>\n <tr><td><code>*</code></td><td>any element</td><td><code>*</code></td></tr>\n <tr><td><code>tag</code></td><td>elements with the given tag name</td><td><code>div</code></td></tr>\n <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>\n <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>\n <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>\n <tr><td><code>#id</code></td><td>elements with attribute ID of \"id\"</td><td><code>div#wrap</code>, <code>#logo</code></td></tr>\n <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>\n <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>\n <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>\n <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>\n <tr><td><code>[attr=&quot;val&quot;]</code></td><td>elements with an attribute named \"attr\", and value equal to \"val\"</td><td><code>span[hello=\"Cleveland\"][goodbye=\"Columbus\"]</code>, <code>a[rel=&quot;nofollow&quot;]</code></td></tr>\n <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>\n <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>\n <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>\n <tr><td><code>[attr~=<em>regex</em>]</code></td><td>elements with an attribute named \"attr\", and value matching the regular expression</td><td><code>img[src~=(?i)\\\\.(png|jpe?g)]</code></td></tr>\n <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>\n <tr><td></td><td>The above may be combined in any order</td><td><code>div.header[title]</code></td></tr>\n\n <tr><td colspan=\"3\"><h3>Combinators</h3></td></tr>\n <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>\n <tr><td><code>E {@literal >} F</code></td><td>an F direct child of E</td><td><code>ol {@literal >} li</code></td></tr>\n <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>\n <tr><td><code>E ~ F</code></td><td>an F element preceded by sibling E</td><td><code>h1 ~ p</code></td></tr>\n <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>\n\n <tr><td colspan=\"3\"><h3>Pseudo selectors</h3></td></tr>\n <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>\n <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>\n <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>\n <tr><td><code>:has(<em>selector</em>)</code></td><td>elements that contains 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>\n <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>\n <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>\n <tr><td><code>:contains(<em>text</em>)</code></td><td>elements that contains 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>\n <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>\n <tr><td><code>:containsData(<em>data</em>)</code></td><td>elements that contains 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:contains(jsoup)</code> finds script elements containing the data \"jsoup\".</td></tr>\n <tr><td><code>:containsWholeText(<em>text</em>)</code></td><td>elements that contains the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, or any of its descendants. <p>To find content that includes parentheses, escape those with a {@code \\}.</p></td><td><code>p:containsWholeText(jsoup\\nThe Java HTML Parser)</code> finds p elements containing the text <code>\"jsoup\\nThe Java HTML Parser\"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr>\n <tr><td><code>:containsWholeOwnText(<em>text</em>)</code></td><td>elements that <b>directly</b> contain the specified <b>non-normalized</b> text. The search is case sensitive, and will match exactly against spaces and newlines found in the original input. The text may appear in the found element, but not in its descendants. <p>To find content that includes parentheses, escape those with a {@code \\}.</p></td><td><code>p:containsWholeOwnText(jsoup\\nThe Java HTML Parser)</code> finds p elements directly containing the text <code>\"jsoup\\nThe Java HTML Parser\"</code> (and not other variations of whitespace or casing, as <code>:contains()</code> would. Note that {@code br} elements are presented as a newline.</p></td></tr>\n <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>\n <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 a run of at least two space characters.</td></tr>\n <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 neewline.</td></tr>\n <tr><td></td><td>The above may be combined in any order and with other selectors</td><td><code>.light:contains(name):eq(0)</code></td></tr>\n <tr><td><code>:matchText</code></td><td>treats text nodes as elements, and so allows you to match against and select text nodes.<p><b>Note</b> that using this selector will modify the DOM, so you may want to {@code clone} your document before using.<p><b>Deprecated</b>. This selector is deprecated and will be removed in a future version. Migrate to <code>::textnode</code> using the <code>Element#selectNodes()</code> method instead.</p></td><td>{@code p:matchText:firstChild} with input {@code <p>One<br />Two</p>} will return one {@link org.jsoup.nodes.PseudoTextElement} with text \"{@code One}\".</td></tr>\n\n <tr><td colspan=\"3\"><h3>Structural pseudo selectors</h3></td></tr>\n <tr><td><code>:root</code></td><td>The element that is the root of the document. In HTML, this is the <code>html</code> element</td><td><code>:root</code></td></tr>\n <tr><td><code>:nth-child(<em>a</em>n+<em>b</em>)</code></td><td><p>elements that have <code><em>a</em>n+<em>b</em>-1</code> siblings <b>before</b> it in the document tree, for any positive integer or zero value of <code>n</code>, and has a parent element. For values of <code>a</code> and <code>b</code> greater than zero, this effectively divides the element's children into groups of a elements (the last group taking the remainder), and selecting the <em>b</em>th element of each group. For example, this allows the selectors to address every other row in a table, and could be used to alternate the color of paragraph text in a cycle of four. The <code>a</code> and <code>b</code> values must be integers (positive, negative, or zero). The index of the first child of an element is 1.</p>\n Additionally, <code>:nth-child()</code> supports <code>odd</code> and <code>even</code> as arguments. <code>odd</code> is the same as <code>2n+1</code>, and <code>even</code> is the same as <code>2n</code>.</td><td><code>tr:nth-child(2n+1)</code> finds every odd row of a table. <code>:nth-child(10n-1)</code> the 9th, 19th, 29th, etc, element. <code>li:nth-child(5)</code> the 5h li</td></tr>\n <tr><td><code>:nth-last-child(<em>a</em>n+<em>b</em>)</code></td><td>elements that have <code><em>a</em>n+<em>b</em>-1</code> siblings <b>after</b> it in the document tree. Otherwise like <code>:nth-child()</code></td><td><code>tr:nth-last-child(-n+2)</code> the last two rows of a table</td></tr>\n <tr><td><code>:nth-of-type(<em>a</em>n+<em>b</em>)</code></td><td>pseudo-class notation represents an element that has <code><em>a</em>n+<em>b</em>-1</code> siblings with the same expanded element name <em>before</em> it in the document tree, for any zero or positive integer value of n, and has a parent element</td><td><code>img:nth-of-type(2n+1)</code></td></tr>\n <tr><td><code>:nth-last-of-type(<em>a</em>n+<em>b</em>)</code></td><td>pseudo-class notation represents an element that has <code><em>a</em>n+<em>b</em>-1</code> siblings with the same expanded element name <em>after</em> it in the document tree, for any zero or positive integer value of n, and has a parent element</td><td><code>img:nth-last-of-type(2n+1)</code></td></tr>\n <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>\n <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>\n <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>\n <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>\n <tr><td><code>:only-child</code></td><td>elements that have a parent element and whose parent element have no other element children</td><td></td></tr>\n <tr><td><code>:only-of-type</code></td><td> an element that has a parent element and whose parent element has no other element children with the same expanded element name</td><td></td></tr>\n <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>\n\n <tr><td colspan=\"3\"><h3>Node pseudo selectors</h3></td></tr>\n <tr><td colspan=\"3\">These selectors enable matching specific leaf nodes, including Comments, TextNodes. When used with {@link Element#select(String)}, these can be used with structural selectors such as <code>:has()</code> to refine which Elements are matched. To retrieve matching Nodes directly, use {@Element#selectNodes(String)}.</td></tr>\n <tr><td>::node</td><td>Matches any node</td><td></td></tr>\n <tr><td>::leafnode</td><td>Matches any leaf-node (this is, a Node which is not an Element)</td><td></td></tr>\n <tr><td>::comment</td><td>Matches a Comment node</td><td></td></tr>\n <tr><td>::text</td><td>Matches a TextNode</td><td></td></tr>\n <tr><td>::data</td><td>Matches a DataNode (e.g. the content of a <code>script</code> or a <code>style</code> element)</td><td></td></tr>\n <tr><td>::cdata</td><td>Matches a CDataNode (which are only present in XML)</td><td></td></tr>\n <tr><td>::node:contains(text)</td><td>Matches a node that has a (normalized, case-insensitive) value containing <i>text</i>.</td><td><code>::comment:contains(foo bar)</code></td></tr>\n <tr><td>::node:matches(regex)</td><td>Matches a node that has a value matching the regex.</td><td><code>::comment:matches(\\\\d+)</code></td></tr>\n <tr><td>::node:blank</td><td>Matches a node that has either no value, or a value of only whitespace.</td><td><code>::comment:not(:blank)</code></td></tr>\n </table>\n\n <p>A word on using regular expressions in these selectors: depending on the content of the regex, you will need to quote the pattern using <b><code>Pattern.quote(\"regex\")</code></b> for it to parse correctly through both the selector parser and the regex parser. E.g. <code>String query = \"div:matches(\" + Pattern.quote(regex) + \");\"</code>.</p>\n <p><b>Escaping special characters:</b> to match a tag, ID, or other selector that does not follow the regular CSS syntax, the query must be escaped with the <code>\\</code> character. For example, to match by ID {@code <p id=\"i.d\">}, use {@code document.select(\"#i\\\\.d\")}.</p>\n\n @see Element#select(String css)\n @see Element#selectFirst(String css)\n @see Element#select(Evaluator eval)\n @see Element#selectNodes(String css)\n @see Element#selectNodes(String css, Class nodeType)\n @see Elements#select(String css)\n @see Element#selectXpath(String xpath) */\npublic class Selector {\n    // not instantiable\n    private Selector() {}\n\n    /**\n     Find Elements matching the CSS query.\n\n     @param query CSS selector\n     @param root root element to descend into\n     @return matching elements, empty if none\n     @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.\n     */\n    public static Elements select(String query, Element root) {\n        Validate.notEmpty(query);\n        return select(evaluatorOf(query), root);\n    }\n\n    /**\n     Find Elements matching the Evaluator.\n\n     @param evaluator CSS Evaluator\n     @param root root (context) element to start from\n     @return matching elements, empty if none\n     */\n    public static Elements select(Evaluator evaluator, Element root) {\n        Validate.notNull(evaluator);\n        Validate.notNull(root);\n        return Collector.collect(evaluator, root);\n    }\n\n    /**\n     Finds a Stream of elements matching the CSS query.\n\n     @param query CSS selector\n     @param root root element to descend into\n     @return a Stream of matching elements, empty if none\n     @throws Selector.SelectorParseException (unchecked) on an invalid CSS query.\n     @since 1.19.1\n     */\n    public static Stream<Element> selectStream(String query, Element root) {\n        Validate.notEmpty(query);\n        return selectStream(evaluatorOf(query), root);\n    }\n\n    /**\n     Finds a Stream of elements matching the evaluator.\n\n     @param evaluator CSS selector\n     @param root root element to descend into\n     @return matching elements, empty if none\n     @since 1.19.1\n     */\n    public static Stream<Element> selectStream(Evaluator evaluator, Element root) {\n        Validate.notNull(evaluator);\n        Validate.notNull(root);\n        return Collector.stream(evaluator, root);\n    }\n\n    /**\n     Find elements matching the query, across multiple roots. Elements will be deduplicated (in the case of\n     overlapping hierarchies).\n\n     @param query CSS selector\n     @param roots root elements to descend into\n     @return matching elements, empty if none\n     */\n    public static Elements select(String query, Iterable<Element> roots) {\n        Validate.notEmpty(query);\n        Validate.notNull(roots);\n        Evaluator evaluator = evaluatorOf(query);\n        Elements elements = new Elements();\n        HashSet<Element> seenElements = new HashSet<>(); // dedupe elements by identity, as .equals is ==\n\n        for (Element root : roots) {\n            selectStream(evaluator, root)\n                .filter(seenElements::add)\n                .forEach(elements::add);\n        }\n\n        return elements;\n    }\n\n    // exclude set. package open so that Elements can implement .not() selector.\n    static Elements filterOut(Collection<Element> elements, Collection<Element> outs) {\n        Elements output = new Elements();\n        for (Element el : elements) {\n            boolean found = false;\n            for (Element out : outs) {\n                if (el.equals(out)) {\n                    found = true;\n                    break;\n                }\n            }\n            if (!found)\n                output.add(el);\n        }\n        return output;\n    }\n\n    /**\n     Find the first Element that matches the query.\n\n     @param cssQuery CSS selector\n     @param root root element to descend into\n     @return the matching element, or <b>null</b> if none.\n     */\n    public static @Nullable Element selectFirst(String cssQuery, Element root) {\n        Validate.notEmpty(cssQuery);\n        return Collector.findFirst(evaluatorOf(cssQuery), root);\n    }\n\n    /**\n     Find the first element matching the query, across multiple roots.\n\n     @param cssQuery CSS selector\n     @param roots root elements to descend into\n     @return the first matching element, or {@code null} if none\n     @since 1.19.1\n     */\n    public static @Nullable Element selectFirst(String cssQuery, Iterable<Element> roots) {\n        Validate.notEmpty(cssQuery);\n        Validate.notNull(roots);\n        Evaluator evaluator = evaluatorOf(cssQuery);\n\n        for (Element root : roots) {\n            Element first = Collector.findFirst(evaluator, root);\n            if (first != null) return first;\n        }\n\n        return null;\n    }\n\n    /**\n     Given a CSS identifier (such as a tag, ID, or class), escape any CSS special characters that would otherwise not be\n     valid in a selector.\n\n     @see <a href=\"https://www.w3.org/TR/cssom-1/#serialize-an-identifier\">CSS Object Model, serialize an identifier</a>\n     @since 1.20.1\n     */\n    public static String escapeCssIdentifier(String in) {\n        return TokenQueue.escapeCssIdentifier(in);\n    }\n\n    /**\n     Consume a CSS identifier (ID or class) off the queue.\n     <p>Note: For backwards compatibility this method supports improperly formatted CSS identifiers, e.g. {@code 1} instead\n     of {@code \\31}.</p>\n\n     @return The unescaped identifier.\n     @throws IllegalArgumentException if an invalid escape sequence was found.\n     @see <a href=\"https://www.w3.org/TR/css-syntax-3/#consume-name\">CSS Syntax Module Level 3, Consume an ident sequence</a>\n     @see <a href=\"https://www.w3.org/TR/css-syntax-3/#typedef-ident-token\">CSS Syntax Module Level 3, ident-token</a>\n     @since 1.20.1\n     */\n    public static String unescapeCssIdentifier(String in) {\n        try (TokenQueue tq = new TokenQueue(in)) {\n            return tq.consumeCssIdentifier();\n        }\n    }\n\n    /**\n     Parse a CSS query into an Evaluator. If you are evaluating the same query repeatedly, it may be more efficient to\n     parse it once and reuse the Evaluator.\n\n     @param css CSS query\n     @return Evaluator\n     @see Selector selector query syntax\n     @throws Selector.SelectorParseException if the CSS query is invalid\n     @since 1.21.1\n     */\n    public static Evaluator evaluatorOf(String css) {\n        return QueryParser.parse(css);\n    }\n\n    public static class SelectorParseException extends IllegalStateException {\n        public SelectorParseException(String msg) {\n            super(msg);\n        }\n\n        public SelectorParseException(String msg, Object... msgArgs) {\n            super(String.format(msg, msgArgs));\n        }\n\n        public SelectorParseException(Throwable cause, String msg, Object... msgArgs) {\n            super(String.format(msg, msgArgs), cause);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/StructuralEvaluator.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.internal.SoftPool;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.NodeIterator;\nimport org.jsoup.nodes.TextNode;\n\nimport java.util.ArrayList;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\n/**\n * Base structural evaluator.\n */\nabstract class StructuralEvaluator extends Evaluator {\n    final Evaluator evaluator;\n    boolean wantsNodes; // if the evaluator requested nodes, not just elements\n\n    public StructuralEvaluator(Evaluator evaluator) {\n        this.evaluator = evaluator;\n        wantsNodes = evaluator.wantsNodes();\n    }\n\n    @Override\n    boolean wantsNodes() {\n        return wantsNodes;\n    }\n\n    // Memoize inner matches, to save repeated re-evaluations of parent, sibling etc.\n    // root + element: Boolean matches. ThreadLocal in case the Evaluator is compiled then reused across multi threads\n    final ThreadLocal<Map<Node, Map<Node, Boolean>>> threadMemo = ThreadLocal.withInitial(WeakHashMap::new);\n\n    boolean memoMatches(final Element root, final Node node) {\n        Map<Node, Map<Node, Boolean>> rootMemo = threadMemo.get();\n        Map<Node, Boolean> memo = rootMemo.computeIfAbsent(root, r -> new WeakHashMap<>());\n        return memo.computeIfAbsent(node, test -> evaluator.matches(root, test));\n    }\n\n    @Override protected void reset() {\n        threadMemo.remove();\n        evaluator.reset();\n        super.reset();\n    }\n\n    @Override\n    public boolean matches(Element root, Element element) {\n        return evaluateMatch(root, element);\n    }\n\n    @Override\n    boolean matches(Element root, LeafNode leafNode) {\n        return evaluateMatch(root, leafNode);\n    }\n\n    abstract boolean evaluateMatch(Element root, Node node);\n\n    static class Root extends Evaluator {\n        @Override\n        public boolean matches(Element root, Element element) {\n            return root == element;\n        }\n\n        @Override protected int cost() {\n            return 1;\n        }\n\n        @Override public String toString() {\n            return \">\";\n        }\n    }\n\n    static class Has extends StructuralEvaluator {\n        static final SoftPool<NodeIterator<Node>> NodeIterPool =\n            new SoftPool<>(() -> new NodeIterator<>(new TextNode(\"\"), Node.class));\n        // the element here is just a placeholder so this can be final - gets set in restart()\n\n        private final boolean checkSiblings; // evaluating against siblings (or children)\n\n        public Has(Evaluator evaluator) {\n            super(evaluator);\n            checkSiblings = evalWantsSiblings(evaluator);\n        }\n\n        @Override public boolean matches(Element root, Element element) {\n            if (checkSiblings) { // evaluating against siblings\n                for (Element sib = element.firstElementSibling(); sib != null; sib = sib.nextElementSibling()) {\n                    if (sib != element && evaluator.matches(element, sib)) { // don't match against self\n                        return true;\n                    }\n                }\n            }\n            // otherwise we only want to match children (or below), and not the input element. And we want to minimize GCs so reusing the Iterator obj\n            NodeIterator<Node> it = NodeIterPool.borrow();\n            it.restart(element);\n            try {\n                while (it.hasNext()) {\n                    Node node = it.next();\n                    if (node == element) continue; // don't match self, only descendants\n                    if (evaluator.matches(element, node)) {\n                        return true;\n                    }\n                }\n            } finally {\n                NodeIterPool.release(it);\n            }\n            return false;\n        }\n\n        @Override\n        boolean evaluateMatch(Element root, Node node) {\n            return false; // unused; :has(::comment)) goes via implicit root combinator\n        }\n\n        /* Test if the :has sub-clause wants sibling elements (vs nested elements) - will be a Combining eval */\n        private static boolean evalWantsSiblings(Evaluator eval) {\n            if (eval instanceof CombiningEvaluator) {\n                CombiningEvaluator ce = (CombiningEvaluator) eval;\n                for (Evaluator innerEval : ce.evaluators) {\n                    if (innerEval instanceof PreviousSibling || innerEval instanceof ImmediatePreviousSibling)\n                        return true;\n                }\n            }\n            return false;\n        }\n\n        @Override protected int cost() {\n            return 10 * evaluator.cost();\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":has(%s)\", evaluator);\n        }\n    }\n\n    /** Implements the :is(sub-query) pseudo-selector */\n    static class Is extends StructuralEvaluator {\n        public Is(Evaluator evaluator) {\n            super(evaluator);\n        }\n\n        @Override\n        boolean evaluateMatch(Element root, Node node) {\n            return evaluator.matches(root, node);\n        }\n\n        @Override protected int cost() {\n            return 2 + evaluator.cost();\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":is(%s)\", evaluator);\n        }\n    }\n\n    static class Not extends StructuralEvaluator {\n        public Not(Evaluator evaluator) {\n            super(evaluator);\n        }\n\n        @Override\n        boolean evaluateMatch(Element root, Node node) {\n            return !memoMatches(root, node);\n        }\n\n        @Override protected int cost() {\n            return 2 + evaluator.cost();\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\":not(%s)\", evaluator);\n        }\n    }\n\n    /**\n     Any Ancestor (i.e., ascending parent chain.).\n     */\n    static class Ancestor extends StructuralEvaluator {\n        public Ancestor(Evaluator evaluator) {\n            super(evaluator);\n        }\n\n        @Override\n        boolean evaluateMatch(Element root, Node node) {\n            if (root == node)\n                return false;\n\n            for (Node parent = node.parent(); parent != null; parent = parent.parent()) {\n                if (memoMatches(root, parent))\n                    return true;\n                if (parent == root)\n                    break;\n            }\n            return false;\n        }\n\n        @Override\n        protected int cost() {\n            return 8 * evaluator.cost(); // probably lower than has(), but still significant, depending on doc and el depth.\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"%s \", evaluator);\n        }\n    }\n\n    /**\n     Holds a list of evaluators for one > two > three immediate parent matches, and the final direct evaluator under\n     test. To match, these are effectively ANDed together, starting from the last, matching up to the first.\n     */\n    static class ImmediateParentRun extends StructuralEvaluator {\n        final ArrayList<Evaluator> evaluators = new ArrayList<>();\n        int cost = 2;\n\n        public ImmediateParentRun(Evaluator evaluator) {\n            super(evaluator);\n            evaluators.add(evaluator);\n            cost += evaluator.cost();\n        }\n\n        void add(Evaluator evaluator) {\n            evaluators.add(evaluator);\n            cost += evaluator.cost();\n            wantsNodes |= evaluator.wantsNodes();\n        }\n\n        @Override boolean evaluateMatch(Element root, Node node) {\n            if (node == root)\n                return false; // cannot match as the second eval (first parent test) would be above the root\n\n            for (int i = evaluators.size() -1; i >= 0; --i) {\n                if (node == null)\n                    return false;\n                Evaluator eval = evaluators.get(i);\n                if (!eval.matches(root, node))\n                    return false;\n                node = node.parent();\n            }\n            return true;\n        }\n\n        @Override protected int cost() {\n            return cost;\n        }\n\n        @Override\n        protected void reset() {\n            for (Evaluator evaluator : evaluators) {\n                evaluator.reset();\n            }\n            super.reset();\n        }\n\n        @Override\n        public String toString() {\n            return StringUtil.join(evaluators, \" > \");\n        }\n    }\n\n    static class PreviousSibling extends StructuralEvaluator {\n        public PreviousSibling(Evaluator evaluator) {\n            super(evaluator);\n        }\n\n        // matches any previous sibling, so can be same in Element only or wantsNodes context\n        @Override boolean evaluateMatch(Element root, Node node) {\n            if (root == node) return false;\n\n            for (Node sib = node.firstSibling(); sib != null; sib = sib.nextSibling()) {\n                if (sib == node) break;\n                if (memoMatches(root, sib)) return true;\n            }\n\n            return false;\n        }\n\n        @Override protected int cost() {\n            return 3 * evaluator.cost();\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"%s ~ \", evaluator);\n        }\n    }\n\n    static class ImmediatePreviousSibling extends StructuralEvaluator {\n        public ImmediatePreviousSibling(Evaluator evaluator) {\n            super(evaluator);\n        }\n\n        @Override boolean evaluateMatch(Element root, Node node) {\n            if (root == node) return false;\n\n            Node prev = wantsNodes ? node.previousSibling() : node.previousElementSibling();\n            return prev != null && memoMatches(root, prev);\n        }\n\n        @Override protected int cost() {\n            return 2 + evaluator.cost();\n        }\n\n        @Override\n        public String toString() {\n            return String.format(\"%s + \", evaluator);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/jsoup/select/package-info.java",
    "content": "/**\n Packages to support the CSS-style element selector.\n {@link org.jsoup.select.Selector Selector defines the query syntax.}\n */\n@NullMarked\npackage org.jsoup.select;\n\nimport org.jspecify.annotations.NullMarked;\n"
  },
  {
    "path": "src/main/java11/module-info.java",
    "content": "module org.jsoup {\n    exports org.jsoup;\n    exports org.jsoup.helper;\n    exports org.jsoup.nodes;\n    exports org.jsoup.parser;\n    exports org.jsoup.safety;\n    exports org.jsoup.select;\n\n    requires transitive java.xml;  // for org.w3c.dom out of W3CDom\n    requires static org.jspecify;  // nullability annotations\n    requires static java.net.http; // HttpClient on Java 11; guarded\n}\n"
  },
  {
    "path": "src/main/java11/org/jsoup/helper/HttpClientExecutor.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jspecify.annotations.Nullable;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpHeaders;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.jsoup.helper.HttpConnection.Response;\nimport static org.jsoup.helper.HttpConnection.Response.writePost;\n\n/**\n Executes requests using the HttpClient, for http/2 support. Enabled by default when available. To disable, set\n property {@code jsoup.useHttpClient} to {@code false}.\n */\nclass HttpClientExecutor extends RequestExecutor {\n    // HttpClient expects proxy settings per client; we do per request, so held as a thread local. Can't do same for\n    // auth because that callback is on a worker thread, so can only do auth per Connection. So we create a new client\n    // if the authenticator is different between requests\n    static ThreadLocal<@Nullable Proxy> perRequestProxy = new ThreadLocal<>();\n\n    @Nullable\n    HttpResponse<InputStream> hRes;\n\n    public HttpClientExecutor(HttpConnection.Request request, HttpConnection.@Nullable Response previousResponse) {\n        super(request, previousResponse);\n    }\n\n    /**\n     Retrieve the HttpClient from the Connection, or create a new one. Allows for connection pooling of requests in the\n     same Connection (session).\n     */\n    HttpClient client() {\n        // we try to reuse the same Client across requests in a given Connection; but if the request's auth or ssl context have changed, we need to create a new client\n        if (req.connection.client != null) {\n            HttpClient client = (HttpClient) req.connection.client;\n            boolean reuse = true;\n\n            RequestAuthenticator prevAuth = req.connection.lastAuth;\n            req.connection.lastAuth = req.authenticator;\n            if (prevAuth != req.authenticator) // might both be null\n                reuse = false;\n            if (req.sslContext != null && !(client.sslContext() == req.sslContext)) // client returns default context if not otherwise set\n                reuse = false;\n            if (reuse) return client;\n        }\n\n        HttpClient.Builder builder = HttpClient.newBuilder();\n        builder.followRedirects(HttpClient.Redirect.NEVER); // customized redirects\n        builder.proxy(new ProxyWrap()); // thread local impl for per request; called on executing thread\n        if (req.authenticator != null) builder.authenticator(new AuthenticationHandler(req.authenticator));\n        if (req.sslContext    != null) builder.sslContext(req.sslContext);\n\n        HttpClient client = builder.build();\n        req.connection.client = client;\n        return client;\n    }\n\n    @Override\n    HttpConnection.Response execute() throws IOException {\n        try {\n            HttpRequest.Builder reqBuilder =\n                HttpRequest.newBuilder(req.url.toURI()).method(req.method.name(), requestBody(req));\n            if (req.timeout() > 0) reqBuilder.timeout(\n                Duration.ofMillis(req.timeout())); // infinite if unset (UrlConnection / jsoup uses 0 for same)\n            CookieUtil.applyCookiesToRequest(req, reqBuilder::header);\n\n            // headers:\n            req.multiHeaders().forEach((key, values) -> {\n                values.forEach(value -> reqBuilder.header(key, value));\n            });\n\n            if (req.proxy() != null) perRequestProxy.set(req.proxy()); // set up per request proxy\n            HttpRequest hReq = reqBuilder.build();\n            HttpClient client = client();\n            hRes = client.send(hReq, HttpResponse.BodyHandlers.ofInputStream());\n            HttpHeaders headers = hRes.headers();\n\n            // set up the response\n            Response res = new Response(req);\n            res.executor = this;\n            res.method = Connection.Method.valueOf(hRes.request().method());\n            res.url = hRes.uri().toURL();\n            res.statusCode = hRes.statusCode();\n            res.statusMessage = StatusMessage(res.statusCode);\n            res.contentType = headers.firstValue(\"content-type\").orElse(\"\");\n            long length = headers.firstValueAsLong(\"content-length\").orElse(-1);\n            res.contentLength = length < Integer.MAX_VALUE ? (int) length : -1;\n            res.prepareResponse(headers.map(), prevRes);\n\n            return res;\n        } catch (IOException e) {\n            safeClose();\n            throw e;\n        } catch (InterruptedException e) {\n            safeClose();\n            Thread.currentThread().interrupt();\n            throw new IOException(e);\n        } catch (URISyntaxException e) {\n            throw new IllegalArgumentException(\"Malformed URL: \" + req.url, e);\n        } finally {\n            // detach per request proxy\n            perRequestProxy.remove();\n        }\n    }\n\n    /**\n     As HTTP/2 no longer provides a server-set status message, and HttpClient doesn't parse it for 1.1, just provide minimal stock ones, for loggers.\n     */\n    static String StatusMessage(int statusCode) {\n        if (statusCode < 400) return \"OK\";\n        if (statusCode == 404) return \"Not Found\";\n        return \"Error \" + statusCode;\n    }\n\n    @Override\n    InputStream responseBody() throws IOException {\n        if (hRes == null) throw new IllegalStateException(\"Not yet executed\");\n        return hRes.body();\n    }\n\n    @Override\n    void safeClose() {\n        if (hRes != null) {\n            InputStream body = hRes.body();\n            if (body != null) {\n                try {\n                    body.close();\n                } catch (IOException ignored) {}\n            }\n            hRes = null;\n        }\n    }\n\n    static HttpRequest.BodyPublisher requestBody(final HttpConnection.Request req) throws IOException {\n        if (req.method.hasBody()) {\n            ByteArrayOutputStream buf = new ByteArrayOutputStream();\n            writePost(req, buf);\n            return HttpRequest.BodyPublishers.ofByteArray(buf.toByteArray());\n        } else {\n            return HttpRequest.BodyPublishers.noBody();\n        }\n    }\n\n    static class ProxyWrap extends ProxySelector {\n        // empty list for no proxy:\n        static final List<Proxy> NoProxy = new ArrayList<>(0);\n\n        @Override\n        public List<Proxy> select(URI uri) {\n            Proxy proxy = perRequestProxy.get();\n            if (proxy != null) {\n                return Collections.singletonList(proxy);\n            }\n            ProxySelector defaultSelector = ProxySelector.getDefault();\n            if (defaultSelector != null && defaultSelector != this) { // avoid recursion if we were set as default\n                return defaultSelector.select(uri);\n            }\n            return NoProxy;\n        }\n\n        @Override\n        public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {\n            if (perRequestProxy.get() != null) {\n                return;  // no-op\n            }\n            ProxySelector defaultSelector = ProxySelector.getDefault();\n            if (defaultSelector != null && defaultSelector != this) {\n                defaultSelector.connectFailed(uri, sa, ioe);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java11/org/jsoup/helper/RequestAuthHandler.java",
    "content": "package org.jsoup.helper;\n\nimport java.net.HttpURLConnection;\nimport java.net.http.HttpClient;\n\n/**\n A per-request authentication shim, used in Java 9+.\n */\nclass RequestAuthHandler implements AuthenticationHandler.AuthShim {\n    public RequestAuthHandler() {}\n\n    @Override public void enable(RequestAuthenticator auth, Object connOrHttp) {\n        AuthenticationHandler authenticator = new AuthenticationHandler(auth);\n\n        // this is a bit ugly, but a simple way to support setting authentication on both urlconnection and httpclient without more multi-version shims\n        if (connOrHttp instanceof HttpURLConnection) {\n            HttpURLConnection conn = (HttpURLConnection) connOrHttp;\n            conn.setAuthenticator(authenticator);\n        } else if (connOrHttp instanceof HttpClient.Builder) {\n            HttpClient.Builder builder = (HttpClient.Builder) connOrHttp;\n            builder.authenticator(authenticator);\n        } else {\n            throw new IllegalArgumentException(\"Unsupported executor: \" + connOrHttp.getClass().getName());\n        }\n    }\n\n    @Override public void remove() {\n        // noop; would remove thread-local in Global Handler\n    }\n\n    @Override public AuthenticationHandler get(AuthenticationHandler helper) {\n        // would get thread-local in Global Handler\n        return helper;\n    }\n}\n"
  },
  {
    "path": "src/main/javadoc/overview.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>jsoup Javadoc overview</title>\n</head>\n<body>\n<h1>jsoup: Java HTML parser that makes sense of real-world HTML soup.</h1>\n\n<p><b>jsoup</b> is a Java library for working with real-world HTML. It provides a very convenient API for fetching URLs\n  and extracting and manipulating data, using the best of HTML5 DOM methods and CSS selectors.</p>\n\n<p>jsoup implements the <a href=\"https://html.spec.whatwg.org/multipage/\">WHATWG HTML</a> specification, and parses HTML to the same DOM\n  as modern browsers do.</p>\n\n<ul>\n  <li>parse HTML from a URL, file, or string\n  <li>find and extract data, using DOM traversal or CSS selectors\n  <li>manipulate the HTML elements, attributes, and text\n  <li>clean user-submitted content against a safelist, to prevent XSS\n  <li>output tidy HTML\n</ul>\n\n<p>jsoup is designed to deal with all varieties of HTML found in the wild; from pristine and validating,\n  to invalid tag-soup; jsoup will create a sensible parse tree.</p>\n\n<p>See <a href=\"https://jsoup.org/\"><b>jsoup.org</b></a> for downloads, documentation, and examples.</p>\n\n@author <a href=\"https://jonathanhedley.com/\">Jonathan Hedley</a>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/main/resources/META-INF/proguard/org.jsoup_jsoup.pro",
    "content": "-dontwarn com.google.re2j.**\n"
  },
  {
    "path": "src/test/java/org/jsoup/JsoupTest.java",
    "content": "package org.jsoup;\n\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class JsoupTest {\n    // Tests for the Jsoup class. Mostly for code coverage for methods that haven't been covered elsewhere already.\n\n    @Test\n    void parseWithPath() throws IOException {\n        // parse(Path path, @Nullable String charsetName, String baseUri)\n        Path path = ParseTest.getPath(\"/htmltests/medium.html\");\n        Document doc = Jsoup.parse(path, \"UTF-8\", \"https://example.com/\");\n        String title = \"Medium HTML\";\n        assertEquals(title, doc.title());\n\n        // parse(Path path)\n        doc = Jsoup.parse(path);\n        assertEquals(title, doc.title());\n\n        // (Path path, @Nullable String charsetName, String baseUri, Parser parser)\n        doc = Jsoup.parse(path, \"UTF-8\", \"https://example.com/\", Parser.htmlParser());\n        assertEquals(title, doc.title());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/MultiLocaleExtension.java",
    "content": "package org.jsoup;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\nimport java.lang.annotation.*;\nimport java.util.Locale;\nimport java.util.stream.Stream;\n\npublic class MultiLocaleExtension implements AfterEachCallback, ArgumentsProvider {\n    private final Locale defaultLocale = Locale.getDefault();\n\n    @Override\n    public void afterEach(ExtensionContext context) {\n        Locale.setDefault(defaultLocale);\n    }\n\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {\n        return Stream.of(Arguments.of(Locale.ENGLISH), Arguments.arguments(new Locale(\"tr\")));\n    }\n\n\n    @Documented\n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.RUNTIME)\n    @ArgumentsSource(MultiLocaleExtension.class)\n    @ExtendWith(MultiLocaleExtension.class)\n    @ParameterizedTest\n    public @interface MultiLocaleTest {\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/SerializationExceptionTest.java",
    "content": "package org.jsoup;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SerializationExceptionTest {\n    @Test void constructors() {\n        SerializationException e = new SerializationException(\"message\");\n        assertEquals(\"message\", e.getMessage());\n        assertNull(e.getCause());\n\n        SerializationException e2 = new SerializationException(\"message\", new Exception(\"cause\"));\n        assertEquals(\"message\", e2.getMessage());\n        assertEquals(\"cause\", e2.getCause().getMessage());\n\n        SerializationException e3 = new SerializationException();\n        assertNull(e3.getMessage());\n        assertNull(e3.getCause());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/TextUtil.java",
    "content": "package org.jsoup;\n\nimport java.util.regex.Pattern;\n\n/**\n Text utils to ease testing\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class TextUtil {\n    static Pattern stripper = Pattern.compile(\"\\\\r?\\\\n\\\\s*\");\n    static Pattern stripLines = Pattern.compile(\"\\\\r?\\\\n?\");\n    static Pattern spaceCollapse = Pattern.compile(\"\\\\s{2,}\");\n    static Pattern tagSpaceCollapse = Pattern.compile(\">\\\\s+<\");\n    static Pattern stripCRs = Pattern.compile(\"\\\\r*\");\n\n    public static String stripNewlines(String text) {\n        return stripper.matcher(text).replaceAll(\"\");\n    }\n\n    public static String normalizeSpaces(String text) {\n        text = stripLines.matcher(text).replaceAll(\"\");\n        text = stripper.matcher(text).replaceAll(\"\");\n        text = spaceCollapse.matcher(text).replaceAll(\" \");\n        text = tagSpaceCollapse.matcher(text).replaceAll(\"><\");\n        return text;\n    }\n\n    public static String stripCRs(String text) {\n        return stripCRs.matcher(text).replaceAll(\"\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/AuthenticationHandlerTest.java",
    "content": "package org.jsoup.helper;\n\npublic class AuthenticationHandlerTest {\n    public static final int MaxAttempts = AuthenticationHandler.MaxAttempts;\n\n    // tests are in ConnectionTest, ProxyTest. This class just makes the MaxAttempts visible for test.\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/CookieUtilTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass CookieUtilTest {\n\n    @Test void parseCookie() {\n        HttpConnection.Response res = new HttpConnection.Response();\n\n        CookieUtil.parseCookie(\"foo=bar qux; Domain=.example.com; Path=/; Secure\", res);\n        CookieUtil.parseCookie(\"bar=foo qux\", res);\n        CookieUtil.parseCookie(\"=bar; Domain=.example.com; Path=/; Secure\", res);\n        CookieUtil.parseCookie(\"; Domain=.example.com; Path=/\", res);\n        CookieUtil.parseCookie(\"\", res);\n        CookieUtil.parseCookie(null, res);\n\n        assertEquals(3, res.cookies().size());\n        assertEquals(\"bar qux\", res.cookies.get(\"foo\"));\n        assertEquals(\"foo qux\", res.cookies.get(\"bar\"));\n        assertEquals(\".example.com\", res.cookies.get(\"; Domain\")); // no actual cookie name or val\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/DataUtilTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.internal.ControllableInputStream;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.*;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jsoup.integration.ParseTest.getFile;\nimport static org.jsoup.integration.ParseTest.getPath;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class DataUtilTest {\n    @Test\n    public void testCharset() {\n        assertEquals(\"utf-8\", DataUtil.getCharsetFromContentType(\"text/html;charset=utf-8 \"));\n        assertEquals(\"UTF-8\", DataUtil.getCharsetFromContentType(\"text/html; charset=UTF-8\"));\n        assertEquals(\"ISO-8859-1\", DataUtil.getCharsetFromContentType(\"text/html; charset=ISO-8859-1\"));\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html\"));\n        assertNull(DataUtil.getCharsetFromContentType(null));\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html;charset=Unknown\"));\n    }\n\n    @Test\n    public void testQuotedCharset() {\n        assertEquals(\"utf-8\", DataUtil.getCharsetFromContentType(\"text/html; charset=\\\"utf-8\\\"\"));\n        assertEquals(\"UTF-8\", DataUtil.getCharsetFromContentType(\"text/html;charset=\\\"UTF-8\\\"\"));\n        assertEquals(\"ISO-8859-1\", DataUtil.getCharsetFromContentType(\"text/html; charset=\\\"ISO-8859-1\\\"\"));\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html; charset=\\\"Unsupported\\\"\"));\n        assertEquals(\"UTF-8\", DataUtil.getCharsetFromContentType(\"text/html; charset='UTF-8'\"));\n    }\n\n    private ControllableInputStream stream(String data) {\n        return ControllableInputStream.wrap(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), 0);\n    }\n\n    private ControllableInputStream stream(String data, String charset) {\n        return ControllableInputStream.wrap(new ByteArrayInputStream(data.getBytes(Charset.forName(charset))), 0);\n    }\n\n    @Test\n    public void discardsSpuriousByteOrderMark() throws IOException {\n        String html = \"\\uFEFF<html><head><title>One</title></head><body>Two</body></html>\";\n        Document doc = DataUtil.parseInputStream(stream(html), \"UTF-8\", \"http://foo.com/\", Parser.htmlParser());\n        assertEquals(\"One\", doc.head().text());\n    }\n\n    @Test\n    public void discardsSpuriousByteOrderMarkWhenNoCharsetSet() throws IOException {\n        String html = \"\\uFEFF<html><head><title>One</title></head><body>Two</body></html>\";\n        Document doc = DataUtil.parseInputStream(stream(html), null, \"http://foo.com/\", Parser.htmlParser());\n        assertEquals(\"One\", doc.head().text());\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().displayName());\n    }\n\n    @Test\n    public void shouldNotThrowExceptionOnEmptyCharset() {\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html; charset=\"));\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html; charset=;\"));\n    }\n\n    @Test\n    public void shouldSelectFirstCharsetOnWeirdMultileCharsetsInMetaTags() {\n        assertEquals(\"ISO-8859-1\", DataUtil.getCharsetFromContentType(\"text/html; charset=ISO-8859-1, charset=1251\"));\n    }\n\n    @Test\n    public void shouldCorrectCharsetForDuplicateCharsetString() {\n        assertEquals(\"iso-8859-1\", DataUtil.getCharsetFromContentType(\"text/html; charset=charset=iso-8859-1\"));\n    }\n\n    @Test\n    public void shouldReturnNullForIllegalCharsetNames() {\n        assertNull(DataUtil.getCharsetFromContentType(\"text/html; charset=$HJKDF§$/(\"));\n    }\n\n    @Test\n    public void generatesMimeBoundaries() {\n        String m1 = DataUtil.mimeBoundary();\n        String m2 = DataUtil.mimeBoundary();\n\n        assertEquals(DataUtil.boundaryLength, m1.length());\n        assertEquals(DataUtil.boundaryLength, m2.length());\n        assertNotSame(m1, m2);\n    }\n\n    @Test\n    public void wrongMetaCharsetFallback() throws IOException {\n        String html = \"<html><head><meta charset=iso-8></head><body></body></html>\";\n\n        Document doc = DataUtil.parseInputStream(stream(html), null, \"http://example.com\", Parser.htmlParser());\n\n        final String expected = \"<html>\\n\" +\n                \" <head>\\n\" +\n                \"  <meta charset=\\\"iso-8\\\">\\n\" +\n                \" </head>\\n\" +\n                \" <body></body>\\n\" +\n                \"</html>\";\n\n        assertEquals(expected, doc.toString());\n    }\n\n    @Test\n    public void secondMetaElementWithContentTypeContainsCharsetParameter() throws Exception {\n        String html = \"<html><head>\" +\n                \"<meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html\\\">\" +\n                \"<meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=euc-kr\\\">\" +\n                \"</head><body>한국어</body></html>\";\n\n        Document doc = DataUtil.parseInputStream(stream(html, \"euc-kr\"), null, \"http://example.com\", Parser.htmlParser());\n\n        assertEquals(\"한국어\", doc.body().text());\n    }\n\n    @Test\n    public void firstMetaElementWithCharsetShouldBeUsedForDecoding() throws Exception {\n        String html = \"<html><head>\" +\n                \"<meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=iso-8859-1\\\">\" +\n                \"<meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=koi8-u\\\">\" +\n                \"</head><body>Übergrößenträger</body></html>\";\n\n        Document doc = DataUtil.parseInputStream(stream(html, \"iso-8859-1\"), null, \"http://example.com\", Parser.htmlParser());\n\n        assertEquals(\"Übergrößenträger\", doc.body().text());\n    }\n\n    @Test\n    public void parseSequenceInputStream() throws IOException {\n        // https://github.com/jhy/jsoup/pull/1671\n        File in = getFile(\"/htmltests/medium.html\");\n        String fileContent = new String(Files.readAllBytes(in.toPath()));\n        int halfLength = fileContent.length() / 2;\n        String firstPart = fileContent.substring(0, halfLength);\n        String secondPart = fileContent.substring(halfLength);\n        SequenceInputStream sequenceStream = new SequenceInputStream(\n                stream(firstPart),\n                stream(secondPart)\n        );\n        ControllableInputStream stream = ControllableInputStream.wrap(sequenceStream, 0);\n        Document doc = DataUtil.parseInputStream(stream, null, \"\", Parser.htmlParser());\n        assertEquals(fileContent, doc.outerHtml());\n    }\n\n    @Test\n    public void supportsBOMinFiles() throws IOException {\n        // test files from http://www.i18nl10n.com/korean/utftest/\n        File in = getFile(\"/bomtests/bom_utf16be.html\");\n        Document doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertTrue(doc.title().contains(\"UTF-16BE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf16le.html\");\n        doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertTrue(doc.title().contains(\"UTF-16LE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf32be.html\");\n        doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertTrue(doc.title().contains(\"UTF-32BE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf32le.html\");\n        doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertTrue(doc.title().contains(\"UTF-32LE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n    }\n\n    @Test\n    public void streamerSupportsBOMinFiles() throws IOException {\n        // test files from http://www.i18nl10n.com/korean/utftest/\n        Path in = getFile(\"/bomtests/bom_utf16be.html\").toPath();\n        Parser parser = Parser.htmlParser();\n        Document doc = DataUtil.streamParser(in, null, \"http://example.com\", parser).complete();\n        assertTrue(doc.title().contains(\"UTF-16BE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf16le.html\").toPath();\n        doc = DataUtil.streamParser(in, null, \"http://example.com\", parser).complete();\n        assertTrue(doc.title().contains(\"UTF-16LE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf32be.html\").toPath();\n        doc = DataUtil.streamParser(in, null, \"http://example.com\", parser).complete();\n        assertTrue(doc.title().contains(\"UTF-32BE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n\n        in = getFile(\"/bomtests/bom_utf32le.html\").toPath();\n        doc = DataUtil.streamParser(in, null, \"http://example.com\", parser).complete();\n        assertTrue(doc.title().contains(\"UTF-32LE\"));\n        assertTrue(doc.text().contains(\"가각갂갃간갅\"));\n    }\n\n    @Test\n    public void supportsUTF8BOM() throws IOException {\n        File in = getFile(\"/bomtests/bom_utf8.html\");\n        Document doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertEquals(\"OK\", doc.head().select(\"title\").text());\n    }\n\n    @Test\n    public void noExtraNULLBytes() throws IOException {\n    \tfinal byte[] b = \"<html><head><meta charset=\\\"UTF-8\\\"></head><body><div><u>ü</u>ü</div></body></html>\".getBytes(StandardCharsets.UTF_8);\n\n    \tDocument doc = Jsoup.parse(new ByteArrayInputStream(b), null, \"\");\n    \tassertFalse( doc.outerHtml().contains(\"\\u0000\") );\n    }\n\n    @Test\n    public void supportsZippedUTF8BOM() throws IOException {\n        File in = getFile(\"/bomtests/bom_utf8.html.gz\");\n        Document doc = Jsoup.parse(in, null, \"http://example.com\");\n        assertEquals(\"OK\", doc.head().select(\"title\").text());\n        assertEquals(\"There is a UTF8 BOM at the top (before the XML decl). If not read correctly, will look like a non-joining space.\", doc.body().text());\n    }\n\n    @Test\n    public void streamerSupportsZippedUTF8BOM() throws IOException {\n        Path in = getFile(\"/bomtests/bom_utf8.html.gz\").toPath();\n        Document doc = DataUtil.streamParser(in, null, \"http://example.com\", Parser.htmlParser()).complete();\n        assertEquals(\"OK\", doc.head().select(\"title\").text());\n        assertEquals(\"There is a UTF8 BOM at the top (before the XML decl). If not read correctly, will look like a non-joining space.\", doc.body().text());\n    }\n\n    @Test\n    public void supportsXmlCharsetDeclaration() throws IOException {\n        String encoding = \"iso-8859-1\";\n        InputStream soup = new ByteArrayInputStream((\n                \"<?xml version=\\\"1.0\\\" encoding=\\\"iso-8859-1\\\"?>\" +\n                        \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Strict//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\\\">\" +\n                        \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" lang=\\\"en\\\" xml:lang=\\\"en\\\">Hellö Wörld!</html>\"\n        ).getBytes(Charset.forName(encoding)));\n\n        Document doc = Jsoup.parse(soup, null, \"\");\n        assertEquals(\"Hellö Wörld!\", doc.body().text());\n    }\n\n\n    @Test\n    public void loadsGzipFile() throws IOException {\n        File in = getFile(\"/htmltests/gzip.html.gz\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"Gzip test\", doc.title());\n        assertEquals(\"This is a gzipped HTML file.\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void loadsGzipPath() throws IOException {\n        Path in = getPath(\"/htmltests/gzip.html.gz\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"Gzip test\", doc.title());\n        assertEquals(\"This is a gzipped HTML file.\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void loadsZGzipFile() throws IOException {\n        // compressed on win, with z suffix\n        File in = getFile(\"/htmltests/gzip.html.z\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"Gzip test\", doc.title());\n        assertEquals(\"This is a gzipped HTML file.\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void loadsZGzipPath() throws IOException {\n        // compressed on win, with z suffix\n        Path in = getPath(\"/htmltests/gzip.html.z\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"Gzip test\", doc.title());\n        assertEquals(\"This is a gzipped HTML file.\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void handlesFakeGzipFile() throws IOException {\n        File in = getFile(\"/htmltests/fake-gzip.html.gz\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"This is not gzipped\", doc.title());\n        assertEquals(\"And should still be readable.\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void handlesFakeGzipPath() throws IOException {\n        Path in = getPath(\"/htmltests/fake-gzip.html.gz\");\n        Document doc = Jsoup.parse(in, null);\n        assertEquals(\"This is not gzipped\", doc.title());\n        assertEquals(\"And should still be readable.\", doc.selectFirst(\"p\").text());\n    }\n\n    // an input stream to give a range of output sizes, that changes on each read\n    static class VaryingReadInputStream extends InputStream {\n        final InputStream in;\n        int stride = 0;\n\n        VaryingReadInputStream(InputStream in) {\n            this.in = in;\n        }\n\n        public int read() throws IOException {\n            return in.read();\n        }\n\n        public int read(byte[] b) throws IOException {\n            return in.read(b, 0, Math.min(b.length, ++stride));\n        }\n\n        public int read(byte[] b, int off, int len) throws IOException {\n            return in.read(b, off, Math.min(len, ++stride));\n        }\n    }\n\n    @Test\n    void handlesChunkedInputStream() throws IOException {\n        File inputFile = ParseTest.getFile(\"/htmltests/large.html\");\n        String input = ParseTest.getFileAsString(inputFile);\n        VaryingReadInputStream stream = new VaryingReadInputStream(ParseTest.inputStreamFrom(input));\n\n        Document expected = Jsoup.parse(input, \"https://example.com\");\n        Document doc = Jsoup.parse(stream, null, \"https://example.com\");\n        assertTrue(doc.hasSameValue(expected));\n    }\n\n    @Test\n    void handlesUnlimitedRead() throws IOException {\n        File inputFile = ParseTest.getFile(\"/htmltests/large.html\");\n        String input = ParseTest.getFileAsString(inputFile);\n        VaryingReadInputStream stream = new VaryingReadInputStream(ParseTest.inputStreamFrom(input));\n\n        ByteBuffer byteBuffer = DataUtil.readToByteBuffer(stream, 0);\n        String read = new String(byteBuffer.array(), 0, byteBuffer.limit(), StandardCharsets.UTF_8);\n\n        assertEquals(input, read);\n    }\n\n    @Test void controllableInputStreamAllowsNull() throws IOException {\n        ControllableInputStream is = ControllableInputStream.wrap(null, 0);\n        assertNotNull(is);\n        assertTrue(is.baseReadFully());\n        is.close();\n    }\n\n    @Test void streamParserSurrogateAcrossBuffer() throws IOException {\n        // https://github.com/jhy/jsoup/issues/2353\n        try (StreamParser parser = DataUtil.streamParser(ParseTest.getPath(\"/fuzztests/2353.html.gz\"), DataUtil.UTF_8, \"\", Parser.htmlParser())) {\n            Document doc = parser.complete();\n            String html = doc.html();\n            assertTrue(html.contains(\"Read-Fully!\"));\n        }\n    }\n\n    @Test void parseSurrogateAcrossBuffer() throws IOException {\n        Document doc = Jsoup.parse(ParseTest.getPath(\"/fuzztests/2353.html.gz\"));\n        assertTrue(doc.html().contains(\"Read-Fully!\"));\n    }\n\n    @Test\n    void charsetSniffingCanReuseTruncatedPreParse() throws IOException {\n        // #2448: when available() reports buffered bytes after the first read, the sniffed pre-parse may be reused while capped, leading to truncation\n\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"<!doctype html><html><head><title>t</title></head><body><pre>\");\n        while (sb.length() < 6200) {\n            sb.append(\"0123456789 abcdefghijklmnopqrstuvwxyz\\n\");\n        }\n        sb.append(\"</pre><main>list</main><hr></body></html>\");\n        String html = sb.toString();\n\n\n        byte[] bytes = html.getBytes(StandardCharsets.UTF_8);\n        ControllableInputStream in = ControllableInputStream.wrap(new BufferedOnceAvailableStream(bytes), 0);\n\n        DataUtil.CharsetDoc charsetDoc = DataUtil.detectCharset(in, null, \"http://example.com/\", Parser.htmlParser());\n        Document doc = DataUtil.parseInputStream(charsetDoc, \"http://example.com/\", Parser.htmlParser());\n\n        assertNotNull(doc.selectFirst(\"hr\"), \"hr should survive the sniff + full parse\");\n    }\n\n    // delivers all bytes in the first read, then signals available()>0 once to trigger a second read and baseReadFully=true\n    static final class BufferedOnceAvailableStream extends InputStream {\n        private final byte[] data;\n        private int pos = 0;\n        private boolean extraSignal = true;\n\n        BufferedOnceAvailableStream(byte[] data) {\n            this.data = data;\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) {\n            if (pos >= data.length) return -1;\n            int take = Math.min(len, data.length - pos);\n            System.arraycopy(data, pos, b, off, take);\n            pos += take;\n            return take;\n        }\n\n        @Override\n        public int read() {\n            return pos < data.length ? (data[pos++] & 0xff) : -1;\n        }\n\n        @Override\n        public int available() {\n            if (pos < data.length)\n                return data.length - pos;\n            if (extraSignal) {\n                extraSignal = false;\n                return 1; // nudge SimpleBufferedInput.fill() to try another read\n            }\n            return 0;\n        }\n    }\n\n    @Test\n    void charsetSniffingIgnoresAdvisoryAvailableIOException() throws IOException {\n        // https://github.com/jhy/jsoup/issues/2474\n        // JDK 8's HttpURLConnection stream may throw from available() once the peer has closed the socket;\n        // that advisory failure does not mean we can't still consume bytes already buffered or read to clean EOF.\n        String html = \"<!doctype html><html><head><title>One</title></head><body>Two</body></html>\";\n        byte[] bytes = html.getBytes(StandardCharsets.UTF_8);\n        InputStream stream = new FilterInputStream(new ByteArrayInputStream(bytes)) {\n            @Override\n            public int available() throws IOException {\n                throw new IOException(\"Stream closed.\");\n            }\n        };\n        ControllableInputStream in = ControllableInputStream.wrap(stream, 0);\n\n        Document doc = DataUtil.parseInputStream(in, null, \"http://example.com/\", Parser.htmlParser());\n\n        assertEquals(\"One\", doc.title());\n        assertEquals(\"Two\", doc.body().text());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/HttpConnectionTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.MultiLocaleExtension.MultiLocaleTest;\nimport org.jsoup.integration.ParseTest;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.IOException;\nimport java.net.Authenticator;\nimport java.net.MalformedURLException;\nimport java.net.PasswordAuthentication;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\nimport static org.jsoup.helper.HttpConnection.Response.fixHeaderEncoding;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class HttpConnectionTest {\n    /* most actual network http connection tests are in integration */\n\n    @Test public void canCreateEmptyConnection() {\n        HttpConnection con = new HttpConnection();\n        assertEquals(Connection.Method.GET, con.request().method());\n        assertThrows(IllegalArgumentException.class, () -> {\n            URL url = con.request().url();\n        });\n    }\n\n    @Test public void throwsExceptionOnResponseWithoutExecute() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection con = HttpConnection.connect(\"http://example.com\");\n            con.response();\n        });\n    }\n\n    @Test public void throwsExceptionOnParseWithoutExecute() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection con = HttpConnection.connect(\"http://example.com\");\n            con.response().parse();\n        });\n    }\n\n    @Test public void throwsExceptionOnBodyWithoutExecute() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection con = HttpConnection.connect(\"http://example.com\");\n            con.response().body();\n        });\n    }\n\n    @Test public void throwsExceptionOnBodyAsBytesWithoutExecute() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection con = HttpConnection.connect(\"http://example.com\");\n            con.response().bodyAsBytes();\n        });\n    }\n\n    @MultiLocaleTest\n    public void caseInsensitiveHeaders(Locale locale) {\n        Locale.setDefault(locale);\n\n        Connection.Response res = new HttpConnection.Response();\n        res.header(\"Accept-Encoding\", \"gzip\");\n        res.header(\"content-type\", \"text/html\");\n        res.header(\"refErrer\", \"http://example.com\");\n\n        assertTrue(res.hasHeader(\"Accept-Encoding\"));\n        assertTrue(res.hasHeader(\"accept-encoding\"));\n        assertTrue(res.hasHeader(\"accept-Encoding\"));\n        assertTrue(res.hasHeader(\"ACCEPT-ENCODING\"));\n\n        assertEquals(\"gzip\", res.header(\"accept-Encoding\"));\n        assertEquals(\"gzip\", res.header(\"ACCEPT-ENCODING\"));\n        assertEquals(\"text/html\", res.header(\"Content-Type\"));\n        assertEquals(\"http://example.com\", res.header(\"Referrer\"));\n\n        res.removeHeader(\"Content-Type\");\n        assertFalse(res.hasHeader(\"content-type\"));\n\n        res.removeHeader(\"ACCEPT-ENCODING\");\n        assertFalse(res.hasHeader(\"Accept-Encoding\"));\n\n        res.header(\"ACCEPT-ENCODING\", \"deflate\");\n        assertEquals(\"deflate\", res.header(\"Accept-Encoding\"));\n        assertEquals(\"deflate\", res.header(\"accept-Encoding\"));\n    }\n\n    @Test public void headers() {\n        Connection con = HttpConnection.connect(\"http://example.com\");\n        Map<String, String> headers = new HashMap<>();\n        headers.put(\"content-type\", \"text/html\");\n        headers.put(\"Connection\", \"keep-alive\");\n        headers.put(\"Host\", \"http://example.com\");\n        con.headers(headers);\n        assertEquals(\"text/html\", con.request().header(\"content-type\"));\n        assertEquals(\"keep-alive\", con.request().header(\"Connection\"));\n        assertEquals(\"http://example.com\", con.request().header(\"Host\"));\n    }\n\n    @Test public void sameHeadersCombineWithComma() {\n        Map<String, List<String>> headers = new HashMap<>();\n        List<String> values = new ArrayList<>();\n        values.add(\"no-cache\");\n        values.add(\"no-store\");\n        headers.put(\"Cache-Control\", values);\n        HttpConnection.Response res = new HttpConnection.Response();\n        res.processResponseHeaders(headers);\n        assertEquals(\"no-cache, no-store\", res.header(\"Cache-Control\"));\n    }\n\n    @Test public void multipleHeaders() {\n        Connection.Request req = new HttpConnection.Request();\n        req.addHeader(\"Accept\", \"Something\");\n        req.addHeader(\"Accept\", \"Everything\");\n        req.addHeader(\"Foo\", \"Bar\");\n\n        assertTrue(req.hasHeader(\"Accept\"));\n        assertTrue(req.hasHeader(\"ACCEpt\"));\n        assertEquals(\"Something, Everything\", req.header(\"accept\"));\n        assertTrue(req.hasHeader(\"fOO\"));\n        assertEquals(\"Bar\", req.header(\"foo\"));\n\n        List<String> accept = req.headers(\"accept\");\n        assertEquals(2, accept.size());\n        assertEquals(\"Something\", accept.get(0));\n        assertEquals(\"Everything\", accept.get(1));\n\n        Map<String, List<String>> headers = req.multiHeaders();\n        assertEquals(accept, headers.get(\"Accept\"));\n        assertEquals(\"Bar\", headers.get(\"Foo\").get(0));\n\n        assertTrue(req.hasHeader(\"Accept\"));\n        assertTrue(req.hasHeaderWithValue(\"accept\", \"Something\"));\n        assertTrue(req.hasHeaderWithValue(\"accept\", \"Everything\"));\n        assertFalse(req.hasHeaderWithValue(\"accept\", \"Something for nothing\"));\n\n        req.removeHeader(\"accept\");\n        headers = req.multiHeaders();\n        assertEquals(\"Bar\", headers.get(\"Foo\").get(0));\n        assertFalse(req.hasHeader(\"Accept\"));\n        assertNull(headers.get(\"Accept\"));\n    }\n\n    @Test void responseHeadersPreserveInsertOrder() throws IOException {\n        // linkedhashmap preserves the response header order\n        Connection.Response res = new HttpConnection.Response();\n        res.addHeader(\"5\", \"5\");\n        res.addHeader(\"4\", \"4\");\n        res.addHeader(\"3\", \"3\");\n        res.addHeader(\"2\", \"2\");\n        res.addHeader(\"1\", \"1\");\n\n        String[] expected = {\"5\", \"4\", \"3\", \"2\", \"1\"};\n        int i = 0;\n        for (String key : res.headers().keySet()) {\n            assertEquals(expected[i++], key);\n        }\n\n        assertInstanceOf(LinkedHashMap.class, res.headers());\n    }\n\n    @Test public void ignoresEmptySetCookies() {\n        // prep http response header map\n        Map<String, List<String>> headers = new HashMap<>();\n        headers.put(\"Set-Cookie\", Collections.emptyList());\n        HttpConnection.Response res = new HttpConnection.Response();\n        res.processResponseHeaders(headers);\n        assertEquals(0, res.cookies().size());\n    }\n\n    @Test public void connectWithUrl() throws MalformedURLException {\n        Connection con = HttpConnection.connect(new URL(\"http://example.com\"));\n        assertEquals(\"http://example.com\", con.request().url().toExternalForm());\n    }\n\n    @Test public void throwsOnMalformedUrl() {\n        assertThrows(IllegalArgumentException.class, () -> HttpConnection.connect(\"bzzt\"));\n    }\n\n    @Test public void userAgent() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        assertEquals(HttpConnection.DEFAULT_UA, con.request().header(\"User-Agent\"));\n        con.userAgent(\"Mozilla\");\n        assertEquals(\"Mozilla\", con.request().header(\"User-Agent\"));\n    }\n\n    @Test public void timeout() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        assertEquals(30 * 1000, con.request().timeout());\n        con.timeout(1000);\n        assertEquals(1000, con.request().timeout());\n    }\n\n    @Test public void referrer() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        con.referrer(\"http://foo.com\");\n        assertEquals(\"http://foo.com\", con.request().header(\"Referer\"));\n    }\n\n    @Test public void method() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        assertEquals(Connection.Method.GET, con.request().method());\n        con.method(Connection.Method.POST);\n        assertEquals(Connection.Method.POST, con.request().method());\n    }\n\n    @Test public void throwsOnOddData() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection con = HttpConnection.connect(\"http://example.com/\");\n            con.data(\"Name\", \"val\", \"what\");\n        });\n    }\n\n    @Test public void data() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        con.data(\"Name\", \"Val\", \"Foo\", \"bar\");\n        Collection<Connection.KeyVal> values = con.request().data();\n        Object[] data =  values.toArray();\n        Connection.KeyVal one = (Connection.KeyVal) data[0];\n        Connection.KeyVal two = (Connection.KeyVal) data[1];\n        assertEquals(\"Name\", one.key());\n        assertEquals(\"Val\", one.value());\n        assertEquals(\"Foo\", two.key());\n        assertEquals(\"bar\", two.value());\n    }\n\n    @Test public void cookie() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        con.cookie(\"Name\", \"Val\");\n        assertEquals(\"Val\", con.request().cookie(\"Name\"));\n    }\n\n    @Test public void inputStream() {\n        Connection.KeyVal kv = HttpConnection.KeyVal.create(\"file\", \"thumb.jpg\", ParseTest.inputStreamFrom(\"Check\"));\n        assertEquals(\"file\", kv.key());\n        assertEquals(\"thumb.jpg\", kv.value());\n        assertTrue(kv.hasInputStream());\n\n        kv = HttpConnection.KeyVal.create(\"one\", \"two\");\n        assertEquals(\"one\", kv.key());\n        assertEquals(\"two\", kv.value());\n        assertFalse(kv.hasInputStream());\n    }\n\n    @Test public void requestBody() {\n        Connection con = HttpConnection.connect(\"http://example.com/\");\n        con.requestBody(\"foo\");\n        assertEquals(\"foo\", con.request().requestBody());\n    }\n\n    @Test public void encodeUrl() throws MalformedURLException {\n        URL url1 = new URL(\"https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag\");\n        URL url2 = new UrlBuilder(url1).build();\n        assertEquals(\"https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag\", url2.toExternalForm());\n    }\n\n    @Test public void encodeUrlSupplementary() throws MalformedURLException {\n        URL url1 = new URL(\"https://example.com/tools/test💩.html\"); // = \"/tools/test\\uD83D\\uDCA9.html\"\n        URL url2 = new UrlBuilder(url1).build();\n        assertEquals(\"https://example.com/tools/test%F0%9F%92%A9.html\", url2.toExternalForm());\n    }\n\n    @Test void encodedUrlDoesntDoubleEncode() throws MalformedURLException {\n        URL url1 = new URL(\"https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag%20ment\");\n        URL url2 = new UrlBuilder(url1).build();\n        URL url3 = new UrlBuilder(url2).build();\n        assertEquals(\"https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag%20ment\", url2.toExternalForm());\n        assertEquals(\"https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag%20ment\", url3.toExternalForm());\n    }\n\n    @Test void urlPathIsPreservedDoesntDoubleEncode() throws MalformedURLException {\n        URL url1 = new URL(\"https://test.com/[foo] bar+/%5BOne%5D?q=white space#frag ment\");\n        URL url2 = new UrlBuilder(url1).build();\n        URL url3 = new UrlBuilder(url2).build();\n        assertEquals(\"https://test.com/%5Bfoo%5D%20bar+/%5BOne%5D?q=white+space#frag%20ment\", url2.toExternalForm());\n        assertEquals(\"https://test.com/%5Bfoo%5D%20bar+/%5BOne%5D?q=white+space#frag%20ment\", url3.toExternalForm());\n    }\n\n    @Test void connectToEncodedUrl() {\n        Connection connect = Jsoup.connect(\"https://example.com/a%20b%20c?query+string\");\n        URL url = connect.request().url();\n        assertEquals(\"https://example.com/a%20b%20c?query+string\", url.toExternalForm());\n    }\n\n    @Test void encodedUrlPathIsPreserved() {\n        // https://github.com/jhy/jsoup/issues/1952\n        Connection connect = Jsoup.connect(\"https://example.com/%2B32\");\n        URL url = connect.request().url();\n        assertEquals(\"https://example.com/%2B32\", url.toExternalForm());\n    }\n\n    @Test void urlPathPlusIsPreserved() {\n        // https://github.com/jhy/jsoup/issues/1952\n        Connection connect = Jsoup.connect(\"https://example.com/123+456\");\n        URL url = connect.request().url();\n        assertEquals(\"https://example.com/123+456\", url.toExternalForm());\n    }\n\n    @Test public void noUrlThrowsValidationError() throws IOException {\n        HttpConnection con = new HttpConnection();\n        boolean threw = false;\n        try {\n            con.execute();\n        } catch (IllegalArgumentException e) {\n            threw = true;\n            assertEquals(\"URL not set. Make sure to call #url(...) before executing the request.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test public void handlesHeaderEncodingOnRequest() {\n        Connection.Request req = new HttpConnection.Request();\n        req.addHeader(\"xxx\", \"é\");\n    }\n\n    @Test public void supportsInternationalDomainNames() throws MalformedURLException {\n        String idn = \"https://www.测试.测试/foo.html?bar\";\n        String puny = \"https://www.xn--0zwm56d.xn--0zwm56d/foo.html?bar\";\n\n        Connection con = Jsoup.connect(idn);\n        assertEquals(puny, con.request().url().toExternalForm());\n\n        HttpConnection.Request req = new HttpConnection.Request();\n        req.url(new URL(idn));\n        assertEquals(puny, req.url().toExternalForm());\n    }\n\n    @Test void supportsIdnWithPort() throws MalformedURLException {\n        String idn = \"https://www.测试.测试:9001/foo.html?bar\";\n        String puny = \"https://www.xn--0zwm56d.xn--0zwm56d:9001/foo.html?bar\";\n\n        Connection con = Jsoup.connect(idn);\n        assertEquals(puny, con.request().url().toExternalForm());\n\n        HttpConnection.Request req = new HttpConnection.Request();\n        req.url(new URL(idn));\n        assertEquals(puny, req.url().toExternalForm());\n    }\n\n    @Test public void validationErrorsOnExecute() throws IOException {\n        Connection con = new HttpConnection();\n        boolean urlThrew = false;\n        try {\n            con.execute();\n        } catch (IllegalArgumentException e) {\n            urlThrew = e.getMessage().contains(\"URL\");\n        }\n        assertTrue(urlThrew);\n    }\n\n    @Test void testMalformedException() {\n        boolean threw = false;\n        try {\n            Jsoup.connect(\"jsoup.org/test\");\n        } catch (IllegalArgumentException e) {\n            threw = true;\n            assertEquals(\"The supplied URL, 'jsoup.org/test', is malformed. Make sure it is an absolute URL, and starts with 'http://' or 'https://'. See https://jsoup.org/cookbook/extracting-data/working-with-urls\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void setHeaderWithUnicodeValue() {\n        Connection connect = Jsoup.connect(\"https://example.com\");\n        String value = \"/foo/我的\";\n        connect.header(\"Key\", value);\n\n        String actual = connect.request().header(\"Key\");\n        assertEquals(value, actual);\n    }\n\n    @Test void setAuth() throws MalformedURLException {\n        Connection con = Jsoup.newSession();\n\n        assertNull(con.request().auth());\n\n        RequestAuthenticator auth1 = new RequestAuthenticator() {\n            @Override public PasswordAuthentication authenticate(Context auth) {\n                return auth.credentials(\"foo\", \"bar\");\n            }\n        };\n\n        RequestAuthenticator auth2 = new RequestAuthenticator() {\n            @Override public PasswordAuthentication authenticate(Context auth) {\n                return auth.credentials(\"qux\", \"baz\");\n            }\n        };\n\n        con.auth(auth1);\n        assertSame(con.request().auth(), auth1);\n\n        con.auth(auth2);\n        assertSame(con.request().auth(), auth2);\n\n        con.request().auth(auth1);\n        assertSame(con.request().auth(), auth1);\n\n        PasswordAuthentication creds = auth1.authenticate(\n            new RequestAuthenticator.Context(new URL(\"http://example.com\"), Authenticator.RequestorType.SERVER, \"Realm\"));\n        assertNotNull(creds);\n        assertEquals(\"foo\", creds.getUserName());\n        assertEquals(\"bar\", new String(creds.getPassword()));\n    }\n\n    /* Tests for fixHeaderEncoding. We are handling two cases when a server sends a header in UTF8. The JVM will decode\n    that in 8859 (per the RFC) and we'll get mojibake, and so we try to fix it. On Android, will be decoded correctly,\n    so should not be modified. */\n    static String mojibake(String input) {\n        // simulate mojibake by encoding in UTF-8 and decoding in ISO-8859-1\n        return new String(input.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"search.php?moji=我的\", \"latin=café\", \"🍕\", \"ascii\"})\n    void fixesHeaderEncodingIfRequired(String input) {\n        // if the input was mojibaked, we fix it; otherwise is passed\n        // https://github.com/jhy/jsoup/issues/2011\n        assertEquals(input, fixHeaderEncoding(input));\n        assertEquals(input, fixHeaderEncoding(mojibake(input)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/RegexTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.select.QueryParser;\nimport org.jsoup.select.Selector;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class RegexTest {\n\n    private boolean originalUseRe2j; // track original setting\n\n    @BeforeEach\n    void setUp() {\n        originalUseRe2j = Regex.wantsRe2j();\n    }\n\n    @AfterEach\n    void tearDown() {\n        Regex.wantsRe2j(originalUseRe2j); // restore original setting\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = {false, true})\n    void testRegexDelegates(boolean useRe2j) {\n        Regex.wantsRe2j(useRe2j);\n        assertEquals(Regex.usingRe2j(), useRe2j);\n        String pattern = \"(\\\\d+)\";\n        String input = \"12345\";\n\n        Regex regex = Regex.compile(pattern);\n        Regex.Matcher matcher = regex.matcher(input);\n        assertTrue(matcher.find());\n    }\n\n    @Test\n    void jdkSupportsBackreferenceMatches() {\n        Regex.wantsRe2j(false);\n        String pattern = \"(\\\\w+)\\\\s+\\\\1\"; // backreference to group 1\n        String input = \"hello hello\";\n\n        Regex regex = Regex.compile(pattern);\n        Regex.Matcher matcher = regex.matcher(input);\n        assertTrue(matcher.find());\n    }\n\n    @Test\n    void re2jRejectsBackreferenceThrows() {\n        Regex.wantsRe2j(true);\n        String pattern = \"(\\\\w+)\\\\s+\\\\1\"; // backreference unsupported by RE2J\n\n        assertThrows(ValidationException.class, () -> Regex.compile(pattern));\n        // and not the rej2 PatternSyntaxException\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = {false, true})\n    void queryParserThrowsSelectorExceptionOnMalformedRegex(boolean useRe2j) {\n        Regex.wantsRe2j(useRe2j);\n        String query = \"[attr~=(unclosed]\";\n\n        boolean threw = false;\n        try {\n            QueryParser.parse(query);\n        } catch (Selector.SelectorParseException e) {\n            threw = true;\n            assertTrue(e.getMessage().contains(\"Pattern syntax error\"));\n        }\n        assertTrue(threw);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/ValidateTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n@SuppressWarnings(\"deprecation\") // keeps tests for ensureNotNull\npublic class ValidateTest {\n    @Test\n    public void testNotNull() {\n        Validate.notNull(\"foo\");\n        boolean threw = false;\n        try {\n            Validate.notNull(null);\n        } catch (IllegalArgumentException e) {\n            threw = true;\n        }\n        Assertions.assertTrue(threw);\n    }\n\n    @Test\n    void stacktraceFiltersOutValidateClass() {\n        boolean threw = false;\n        try {\n            Validate.notNull(null);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Object must not be null\", e.getMessage());\n            StackTraceElement[] stackTrace = e.getStackTrace();\n            for (StackTraceElement trace : stackTrace) {\n                assertNotEquals(trace.getClassName(), Validate.class.getName());\n            }\n            assertTrue(stackTrace.length >= 1);\n        }\n        Assertions.assertTrue(threw);\n    }\n\n    @Test\n    void nonnullParam() {\n        boolean threw = true;\n        try {\n            Validate.notNullParam(null, \"foo\");\n        } catch (ValidationException e) {\n            assertEquals(\"The parameter 'foo' must not be null.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testWtf() {\n        boolean threw = false;\n        try {\n            Validate.wtf(\"Unexpected state reached\");\n        } catch (IllegalStateException e) {\n            threw = true;\n            assertEquals(\"Unexpected state reached\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testEnsureNotNull() {\n        // Test with a non-null object\n        Object obj = new Object();\n        assertSame(obj, Validate.ensureNotNull(obj));\n\n        // Test with a null object\n        boolean threw = false;\n        try {\n            Validate.ensureNotNull(null);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Object must not be null\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testEnsureNotNullWithMessage() {\n        // Test with a non-null object\n        Object obj = new Object();\n        assertSame(obj, Validate.ensureNotNull(obj, \"Object must not be null\"));\n\n        // Test with a null object\n        boolean threw = false;\n        try {\n            Validate.ensureNotNull(null, \"Custom error message\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Custom error message\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testEnsureNotNullWithFormattedMessage() {\n        // Test with a non-null object\n        Object obj = new Object();\n        assertSame(obj, Validate.ensureNotNull(obj, \"Object must not be null: %s\", \"additional info\"));\n\n        // Test with a null object\n        boolean threw = false;\n        try {\n            Validate.ensureNotNull(null, \"Object must not be null: %s\", \"additional info\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Object must not be null: additional info\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void expectNotNull() {\n        String foo = \"Foo\";\n        String foo2 = Validate.expectNotNull(foo);\n        assertSame(foo, foo2);\n\n        // Test with a null object\n        String bar = null;\n        boolean threw = false;\n        try {\n            Validate.expectNotNull(bar);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Object must not be null\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testNotNullParam() {\n        // Test with a non-null object\n        Object obj = new Object();\n        Validate.notNullParam(obj, \"param\");\n\n        // Test with a null object\n        boolean threw = false;\n        try {\n            Validate.notNullParam(null, \"param\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"The parameter 'param' must not be null.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testNotEmpty() {\n        // Test with a non-empty string\n        String str = \"foo\";\n        Validate.notEmpty(str);\n\n        // Test with an empty string\n        boolean threw = false;\n        try {\n            Validate.notEmpty(\"\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"String must not be empty\", e.getMessage());\n        }\n        assertTrue(threw);\n\n        // Test with a null string\n        threw = false;\n        try {\n            Validate.notEmpty(null);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"String must not be empty\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testIsTrue() {\n        // Test with a true value\n        Validate.isTrue(true);\n\n        // Test with a false value\n        boolean threw = false;\n        try {\n            Validate.isTrue(false);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Must be true\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testIsFalse() {\n        // Test with a false value\n        Validate.isFalse(false);\n\n        // Test with a true value\n        boolean threw = false;\n        try {\n            Validate.isFalse(true);\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Must be false\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testAssertFail() {\n        boolean result = false;\n        boolean threw = false;\n        try {\n            result = Validate.assertFail(\"This should fail\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"This should fail\", e.getMessage());\n        }\n        assertTrue(threw);\n        assertFalse(result);\n    }\n\n    @Test\n    public void testNotEmptyParam() {\n        // Test with a non-empty string\n        Validate.notEmptyParam(\"foo\", \"param\");\n\n        // Test with an empty string\n        boolean threw = false;\n        try {\n            Validate.notEmptyParam(\"\", \"param\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"The 'param' parameter must not be empty.\", e.getMessage());\n        }\n        assertTrue(threw);\n\n        // Test with a null string\n        threw = false;\n        try {\n            Validate.notEmptyParam(null, \"param\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"The 'param' parameter must not be empty.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testNoNullElementsWithMessage() {\n        // Test with an array with no null elements\n        Object[] array = {new Object(), new Object()};\n        Validate.noNullElements(array, \"Custom error message\");\n\n        // Test with an array containing a null element\n        boolean threw = false;\n        try {\n            Validate.noNullElements(new Object[]{new Object(), null}, \"Custom error message\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Custom error message\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testNotEmptyWithMessage() {\n        // Test with a non-empty string\n        Validate.notEmpty(\"foo\", \"Custom error message\");\n\n        // Test with an empty string\n        boolean threw = false;\n        try {\n            Validate.notEmpty(\"\", \"Custom error message\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Custom error message\", e.getMessage());\n        }\n        assertTrue(threw);\n\n        // Test with a null string\n        threw = false;\n        try {\n            Validate.notEmpty(null, \"Custom error message\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"Custom error message\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/helper/W3CDomTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.xml.sax.InputSource;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.transform.OutputKeys;\nimport javax.xml.xpath.XPathConstants;\nimport javax.xml.xpath.XPathExpression;\nimport javax.xml.xpath.XPathExpressionException;\nimport javax.xml.xpath.XPathFactory;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.TextUtil.normalizeSpaces;\nimport static org.jsoup.nodes.Document.OutputSettings.Syntax.xml;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class W3CDomTest {\n\n    private static Document parseXml(String xml, boolean nameSpaceAware) {\n        try {\n            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n            factory.setNamespaceAware(nameSpaceAware);\n            DocumentBuilder builder = factory.newDocumentBuilder();\n            builder.setEntityResolver((publicId, systemId) -> {\n                if (systemId.contains(\"about:legacy-compat\")) { // <!doctype html>\n                    return new InputSource(new StringReader(\"\"));\n                } else {\n                    return null;\n                }\n            });\n            Document dom = builder.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));\n            dom.normalizeDocument();\n            return dom;\n        } catch (Exception e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    @Test\n    public void simpleConversion() {\n        String html = \"<html><head><title>W3c</title></head><body><p class='one' id=12>Text</p><!-- comment --><invalid>What<script>alert('!')\";\n        org.jsoup.nodes.Document doc = Jsoup.parse(html);\n\n        W3CDom w3c = new W3CDom();\n        Document wDoc = w3c.fromJsoup(doc);\n        NodeList meta = wDoc.getElementsByTagName(\"META\");\n        assertEquals(0, meta.getLength());\n\n        String out = W3CDom.asString(wDoc, W3CDom.OutputXml());\n        String expected = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><title>W3c</title></head><body><p class=\\\"one\\\" id=\\\"12\\\">Text</p><!-- comment --><invalid>What<script>alert('!')</script></invalid></body></html>\";\n        assertEquals(expected, TextUtil.stripNewlines(out));\n\n        Document roundTrip = parseXml(out, true);\n        assertEquals(\"Text\", roundTrip.getElementsByTagName(\"p\").item(0).getTextContent());\n\n        // check we can set properties\n        Map<String, String> properties = W3CDom.OutputXml();\n        properties.put(OutputKeys.INDENT, \"yes\");\n        String furtherOut = W3CDom.asString(wDoc, properties);\n        assertTrue(furtherOut.length() > out.length()); // wanted to assert formatting, but actual indentation is platform specific so breaks in CI\n        String furtherExpected =\n            \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><title>W3c</title></head><body><p class=\\\"one\\\" id=\\\"12\\\">Text</p><!-- comment --><invalid>What<script>alert('!')</script></invalid></body></html>\";\n        assertEquals(furtherExpected, TextUtil.stripNewlines(furtherOut)); // on windows, DOM will write newlines as \\r\\n\n    }\n\n    @Test\n    public void namespacePreservation() throws IOException {\n        File in = ParseTest.getFile(\"/htmltests/namespaces.xhtml\");\n        org.jsoup.nodes.Document jsoupDoc;\n        jsoupDoc = Jsoup.parse(in, \"UTF-8\", \"\", Parser.xmlParser());\n\n        Document doc;\n        org.jsoup.helper.W3CDom jDom = new org.jsoup.helper.W3CDom();\n        doc = jDom.fromJsoup(jsoupDoc);\n\n        Node htmlEl = doc.getChildNodes().item(0);\n        assertEquals(\"http://www.w3.org/1999/xhtml\", htmlEl.getNamespaceURI());\n        assertEquals(\"html\", htmlEl.getLocalName());\n        assertEquals(\"html\", htmlEl.getNodeName());\n\n        // inherits default namespace\n        Node head = htmlEl.getFirstChild().getNextSibling();\n        assertEquals(\"http://www.w3.org/1999/xhtml\", head.getNamespaceURI());\n        assertEquals(\"head\", head.getLocalName());\n        assertEquals(\"head\", head.getNodeName());\n\n        Node epubTitle = htmlEl.getChildNodes().item(3).getChildNodes().item(3);\n        assertEquals(\"Check\", epubTitle.getTextContent());\n        assertEquals(\"http://www.idpf.org/2007/ops\", epubTitle.getNamespaceURI());\n        assertEquals(\"title\", epubTitle.getLocalName());\n        assertEquals(\"epub:title\", epubTitle.getNodeName());\n\n        Node xSection = epubTitle.getNextSibling().getNextSibling();\n        assertEquals(\"urn:test\", xSection.getNamespaceURI());\n        assertEquals(\"section\", xSection.getLocalName());\n        assertEquals(\"x:section\", xSection.getNodeName());\n\n        // https://github.com/jhy/jsoup/issues/977\n        // does not keep last set namespace\n        Node svg = xSection.getNextSibling().getNextSibling();\n        assertEquals(\"http://www.w3.org/2000/svg\", svg.getNamespaceURI());\n        assertEquals(\"svg\", svg.getLocalName());\n        assertEquals(\"svg\", svg.getNodeName());\n\n        Node path = svg.getChildNodes().item(1);\n        assertEquals(\"http://www.w3.org/2000/svg\", path.getNamespaceURI());\n        assertEquals(\"path\", path.getLocalName());\n        assertEquals(\"path\", path.getNodeName());\n\n        Node clip = path.getChildNodes().item(1);\n        assertEquals(\"http://example.com/clip\", clip.getNamespaceURI());\n        assertEquals(\"clip\", clip.getLocalName());\n        assertEquals(\"clip\", clip.getNodeName());\n        assertEquals(\"456\", clip.getTextContent());\n\n        Node picture = svg.getNextSibling().getNextSibling();\n        assertEquals(\"http://www.w3.org/1999/xhtml\", picture.getNamespaceURI());\n        assertEquals(\"picture\", picture.getLocalName());\n        assertEquals(\"picture\", picture.getNodeName());\n\n        Node img = picture.getFirstChild();\n        assertEquals(\"http://www.w3.org/1999/xhtml\", img.getNamespaceURI());\n        assertEquals(\"img\", img.getLocalName());\n        assertEquals(\"img\", img.getNodeName());\n    }\n\n    @Test\n    public void handlesInvalidAttributeNames() {\n        String html = \"<html><head></head><body style=\\\"color: red\\\" \\\" name\\\"></body></html>\";\n        org.jsoup.nodes.Document jsoupDoc;\n        jsoupDoc = Jsoup.parse(html);\n        Element body = jsoupDoc.select(\"body\").first();\n        assertTrue(body.hasAttr(\"\\\"\")); // actually an attribute with key '\"'. Correct per HTML5 spec, but w3c xml dom doesn't dig it\n        assertTrue(body.hasAttr(\"name\\\"\"));\n\n        Document w3Doc = W3CDom.convert(jsoupDoc);\n        String xml = W3CDom.asString(w3Doc, W3CDom.OutputXml());\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body _=\\\"\\\" name_=\\\"\\\" style=\\\"color: red\\\"/></html>\", xml);\n    }\n\n    @Test\n    public void htmlInputDocMaintainsHtmlAttributeNames() {\n        String html = \"<!DOCTYPE html><html><head></head><body><p hành=\\\"1\\\" hình=\\\"2\\\">unicode attr names</p></body></html>\";\n        org.jsoup.nodes.Document jsoupDoc;\n        jsoupDoc = Jsoup.parse(html);\n\n        Document w3Doc = W3CDom.convert(jsoupDoc);\n        String out = W3CDom.asString(w3Doc, W3CDom.OutputHtml());\n        String expected = \"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body><p hành=\\\"1\\\" hình=\\\"2\\\">unicode attr names</p></body></html>\";\n        assertEquals(expected, TextUtil.stripNewlines(out));\n    }\n\n    @Test\n    public void xmlInputDocMaintainsHtmlAttributeNames() {\n        String html = \"<!DOCTYPE html><html><head></head><body><p hành=\\\"1\\\" hình=\\\"2\\\">unicode attr names coerced</p></body></html>\";\n        org.jsoup.nodes.Document jsoupDoc;\n        jsoupDoc = Jsoup.parse(html);\n        jsoupDoc.outputSettings().syntax(xml);\n\n        Document w3Doc = W3CDom.convert(jsoupDoc);\n        String out = W3CDom.asString(w3Doc, W3CDom.OutputHtml());\n        String expected = \"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body><p h_nh=\\\"2\\\">unicode attr names coerced</p></body></html>\";\n        assertEquals(expected, TextUtil.stripNewlines(out));\n    }\n\n    @Test\n    public void handlesInvalidTagAsText() {\n        org.jsoup.nodes.Document jsoup = Jsoup.parse(\"<インセンティブで高収入！>Text <p>More</p>\");\n\n        Document w3Doc = W3CDom.convert(jsoup);\n        String xml = W3CDom.asString(w3Doc, W3CDom.OutputXml());\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body>&lt;インセンティブで高収入！&gt;Text <p>More</p></body></html>\", xml);\n    }\n\n    @Test void handlesHtmlElsWithLt() {\n        // In HTML, elements can be named \"foo<bar\" (<foo<bar>). Test that we can convert to W3C, that we can HTML parse our HTML serial, XML parse our XML serial, and W3C XML parse the XML serial and the W3C serial\n        // And similarly attributes may have \"<\" in their name\n        // https://github.com/jhy/jsoup/issues/2259\n        String input = \"<foo<bar attr<name=\\\"123\\\"><b>Text</b></foo<bar>\";\n        String xmlExpect = \"<foo_bar attr_name=\\\"123\\\"><b>Text</b></foo_bar>\"; // rewrites < to _ in el and attr\n\n        // html round trips\n        org.jsoup.nodes.Document htmlDoc = Jsoup.parse(input);\n        String htmlSerial = htmlDoc.body().html();\n        assertEquals(input, normalizeSpaces(htmlSerial)); // same as input\n        Element htmlRound = Jsoup.parse(htmlSerial).body();\n        assertTrue(htmlDoc.body().hasSameValue(htmlRound));\n\n        // xml round trips\n        htmlDoc.outputSettings().syntax(xml);\n        String asXml = htmlDoc.body().html();\n        assertEquals(xmlExpect, normalizeSpaces(asXml)); // <foo<bar> -> <foo_bar>\n        org.jsoup.nodes.Document xmlDoc = Jsoup.parse(asXml);\n        String xmlSerial = xmlDoc.body().html();\n        assertEquals(xmlExpect, normalizeSpaces(xmlSerial)); // same as xmlExpect\n        Element xmlRound = Jsoup.parse(xmlSerial).body();\n        assertTrue(xmlDoc.body().hasSameValue(xmlRound));\n\n        // Can W3C parse that XML\n        Document w3cXml = parseXml(asXml, true);\n        NodeList w3cXmlNodes = w3cXml.getElementsByTagName(\"foo_bar\");\n        assertEquals(1, w3cXmlNodes.getLength());\n        assertEquals(\"123\", w3cXmlNodes.item(0).getAttributes().getNamedItem(\"attr_name\").getTextContent());\n\n        // Can convert to W3C\n        Document w3cDoc = W3CDom.convert(htmlDoc);\n        NodeList w3cNodes = w3cDoc.getElementsByTagName(\"foo_bar\");\n        assertEquals(1, w3cNodes.getLength());\n        assertEquals(\"123\", w3cNodes.item(0).getAttributes().getNamedItem(\"attr_name\").getTextContent());\n    }\n\n    @Test\n    public void canConvertToCustomDocument() throws ParserConfigurationException {\n        org.jsoup.nodes.Document document = Jsoup.parse(\"<html><div></div></html>\");\n\n        DocumentBuilderFactory localDocumentBuilderFactory = DocumentBuilderFactory.newInstance();\n        Document customDocumentResult = localDocumentBuilderFactory.newDocumentBuilder().newDocument();\n\n        W3CDom w3cDom = new W3CDom();\n        w3cDom.convert(document, customDocumentResult);\n\n        String html = W3CDom.asString(customDocumentResult, W3CDom.OutputHtml());\n        assertEquals(\"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body><div></div></body></html>\", html);\n    }\n\n    @Test\n    public void treatsUndeclaredNamespaceAsLocalName() {\n        String html = \"<fb:like>One</fb:like>\";\n        org.jsoup.nodes.Document doc = Jsoup.parse(html);\n\n        Document w3Doc = new W3CDom().fromJsoup(doc);\n        Node htmlEl = w3Doc.getFirstChild();\n\n        assertEquals(\"http://www.w3.org/1999/xhtml\", htmlEl.getNamespaceURI());\n        assertEquals(\"html\", htmlEl.getLocalName());\n        assertEquals(\"html\", htmlEl.getNodeName());\n\n        Node fb = htmlEl.getFirstChild().getNextSibling().getFirstChild();\n        assertEquals(\"http://www.w3.org/1999/xhtml\", fb.getNamespaceURI());\n        assertEquals(\"like\", fb.getLocalName());\n        assertEquals(\"fb:like\", fb.getNodeName());\n    }\n\n    @Test\n    public void xmlnsXpathTest() throws XPathExpressionException {\n        W3CDom w3c = new W3CDom();\n        String html = \"<html><body><div>hello</div></body></html>\";\n        Document dom = w3c.fromJsoup(Jsoup.parse(html));\n        NodeList nodeList = xpath(dom, \"//*[local-name()=\\\"body\\\"]\");// namespace aware; HTML namespace is default\n        assertEquals(\"div\", nodeList.item(0).getLocalName());\n\n        // default output is namespace aware, so query needs to be as well\n        html = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        dom = w3c.fromJsoup(Jsoup.parse(html));\n        nodeList = xpath(dom, \"//body\");\n        assertNull(nodeList); // no matches\n\n        dom = w3c.fromJsoup(Jsoup.parse(html));\n        nodeList = xpath(dom, \"//*[local-name()=\\\"body\\\"]\");\n        assertNotNull(nodeList);\n        assertEquals(1, nodeList.getLength());\n        assertEquals(\"div\", nodeList.item(0).getLocalName());\n        assertEquals(\"http://www.w3.org/1999/xhtml\", nodeList.item(0).getNamespaceURI());\n        assertNull(nodeList.item(0).getPrefix());\n\n        // get rid of the name space awareness\n        String xml = w3c.asString(dom);\n        dom = parseXml(xml, false);\n        Node item = (Node) xpath(dom, \"//body\");\n        assertEquals(\"body\", item.getNodeName());\n        assertNull(item.getNamespaceURI());\n        assertNull(item.getPrefix());\n\n        // put back, will get zero\n        dom = parseXml(xml, true);\n        nodeList = xpath(dom, \"//body\");\n        assertNull(nodeList);\n    }\n\n    @Test\n    public void xhtmlNoNamespace() throws XPathExpressionException {\n        W3CDom w3c = new W3CDom();\n        String html = \"<html><body><div>hello</div></body></html>\";\n        w3c.namespaceAware(false);\n        Document dom = w3c.fromJsoup(Jsoup.parse(html));\n        NodeList nodeList = xpath(dom, \"//body\");// no namespace\n        assertEquals(1, nodeList.getLength());\n        assertEquals(\"div\", nodeList.item(0).getLocalName());\n    }\n\n    @Test\n    void canDisableNamespaces() throws XPathExpressionException {\n        W3CDom w3c = new W3CDom();\n        assertTrue(w3c.namespaceAware());\n\n        w3c.namespaceAware(false);\n        assertFalse(w3c.namespaceAware());\n\n        String html = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        Document dom = w3c.fromJsoup(Jsoup.parse(html));\n        NodeList nodeList = xpath(dom, \"//body\");// no ns, so needs no prefix\n        assertEquals(\"div\", nodeList.item(0).getLocalName());\n    }\n\n    private NodeList xpath(Document w3cDoc, String query) throws XPathExpressionException {\n        XPathExpression xpath = XPathFactory.newInstance().newXPath().compile(query);\n        return ((NodeList) xpath.evaluate(w3cDoc, XPathConstants.NODE));\n    }\n\n    @Test\n    public void testRoundTripDoctype() {\n        // because we have Saxon on the test classpath, the transformer will change to that, and so case may change (e.g. Java base is META, Saxon is meta for HTML)\n        String base = \"<!DOCTYPE html><p>One</p>\";\n        assertEqualsIgnoreCase(\"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body><p>One</p></body></html>\", output(base, true));\n        assertEqualsIgnoreCase(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body><p>One</p></body></html>\", output(base, false));\n\n        String publicDoc = \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\";\n        assertEqualsIgnoreCase(\"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body></body></html>\", output(publicDoc, true));\n        // different impls will have different XML formatting. OpenJDK 13 default gives this: <body /> but others have <body/>, so just check start\n        assertTrue(output(publicDoc, false).startsWith(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><!DOCTYPE html PUBLIC\"));\n\n        String systemDoc = \"<!DOCTYPE html SYSTEM \\\"exampledtdfile.dtd\\\">\";\n        assertEqualsIgnoreCase(\"<!DOCTYPE html SYSTEM \\\"exampledtdfile.dtd\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body></body></html>\", output(systemDoc, true));\n        assertEqualsIgnoreCase(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><!DOCTYPE html SYSTEM \\\"exampledtdfile.dtd\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body/></html>\", output(systemDoc, false));\n\n        String legacyDoc = \"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\">\";\n        assertEqualsIgnoreCase(\"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body></body></html>\", output(legacyDoc, true));\n        assertEqualsIgnoreCase(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\"><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body/></html>\", output(legacyDoc, false));\n\n        String noDoctype = \"<p>One</p>\";\n        assertEqualsIgnoreCase(\"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head><META http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\"></head><body><p>One</p></body></html>\", output(noDoctype, true));\n        assertEqualsIgnoreCase(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body><p>One</p></body></html>\", output(noDoctype, false));\n    }\n\n    private String output(String in, boolean modeHtml) {\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(in);\n        Document w3c = W3CDom.convert(jdoc);\n\n        Map<String, String> properties = modeHtml ? W3CDom.OutputHtml() : W3CDom.OutputXml();\n        return normalizeSpaces(W3CDom.asString(w3c, properties));\n    }\n\n    private void assertEqualsIgnoreCase(String want, String have) {\n        assertEquals(want.toLowerCase(Locale.ROOT), have.toLowerCase(Locale.ROOT));\n    }\n\n\n    @Test\n    public void canOutputHtmlWithoutNamespace() {\n        String html = \"<p>One</p>\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        W3CDom w3c = new W3CDom();\n        w3c.namespaceAware(false);\n\n        String asHtml = W3CDom.asString(w3c.fromJsoup(jdoc), W3CDom.OutputHtml());\n        String asXtml = W3CDom.asString(w3c.fromJsoup(jdoc), W3CDom.OutputXml());\n        assertEqualsIgnoreCase(\n            \"<html><head><meta http-equiv=\\\"content-type\\\" content=\\\"text/html; charset=utf-8\\\"></head><body><p>one</p></body></html>\",\n            asHtml);\n        assertEqualsIgnoreCase(\n            \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html><head/><body><p>One</p></body></html>\",\n            asXtml);\n    }\n\n    @Test public void convertsElementsAndMaintainsSource() {\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(\"<body><div><p>One</div><div><p>Two\");\n        W3CDom w3CDom = new W3CDom();\n        Element jDiv = jdoc.selectFirst(\"div\");\n        assertNotNull(jDiv);\n        Document doc = w3CDom.fromJsoup(jDiv);\n        Node div = w3CDom.contextNode(doc);\n\n        assertEquals(\"div\", div.getLocalName());\n        assertEquals(jDiv, div.getUserData(W3CDom.SourceProperty));\n\n        Node textNode = div.getFirstChild().getFirstChild();\n        assertEquals(\"One\", textNode.getTextContent());\n        assertEquals(Node.TEXT_NODE, textNode.getNodeType());\n\n        org.jsoup.nodes.TextNode jText = (TextNode) jDiv.childNode(0).childNode(0);\n        assertEquals(jText, textNode.getUserData(W3CDom.SourceProperty));\n    }\n    \n    @Test public void canXmlParseCdataNodes() throws XPathExpressionException {\n        String html = \"<p><script>1 && 2</script><style>3 && 4</style> 5 &amp;&amp; 6</p>\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        jdoc.outputSettings().syntax(xml);\n        String xml = jdoc.body().html();\n        assertTrue(xml.contains(\"<script>//<![CDATA[\\n1 && 2\\n//]]></script>\")); // as asserted in ElementTest\n        Document doc = parseXml(xml, false);\n        NodeList list = xpath(doc, \"//script\");\n        assertEquals(2, list.getLength());\n        Node scriptComment = list.item(0); // will be the cdata node\n        assertEquals(\"//\", scriptComment.getTextContent());\n        Node script = list.item(1);\n        assertEquals(\"\\n\" +\n            \"1 && 2\\n\" +\n            \"//\", script.getTextContent());\n\n    }\n\n    @Test public void handlesEmptyDoctype() {\n        String html = \"<!doctype>Foo\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        Document doc = (new W3CDom()).fromJsoup(jdoc);\n        assertNull(doc.getDoctype());\n        assertEquals(\"Foo\", doc.getFirstChild().getTextContent());\n    }\n\n    @Test void testHtmlParseAttributesAreCaseInsensitive() throws IOException {\n        // https://github.com/jhy/jsoup/issues/981\n        String html = \"<html lang=en>\\n\" +\n            \"<body>\\n\" +\n            \"<img src=\\\"firstImage.jpg\\\" alt=\\\"Alt one\\\" />\\n\" +\n            \"<IMG SRC=\\\"secondImage.jpg\\\" AlT=\\\"Alt two\\\" />\\n\" +\n            \"</body>\\n\" +\n            \"</html>\";\n        org.jsoup.nodes.Document jsoupDoc;\n        jsoupDoc = Jsoup.parse(html);\n        org.jsoup.helper.W3CDom jDom = new org.jsoup.helper.W3CDom();\n        Document doc = jDom.fromJsoup(jsoupDoc);\n        org.w3c.dom.Element body = (org.w3c.dom.Element) doc.getDocumentElement().getElementsByTagName(\"body\").item(0);\n        NodeList imgs = body.getElementsByTagName(\"img\");\n        assertEquals(2, imgs.getLength());\n        org.w3c.dom.Element first = (org.w3c.dom.Element) imgs.item(0);\n        assertEquals(first.getAttributes().getLength(), 2);\n        String img1 = first.getAttribute(\"src\");\n        assertEquals(\"firstImage.jpg\", img1);\n        String alt1 = first.getAttribute(\"alt\");\n        assertEquals(\"Alt one\", alt1);\n        org.w3c.dom.Element second = (org.w3c.dom.Element) imgs.item(1);\n        assertEquals(second.getAttributes().getLength(), 2);\n        String img2 = second.getAttribute(\"src\");\n        assertEquals(\"secondImage.jpg\", img2);\n        String alt2 = second.getAttribute(\"alt\");\n        assertEquals(\"Alt two\", alt2);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"parserProvider\")\n    void doesNotExpandEntities(Parser parser) {\n        // Tests that the billion laughs attack doesn't expand entities; also for XXE\n        // Not impacted because jsoup doesn't parse the entities within the doctype, and so won't get to the w3c.\n        // Added to confirm, and catch if that ever changes\n        String billionLaughs = \"<?xml version=\\\"1.0\\\"?>\\n\" +\n            \"<!DOCTYPE lolz [\\n\" +\n            \" <!ENTITY lol \\\"lol\\\">\\n\" +\n            \" <!ENTITY lol1 \\\"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\\\">\\n\" +\n            \"]>\\n\" +\n            \"<html><body><p>&lol1;</p></body></html>\";\n\n        org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(billionLaughs, parser);\n        W3CDom w3cDom = new W3CDom();\n\n        org.w3c.dom.Document w3cDoc = w3cDom.fromJsoup(jsoupDoc);\n        assertNotNull(w3cDoc);\n        // select the p and make sure it's unexpanded\n        NodeList p = w3cDoc.getElementsByTagName(\"p\");\n        assertEquals(1, p.getLength());\n        assertEquals(\"&lol1;\", p.item(0).getTextContent());\n\n        // Check the string\n        String string = W3CDom.asString(w3cDoc, W3CDom.OutputXml());\n        assertFalse(string.contains(\"lololol\"));\n        assertTrue(string.contains(\"&amp;lol1;\"));\n    }\n\n    @Test void undeclaredAttrNamespaceAsString() {\n        // https://github.com/jhy/jsoup/issues/2087\n        W3CDom w3CDom = new W3CDom();\n        String html = \"<html><body><div v-bind:class='test'></div></body></html>\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        org.w3c.dom.Document w3CDoc = w3CDom.fromJsoup(jdoc);\n\n        String xml = w3CDom.asString(w3CDoc);\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body><div xmlns:v-bind=\\\"undefined\\\" v-bind:class=\\\"test\\\"/></body></html>\", xml);\n    }\n\n    @Test void declaredNamespaceIsUsed() {\n        W3CDom w3CDom = new W3CDom();\n        String html = \"<html xmlns:v-bind=\\\"http://example.com\\\"><body><div v-bind:class='test'></div></body></html>\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        org.w3c.dom.Document w3CDoc = w3CDom.fromJsoup(jdoc);\n\n        String xml = w3CDom.asString(w3CDoc);\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\" xmlns:v-bind=\\\"http://example.com\\\"><head/><body><div v-bind:class=\\\"test\\\"/></body></html>\", xml);\n    }\n\n    @Test void nestedElementsWithUndeclaredNamespace() {\n        W3CDom w3CDom = new W3CDom();\n        String html = \"<html><body><div v-bind:class='test'><span v-bind:style='color:red'></span></div></body></html>\";\n        org.jsoup.nodes.Document jdoc = Jsoup.parse(html);\n        org.w3c.dom.Document w3CDoc = w3CDom.fromJsoup(jdoc);\n\n        String xml = w3CDom.asString(w3CDoc);\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><html xmlns=\\\"http://www.w3.org/1999/xhtml\\\"><head/><body><div xmlns:v-bind=\\\"undefined\\\" v-bind:class=\\\"test\\\"><span v-bind:style=\\\"color:red\\\"/></div></body></html>\", xml);\n    }\n\n    private static Stream<Arguments> parserProvider() {\n        return Stream.of(\n            Arguments.of(Parser.htmlParser()),\n            Arguments.of(Parser.xmlParser())\n        );\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/Benchmark.java",
    "content": "package org.jsoup.integration;\n\nimport java.util.Date;\n\n/**\n Does an A/B test on two methods, and prints out how long each took.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class Benchmark {\n    public static void run(Runnable a, Runnable b, int count) {\n        long aMillis;\n        long bMillis;\n\n        print(\"Running test A (x%d)\", count);\n        aMillis = time(a, count);\n        print(\"Running test B\");\n        bMillis = time(b, count);\n\n        print(\"\\nResults:\");\n        print(\"A: %.2fs\", aMillis / 1000f);\n        print(\"B: %.2fs\", bMillis / 1000f);\n        print(\"\\nB ran in %.2f %% time of A\\n\", (bMillis *1f / aMillis * 1f) * 100f);\n    }\n\n    private static long time(Runnable test, int count) {\n        Date start = new Date();\n        for (int i = 0; i < count; i++) {\n            test.run();\n        }\n        Date end = new Date();\n        return end.getTime() - start.getTime();\n    }\n\n    private static void print(String msgFormat, Object... msgParams) {\n        System.out.println(String.format(msgFormat, msgParams));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/ConnectIT.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.integration.servlets.EchoServlet;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.integration.servlets.SlowRider;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.parser.StreamParser;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.BufferedInputStream;\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.net.SocketTimeoutException;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Failsafe integration tests for Connect methods. These take a bit longer to run, so included as Integ, not Unit, tests.\n */\npublic class ConnectIT {\n    @BeforeAll\n    public static void setUp() {\n        TestServer.start();\n        System.setProperty(SharedConstants.UseHttpClient, \"false\"); // use the default UrlConnection. See HttpClientConnectIT for other version\n    }\n\n    // Slow Rider tests.\n    @Test\n    public void canInterruptBodyStringRead() throws InterruptedException {\n        final String[] body = new String[1];\n        Thread runner = new Thread(() -> {\n            try {\n                Connection.Response res = Jsoup.connect(SlowRider.Url)\n                    .timeout(15 * 1000)\n                    .execute();\n                body[0] = res.body();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n\n        });\n\n        runner.start();\n        Thread.sleep(1000 * 3);\n        runner.interrupt();\n        assertTrue(runner.isInterrupted());\n        runner.join();\n\n        assertTrue(body[0].length() > 0);\n        assertTrue(body[0].contains(\"<p>Are you still there?\"));\n    }\n\n    @Test\n    public void canInterruptDocumentRead() throws InterruptedException {\n        long start = System.currentTimeMillis();\n        final String[] body = new String[1];\n        Thread runner = new Thread(() -> {\n            try {\n                Connection.Response res = Jsoup.connect(SlowRider.Url)\n                    .timeout(15 * 1000)\n                    .execute();\n                body[0] = res.parse().text();\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            }\n\n        });\n\n        runner.start();\n        Thread.sleep(3 * 1000);\n        runner.interrupt();\n        assertTrue(runner.isInterrupted());\n        runner.join();\n\n        long end = System.currentTimeMillis();\n        // check we are between 3 and connect timeout seconds (should be just over 3; but allow some slack for slow CI runners)\n        assertTrue(end - start > 3 * 1000);\n        assertTrue(end - start < 10 * 1000);\n    }\n\n    @Test public void canInterruptThenJoinASpawnedThread() throws InterruptedException {\n        // https://github.com/jhy/jsoup/issues/1991\n        AtomicBoolean ioException = new AtomicBoolean();\n        Thread runner = new Thread(() -> {\n            try {\n                while (!Thread.currentThread().isInterrupted()) {\n                    Document doc  = Jsoup.connect(SlowRider.Url)\n                        .timeout(30000)\n                        .get();\n                }\n            } catch (IOException e) {\n                ioException.set(true); // don't expect to catch, because the outer sleep will complete before this timeout\n            }\n        });\n\n        runner.start();\n        Thread.sleep(2 * 1000);\n        runner.interrupt();\n        runner.join();\n        assertFalse(ioException.get());\n    }\n\n    @Test\n    public void totalTimeout() throws IOException {\n        int timeout = 3 * 1000;\n        long start = System.currentTimeMillis();\n        boolean threw = false;\n        try {\n            Jsoup.connect(SlowRider.Url).timeout(timeout).get();\n        } catch (SocketTimeoutException e) {\n            long end = System.currentTimeMillis();\n            long took = end - start;\n            assertTrue(took > timeout, (\"Time taken was \" + took));\n            assertTrue(took < timeout * 1.8, (\"Time taken was \" + took));\n            threw = true;\n        }\n\n        assertTrue(threw);\n    }\n\n    @Test\n    public void slowReadOk() throws IOException {\n        // make sure that a slow read that is under the request timeout is still OK\n        Document doc = Jsoup.connect(SlowRider.Url)\n            .data(SlowRider.MaxTimeParam, \"2000\") // the request completes in 2 seconds\n            .get();\n\n        Element h1 = doc.selectFirst(\"h1\");\n        assertEquals(\"outatime\", h1.text());\n    }\n\n    @Test void readFullyThrowsOnTimeout() throws IOException {\n        // tests that response.readFully excepts on timeout\n        boolean caught = false;\n        Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();\n        try {\n            res.readFully();\n        } catch (IOException e) {\n            caught = true;\n        }\n        assertTrue(caught);\n    }\n\n    @Test void readBodyThrowsOnTimeout() throws IOException {\n        // tests that response.readBody excepts on timeout\n        boolean caught = false;\n        Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();\n        try {\n            res.readBody();\n        } catch (IOException e) {\n            caught = true;\n        }\n        assertTrue(caught);\n    }\n\n    @Test void bodyThrowsUncheckedOnTimeout() throws IOException {\n        // tests that response.body unchecked excepts on timeout\n        boolean caught = false;\n        Connection.Response res = Jsoup.connect(SlowRider.Url).timeout(3000).execute();\n        try {\n            res.body();\n        } catch (UncheckedIOException e) {\n            caught = true;\n        }\n        assertTrue(caught);\n    }\n\n    @Test\n    public void infiniteReadSupported() throws IOException {\n        Document doc = Jsoup.connect(SlowRider.Url)\n            .timeout(0)\n            .data(SlowRider.MaxTimeParam, \"2000\")\n            .get();\n\n        Element h1 = doc.selectFirst(\"h1\");\n        assertEquals(\"outatime\", h1.text());\n    }\n\n    @Test void streamParserUncheckedExceptionOnTimeoutInStream() throws IOException {\n        boolean caught = false;\n        try (StreamParser streamParser = Jsoup.connect(SlowRider.Url)\n            .data(SlowRider.MaxTimeParam, \"10000\")\n            .data(SlowRider.IntroSizeParam, \"8000\") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()\n            .timeout(4000) // has a 1000 sleep at the start\n            .execute()\n            .streamParser()) {\n\n            // we should expect to timeout while in stream\n            try {\n                long count = streamParser.stream().count();\n            } catch (Exception e) {\n                caught = true;\n                UncheckedIOException ioe = (UncheckedIOException) e;\n                IOException cause = ioe.getCause();\n                //assertInstanceOf(SocketTimeoutException.class, cause); // different JDKs seem to wrap this differently\n                assertInstanceOf(IOException.class, cause);\n\n            }\n        }\n        assertTrue(caught);\n    }\n\n    @Test void streamParserCheckedExceptionOnTimeoutInSelect() throws IOException {\n        boolean caught = false;\n        try (StreamParser streamParser = Jsoup.connect(SlowRider.Url)\n            .data(SlowRider.MaxTimeParam, \"10000\")\n            .data(SlowRider.IntroSizeParam, \"8000\") // 8K to pass first buffer, or the timeout would occur in execute or streamparser()\n            .timeout(4000) // has a 1000 sleep at the start\n            .execute()\n            .streamParser()) {\n\n            // we should expect to timeout while in stream\n            try {\n                long count = 0;\n                while (streamParser.selectNext(\"p\") != null) {\n                    count++;\n                }\n            } catch (IOException e) {\n                caught = true;\n            }\n        }\n        assertTrue(caught);\n    }\n\n    private static final int LargeHtmlSize = 280735;\n\n    @Test\n    public void remainingAfterFirstRead() throws IOException {\n        int bufferSize = 5 * 1024;\n        int capSize = 100 * 1024;\n\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n\n        try (BufferedInputStream stream = Jsoup.connect(url).maxBodySize(capSize)\n            .execute().bodyStream()) {\n\n            // simulates parse which does a limited read first\n            stream.mark(bufferSize);\n            ByteBuffer firstBytes = DataUtil.readToByteBuffer(stream, bufferSize);\n\n            byte[] array = firstBytes.array();\n            String firstText = new String(array, StandardCharsets.UTF_8);\n            assertTrue(firstText.startsWith(\"<html><head><title>Large\"));\n            assertEquals(bufferSize, array.length);\n\n            boolean fullyRead = stream.read() == -1;\n            assertFalse(fullyRead);\n\n            // reset and read again\n            stream.reset();\n            ByteBuffer fullRead = DataUtil.readToByteBuffer(stream, 0);\n            byte[] fullArray = fullRead.array();\n\n            // bodyStream is not capped to body size - only for jsoup consumed stream\n            assertTrue(fullArray.length > capSize);\n\n            assertEquals(LargeHtmlSize, fullRead.limit());\n            String fullText = new String(fullRead.array(), 0, fullRead.limit(), StandardCharsets.UTF_8);\n            assertTrue(fullText.startsWith(firstText));\n            assertEquals(LargeHtmlSize, fullText.length());\n        }\n    }\n\n    @Test\n    public void noLimitAfterFirstRead() throws IOException {\n        int firstMaxRead = 5 * 1024;\n\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n        try (BufferedInputStream stream = Jsoup.connect(url).execute().bodyStream()) {\n            // simulates parse which does a limited read first\n            stream.mark(firstMaxRead);\n            ByteBuffer firstBytes = DataUtil.readToByteBuffer(stream, firstMaxRead);\n            byte[] array = firstBytes.array();\n            String firstText = new String(array, StandardCharsets.UTF_8);\n            assertTrue(firstText.startsWith(\"<html><head><title>Large\"));\n            assertEquals(firstMaxRead, array.length);\n\n            // reset and read fully\n            stream.reset();\n            ByteBuffer fullRead = DataUtil.readToByteBuffer(stream, 0);\n            assertEquals(LargeHtmlSize, fullRead.limit());\n            String fullText = new String(fullRead.array(), 0, fullRead.limit(), StandardCharsets.UTF_8);\n            assertTrue(fullText.startsWith(firstText));\n            assertEquals(LargeHtmlSize, fullText.length());\n        }\n    }\n\n    @Test public void bodyStreamConstrainedViaReadFully() throws IOException {\n        int cap = 5 * 1024;\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n        try (BufferedInputStream stream = Jsoup\n            .connect(url)\n            .maxBodySize(cap)\n            .execute()\n            .readFully()\n            .bodyStream()) {\n\n            ByteBuffer cappedRead = DataUtil.readToByteBuffer(stream, 0);\n            assertEquals(cap, cappedRead.limit());\n        }\n    }\n\n    @Test public void bodyStreamConstrainedViaBufferUp() throws IOException {\n        int cap = 5 * 1024;\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n        try (BufferedInputStream stream = Jsoup\n            .connect(url)\n            .maxBodySize(cap)\n            .execute()\n            .bufferUp()\n            .bodyStream()) {\n\n            ByteBuffer cappedRead = DataUtil.readToByteBuffer(stream, 0);\n            assertEquals(cap, cappedRead.limit());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/ConnectTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Connection;\nimport org.jsoup.HttpStatusException;\nimport org.jsoup.Jsoup;\nimport org.jsoup.Connection.Method;\nimport org.jsoup.TextUtil;\nimport org.jsoup.UnsupportedMimeTypeException;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.helper.W3CDom;\nimport org.jsoup.integration.servlets.*;\nimport org.jsoup.internal.SharedConstants;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.FormElement;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.XmlDeclaration;\nimport org.jsoup.parser.HtmlTreeBuilder;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.jsoup.parser.XmlTreeBuilder;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.Authenticator;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.helper.AuthenticationHandlerTest.MaxAttempts;\nimport static org.jsoup.helper.HttpConnection.CONTENT_TYPE;\nimport static org.jsoup.helper.HttpConnection.MULTIPART_FORM_DATA;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests Jsoup.connect against a local server.\n */\npublic class ConnectTest {\n    private static final int LargeDocFileLen = 280735;\n    private static final int LargeDocTextLen = 269535;\n    private static String echoUrl;\n\n    @BeforeAll\n    public static void setUp() {\n        TestServer.start();\n        echoUrl = EchoServlet.Url;\n        System.setProperty(SharedConstants.UseHttpClient, \"false\"); // use the default UrlConnection. See HttpClientConnectTest for other version\n    }\n\n    @BeforeEach\n    public void emptyCookieJar() {\n        // empty the cookie jar, so cookie tests are independent.\n        Jsoup.connect(\"http://example.com\").cookieStore().removeAll();\n    }\n\n    @Test\n    public void canConnectToLocalServer() throws IOException {\n        String url = HelloServlet.Url;\n        Document doc = Jsoup.connect(url).get();\n        Element p = doc.selectFirst(\"p\");\n        assertEquals(\"Hello, World!\", p.text());\n    }\n\n    @Test void canConnectToLocalTlsServer() throws IOException {\n        String url = HelloServlet.TlsUrl;\n        Document doc = Jsoup.connect(url).get();\n        Element p = doc.selectFirst(\"p\");\n        assertEquals(\"Hello, World!\", p.text());\n    }\n\n    @Test\n    public void fetchURl() throws IOException {\n        Document doc = Jsoup.parse(new URL(echoUrl), 10 * 1000);\n        assertTrue(doc.title().contains(\"Environment Variables\"));\n    }\n\n    @Test\n    public void fetchURIWithWhitespace() throws IOException {\n        Connection con = Jsoup.connect(echoUrl + \"#with whitespaces\");\n        Document doc = con.get();\n        assertTrue(doc.title().contains(\"Environment Variables\"));\n    }\n\n    @Test\n    public void exceptOnUnsupportedProtocol() {\n        String url = \"file://etc/passwd\";\n        boolean threw = false;\n        try {\n            Document doc = Jsoup.connect(url).get();\n        } catch (MalformedURLException e) {\n            threw = true;\n            assertEquals(\"java.net.MalformedURLException: Only http & https protocols supported\", e.toString());\n        } catch (IOException e) {\n        }\n        assertTrue(threw);\n    }\n\n    static String ihVal(String key, Document doc) {\n        final Element first = doc.select(\"th:contains(\" + key + \") + td\").first();\n        return first != null ? first.text() : null;\n    }\n\n    @Test void statusMessage() throws IOException {\n        Connection con = Jsoup.connect(EchoServlet.Url);\n        Document doc = con.get();\n        assertEquals(\"OK\", con.response().statusMessage());\n    }\n\n    @Test\n    public void throwsExceptionOn404() {\n        String url = EchoServlet.Url;\n        Connection con = Jsoup.connect(url).header(EchoServlet.CodeParam, \"404\");\n\n        boolean threw = false;\n        try {\n            Document doc = con.get();\n        } catch (HttpStatusException e) {\n            threw = true;\n            assertEquals(\"org.jsoup.HttpStatusException: HTTP error fetching URL. Status=404, URL=[\" + e.getUrl() + \"]\", e.toString());\n            assertTrue(e.getUrl().startsWith(url));\n            assertEquals(404, e.getStatusCode());\n        } catch (IOException e) {\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void ignoresExceptionIfSoConfigured() throws IOException {\n        String url = EchoServlet.Url;\n        Connection con = Jsoup.connect(url)\n            .header(EchoServlet.CodeParam, \"404\")\n            .ignoreHttpErrors(true);\n        Connection.Response res = con.execute();\n        Document doc = res.parse();\n        assertEquals(404, res.statusCode());\n        assertEquals(\"Not Found\", res.statusMessage());\n        assertEquals(\"Webserver Environment Variables\", doc.title());\n    }\n\n    @Test\n    public void doesPost() throws IOException {\n        Document doc = Jsoup.connect(echoUrl)\n            .data(\"uname\", \"Jsoup\", \"uname\", \"Jonathan\", \"百\", \"度一下\")\n            .cookie(\"auth\", \"token\")\n            .post();\n\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"gzip\", ihVal(\"Accept-Encoding\", doc));\n        assertEquals(\"auth=token\", ihVal(\"Cookie\", doc));\n        assertEquals(\"度一下\", ihVal(\"百\", doc));\n        assertEquals(\"Jsoup, Jonathan\", ihVal(\"uname\", doc));\n        assertEquals(\"application/x-www-form-urlencoded; charset=UTF-8\", ihVal(\"Content-Type\", doc));\n    }\n\n    @Test\n    public void doesPostMultipartWithoutInputstream() throws IOException {\n        Document doc = Jsoup.connect(echoUrl)\n                .header(CONTENT_TYPE, MULTIPART_FORM_DATA)\n                .data(\"uname\", \"Jsoup\", \"uname\", \"Jonathan\", \"百\", \"度一下\")\n                .post();\n\n        assertTrue(ihVal(\"Content-Type\", doc).contains(MULTIPART_FORM_DATA));\n\n        assertTrue(ihVal(\"Content-Type\", doc).contains(\"boundary\")); // should be automatically set\n        assertEquals(\"Jsoup, Jonathan\", ihVal(\"uname\", doc));\n        assertEquals(\"度一下\", ihVal(\"百\", doc));\n    }\n\n    @Test\n    public void canSendSecFetchHeaders() throws IOException {\n        // https://github.com/jhy/jsoup/issues/1461\n        Document doc = Jsoup.connect(echoUrl)\n            .header(\"Random-Header-name\", \"hello\")\n            .header(\"Sec-Fetch-Site\", \"cross-site\")\n            .header(\"Sec-Fetch-Mode\", \"cors\")\n            .get();\n\n        assertEquals(\"hello\", ihVal(\"Random-Header-name\", doc));\n        assertEquals(\"cross-site\", ihVal(\"Sec-Fetch-Site\", doc));\n        assertEquals(\"cors\", ihVal(\"Sec-Fetch-Mode\", doc));\n    }\n\n    @Test\n    public void secFetchHeadersSurviveRedirect() throws IOException {\n        Document doc = Jsoup\n            .connect(RedirectServlet.Url)\n            .data(RedirectServlet.LocationParam, echoUrl)\n            .header(\"Random-Header-name\", \"hello\")\n            .header(\"Sec-Fetch-Site\", \"cross-site\")\n            .header(\"Sec-Fetch-Mode\", \"cors\")\n            .get();\n\n        assertEquals(\"hello\", ihVal(\"Random-Header-name\", doc));\n        assertEquals(\"cross-site\", ihVal(\"Sec-Fetch-Site\", doc));\n        assertEquals(\"cors\", ihVal(\"Sec-Fetch-Mode\", doc));\n    }\n\n    @Test\n    public void sendsRequestBodyJsonWithData() throws IOException {\n        final String body = \"{key:value}\";\n        Document doc = Jsoup.connect(echoUrl)\n            .requestBody(body)\n            .header(\"Content-Type\", \"application/json\")\n            .data(\"foo\", \"true\")\n            .post();\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"application/json\", ihVal(\"Content-Type\", doc));\n        assertEquals(\"foo=true\", ihVal(\"Query String\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @Test\n    public void sendsRequestBodyJsonWithoutData() throws IOException {\n        final String body = \"{key:value}\";\n        Document doc = Jsoup.connect(echoUrl)\n            .requestBody(body)\n            .header(\"Content-Type\", \"application/json\")\n            .post();\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"application/json\", ihVal(\"Content-Type\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @Test\n    public void sendsRequestBody() throws IOException {\n        final String body = \"{key:value}\";\n        Document doc = Jsoup.connect(echoUrl)\n            .requestBody(body)\n            .header(\"Content-Type\", \"text/plain\")\n            .post();\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"text/plain\", ihVal(\"Content-Type\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @Test\n    public void sendsRequestBodyWithUrlParams() throws IOException {\n        final String body = \"{key:value}\";\n        Document doc = Jsoup.connect(echoUrl)\n            .requestBody(body)\n            .data(\"uname\", \"Jsoup\", \"uname\", \"Jonathan\", \"百\", \"度一下\")\n            .header(\"Content-Type\", \"text/plain\") // todo - if user sets content-type, we should append postcharset\n            .post();\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"uname=Jsoup&uname=Jonathan&%E7%99%BE=%E5%BA%A6%E4%B8%80%E4%B8%8B\", ihVal(\"Query String\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @Test void sendsRequestBodyStream() throws IOException {\n        final String body = \"{key:value}\";\n        InputStream stream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));\n\n        Document doc = Jsoup.connect(echoUrl)\n            .requestBodyStream(stream)\n            .header(\"Content-Type\", \"application/json\")\n            .data(\"foo\", \"true\")\n            .post();\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"application/json\", ihVal(\"Content-Type\", doc));\n        assertEquals(\"foo=true\", ihVal(\"Query String\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\") // http and https\n    public void doesGet(String url) throws IOException {\n        Connection con = Jsoup.connect(url + \"?what=the\")\n            .userAgent(\"Mozilla\")\n            .referrer(\"http://example.com\")\n            .data(\"what\", \"about & me?\");\n\n        Document doc = con.get();\n        assertEquals(\"what=the&what=about+%26+me%3F\", ihVal(\"Query String\", doc));\n        assertEquals(\"the, about & me?\", ihVal(\"what\", doc));\n        assertEquals(\"Mozilla\", ihVal(\"User-Agent\", doc));\n        assertEquals(\"http://example.com\", ihVal(\"Referer\", doc));\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\") // http and https\n    public void streamParserGet(String url) throws IOException {\n        Connection con = Jsoup.connect(url)\n            .userAgent(\"Mozilla\")\n            .referrer(\"http://example.com\")\n            .data(\"what\", \"about & me?\");\n\n        //final Element first = doc.select(\"th:contains(\" + key + \") + td\").first();\n        try (StreamParser streamer = con.execute().streamParser()) {\n            Element title = streamer.expectFirst(\"title\");\n            assertEquals(\"Webserver Environment Variables\", title.text());\n            Element method = streamer.expectNext(echoSelect(\"Method\"));\n            assertEquals(\"GET\", method.text());\n\n            Document doc = streamer.document();\n            assertSame(doc, title.ownerDocument());\n\n            assertEquals(url + \"?what=about+%26+me%3F\", doc.location()); // with the query string\n        }\n    }\n\n    static String echoSelect(String key) {\n        return String.format(\"th:contains(%s) + td\", key);\n    }\n\n    @Test\n    public void doesPut() throws IOException {\n        Connection.Response res = Jsoup.connect(echoUrl)\n            .data(\"uname\", \"Jsoup\", \"uname\", \"Jonathan\", \"百\", \"度一下\")\n            .cookie(\"auth\", \"token\")\n            .method(Connection.Method.PUT)\n            .execute();\n\n        Document doc = res.parse();\n        assertEquals(\"PUT\", ihVal(\"Method\", doc));\n        assertEquals(\"gzip\", ihVal(\"Accept-Encoding\", doc));\n        assertEquals(\"auth=token\", ihVal(\"Cookie\", doc));\n    }\n\n    @Test\n    public void doesDeleteWithBody() throws IOException {\n        // https://github.com/jhy/jsoup/issues/1972\n        String body = \"some body\";\n        Connection.Response res = Jsoup.connect(echoUrl)\n            .requestBody(body)\n            .method(Method.DELETE)\n            .execute();\n\n        Document doc = res.parse();\n        assertEquals(\"DELETE\", ihVal(\"Method\", doc));\n        assertEquals(body, ihVal(\"Post Data\", doc));\n    }\n\n    @Test\n    public void doesDeleteWithoutBody() throws IOException {\n        Connection.Response res = Jsoup.connect(echoUrl)\n            .method(Method.DELETE)\n            .execute();\n\n        Document doc = res.parse();\n        assertEquals(\"DELETE\", ihVal(\"Method\", doc));\n        assertEquals(null, ihVal(\"Post Data\", doc));\n    }\n\n    /**\n     * Tests upload of content to a remote service.\n     */\n    @ParameterizedTest @MethodSource(\"echoUrls\") // http and https\n    public void postFiles(String url) throws IOException {\n        File thumb = ParseTest.getFile(\"/htmltests/thumb.jpg\");\n        File html = ParseTest.getFile(\"/htmltests/large.html\");\n\n        Document res = Jsoup\n            .connect(url)\n            .data(\"firstname\", \"Jay\")\n            .data(\"firstPart\", thumb.getName(), Files.newInputStream(thumb.toPath()), \"image/jpeg\")\n            .data(\"secondPart\", html.getName(), Files.newInputStream(html.toPath())) // defaults to \"application-octetstream\";\n            .data(\"surname\", \"Soup\")\n            .post();\n\n        assertEquals(\"4\", ihVal(\"Parts\", res));\n\n        assertEquals(\"application/octet-stream\", ihVal(\"Part secondPart ContentType\", res));\n        assertEquals(\"secondPart\", ihVal(\"Part secondPart Name\", res));\n        assertEquals(\"large.html\", ihVal(\"Part secondPart Filename\", res));\n        assertEquals(\"280735\", ihVal(\"Part secondPart Size\", res));\n\n        assertEquals(\"image/jpeg\", ihVal(\"Part firstPart ContentType\", res));\n        assertEquals(\"firstPart\", ihVal(\"Part firstPart Name\", res));\n        assertEquals(\"thumb.jpg\", ihVal(\"Part firstPart Filename\", res));\n        assertEquals(\"1052\", ihVal(\"Part firstPart Size\", res));\n\n        assertEquals(\"Jay\", ihVal(\"firstname\", res));\n        assertEquals(\"Soup\", ihVal(\"surname\", res));\n\n        /*\n        <tr><th>Part secondPart ContentType</th><td>application/octet-stream</td></tr>\n        <tr><th>Part secondPart Name</th><td>secondPart</td></tr>\n        <tr><th>Part secondPart Filename</th><td>google-ipod.html</td></tr>\n        <tr><th>Part secondPart Size</th><td>43972</td></tr>\n        <tr><th>Part firstPart ContentType</th><td>image/jpeg</td></tr>\n        <tr><th>Part firstPart Name</th><td>firstPart</td></tr>\n        <tr><th>Part firstPart Filename</th><td>thumb.jpg</td></tr>\n        <tr><th>Part firstPart Size</th><td>1052</td></tr>\n         */\n    }\n\n    @Test\n    public void multipleParsesOkAfterReadFully() throws IOException {\n        Connection.Response res = Jsoup.connect(echoUrl).execute().readFully();\n\n        Document doc = res.parse();\n        assertTrue(doc.title().contains(\"Environment\"));\n\n        Document doc2 = res.parse();\n        assertTrue(doc2.title().contains(\"Environment\"));\n    }\n\n    @Test\n    public void multipleParsesOkAfterBufferUp() throws IOException {\n        Connection.Response res = Jsoup.connect(echoUrl).execute().bufferUp();\n\n        Document doc = res.parse();\n        assertTrue(doc.title().contains(\"Environment\"));\n\n        Document doc2 = res.parse();\n        assertTrue(doc2.title().contains(\"Environment\"));\n    }\n\n    @Test\n    public void bodyAfterParseThrowsValidationError() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection.Response res = Jsoup.connect(echoUrl).execute();\n            Document doc = res.parse();\n            String body = res.body();\n        });\n    }\n\n    @Test\n    public void bodyAndBytesAvailableBeforeParse() throws IOException {\n        Connection.Response res = Jsoup.connect(echoUrl).execute();\n        String body = res.body();\n        assertTrue(body.contains(\"Environment\"));\n        byte[] bytes = res.bodyAsBytes();\n        assertTrue(bytes.length > 100);\n\n        Document doc = res.parse();\n        assertTrue(doc.title().contains(\"Environment\"));\n    }\n\n    @Test\n    public void parseParseThrowsValidates() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Connection.Response res = Jsoup.connect(echoUrl).execute();\n            Document doc = res.parse();\n            assertTrue(doc.title().contains(\"Environment\"));\n            Document doc2 = res.parse(); // should blow up because the response input stream has been drained\n        });\n    }\n\n\n    @Test\n    public void multiCookieSet() throws IOException {\n        Connection con = Jsoup\n                .connect(RedirectServlet.Url)\n                .data(RedirectServlet.CodeParam, \"302\")\n                .data(RedirectServlet.SetCookiesParam, \"true\")\n                .data(RedirectServlet.LocationParam, echoUrl);\n        Connection.Response res = con.execute();\n\n        // test cookies set by redirect:\n        Map<String, String> cookies = res.cookies();\n        assertEquals(\"asdfg123\", cookies.get(\"token\"));\n        assertEquals(\"jhy\", cookies.get(\"uid\")); // two uids set, order dependent\n\n        // send those cookies into the echo URL by map:\n        Document doc = Jsoup.connect(echoUrl).cookies(cookies).get();\n        assertEquals(\"token=asdfg123; uid=jhy\", ihVal(\"Cookie\", doc));\n    }\n\n    @Test public void requestCookiesSurviveRedirect() throws IOException {\n        // this test makes sure that Request keyval cookies (not in the cookie store) are sent on subsequent redirections,\n        // when not using the session method\n        Connection con = Jsoup.connect(RedirectServlet.Url)\n            .data(RedirectServlet.LocationParam, echoUrl)\n            .cookie(\"LetMeIn\", \"True\")\n            .cookie(\"DoesItWork\", \"Yes\");\n\n        Connection.Response res = con.execute();\n        assertEquals(0, res.cookies().size()); // were not set by Redir or Echo servlet\n        Document doc = res.parse();\n        assertEquals(echoUrl, doc.location());\n        assertEquals(\"True\", ihVal(\"Cookie: LetMeIn\", doc));\n        assertEquals(\"Yes\", ihVal(\"Cookie: DoesItWork\", doc));\n    }\n\n    @Test\n    public void supportsDeflate() throws IOException {\n        Connection.Response res = Jsoup.connect(DeflateServlet.Url).execute();\n        assertEquals(\"deflate\", res.header(\"Content-Encoding\"));\n\n        Document doc = res.parse();\n        assertEquals(\"Hello, World!\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test\n    public void handlesLargerContentLengthParseRead() throws IOException {\n        // this handles situations where the remote server sets a content length greater than it actually writes\n\n        Connection.Response res = Jsoup.connect(InterruptedServlet.Url)\n            .data(InterruptedServlet.Magnitude, InterruptedServlet.Larger)\n            .timeout(400)\n            .execute();\n\n        try {\n            Document document = res.parse();\n            assertEquals(\"Something\", document.title());\n            assertEquals(0, document.select(\"p\").size());\n        } catch (IOException ignored) {\n            // HttpUrlConnection will read the amount provided and the tests in the try will pass\n            // HttpClient will throw unexpected EOF during the read, and this will catch it\n            // Either are OK\n        }\n        // current impl, jetty won't write past content length\n        // todo - find way to trick jetty into writing larger than set header. Take over the stream?\n    }\n\n    @Test\n    public void handlesWrongContentLengthDuringBufferedRead() throws IOException {\n        Connection.Response res = Jsoup.connect(InterruptedServlet.Url)\n                .timeout(400)\n                .execute();\n        // this servlet writes max_buffer data, but sets content length to max_buffer/2. So will read up to that.\n        // previous versions of jetty would allow to write less, and would throw except here\n\n        res.bufferUp();\n        Document doc = res.parse();\n        assertEquals(0, doc.select(\"p\").size());\n    }\n\n    @Test public void handlesRedirect() throws IOException {\n        Document doc = Jsoup.connect(RedirectServlet.Url)\n            .data(RedirectServlet.LocationParam, HelloServlet.Url)\n            .get();\n\n        Element p = doc.selectFirst(\"p\");\n        assertEquals(\"Hello, World!\", p.text());\n\n        assertEquals(HelloServlet.Url, doc.location());\n    }\n\n    @Test public void handlesEmptyRedirect() {\n        boolean threw = false;\n        try {\n            Connection.Response res = Jsoup.connect(RedirectServlet.Url)\n                .execute();\n        } catch (IOException e) {\n            assertTrue(e.getMessage().contains(\"Too many redirects\"));\n            threw = true;\n        }\n        assertTrue(threw);\n    }\n\n    @Test public void doesNotPostFor302() throws IOException {\n        final Document doc = Jsoup.connect(RedirectServlet.Url)\n            .data(\"Hello\", \"there\")\n            .data(RedirectServlet.LocationParam, EchoServlet.Url)\n            .post();\n\n        assertEquals(EchoServlet.Url, doc.location());\n        assertEquals(\"GET\", ihVal(\"Method\", doc));\n        assertNull(ihVal(\"Hello\", doc)); // data not sent\n    }\n\n    @Test public void doesPostFor307() throws IOException {\n        final Document doc = Jsoup.connect(RedirectServlet.Url)\n            .data(\"Hello\", \"there\")\n            .data(RedirectServlet.LocationParam, EchoServlet.Url)\n            .data(RedirectServlet.CodeParam, \"307\")\n            .post();\n\n        assertEquals(EchoServlet.Url, doc.location());\n        assertEquals(\"POST\", ihVal(\"Method\", doc));\n        assertEquals(\"there\", ihVal(\"Hello\", doc));\n    }\n\n    @Test public void getUtf8Bom() throws IOException {\n        Connection con = Jsoup.connect(FileServlet.urlTo(\"/bomtests/bom_utf8.html\"));\n        Document doc = con.get();\n\n        assertEquals(\"UTF-8\", con.response().charset());\n        assertEquals(\"OK\", doc.title());\n    }\n\n    @Test public void streamerGetUtf8Bom() throws IOException {\n        Connection con = Jsoup.connect(FileServlet.urlTo(\"/bomtests/bom_utf8.html\"));\n        Document doc = con.execute().streamParser().complete();\n\n        assertEquals(\"UTF-8\", con.response().charset());\n        assertEquals(\"OK\", doc.title());\n    }\n\n    @Test\n    public void testBinaryContentTypeThrowsException() throws IOException {\n        Connection con = Jsoup.connect(FileServlet.urlTo(\"/htmltests/thumb.jpg\"));\n        con.data(FileServlet.ContentTypeParam, \"image/jpeg\");\n\n        boolean threw = false;\n        try {\n            con.execute();\n            Document doc = con.response().parse();\n        } catch (UnsupportedMimeTypeException e) {\n            threw = true;\n            assertEquals(\"Unhandled content type. Must be text/*, */xml, or */*+xml\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test public void testParseRss() throws IOException {\n        // test that we switch automatically to xml, and we support application/rss+xml\n        Connection con = Jsoup.connect(FileServlet.urlTo(\"/htmltests/test-rss.xml\"));\n        con.data(FileServlet.ContentTypeParam, \"application/rss+xml\");\n        Document doc = con.get();\n        Element title = doc.selectFirst(\"title\");\n        assertNotNull(title);\n        assertEquals(\"jsoup RSS news\", title.text());\n        assertEquals(\"channel\", title.parent().nodeName());\n        assertEquals(\"\", doc.title()); // the document title is unset, this tag is channel>title, not html>head>title\n        assertEquals(3, doc.select(\"link\").size());\n        assertEquals(\"application/rss+xml\", con.response().contentType());\n        assertTrue(doc.parser().getTreeBuilder() instanceof XmlTreeBuilder);\n        assertEquals(Document.OutputSettings.Syntax.xml, doc.outputSettings().syntax());\n    }\n\n    @Test\n    public void testSupplyParserToConnection() throws IOException {\n        String xmlUrl = FileServlet.urlTo(\"/htmltests/xml-test.xml\");\n\n        // parse with both xml and html parser, ensure different\n        Document xmlDoc = Jsoup.connect(xmlUrl).parser(Parser.xmlParser()).get();\n        Document htmlDoc = Jsoup.connect(xmlUrl).parser(Parser.htmlParser()).get();\n        Document autoXmlDoc = Jsoup.connect(xmlUrl).get(); // check connection auto detects xml, uses xml parser\n\n        assertEquals(\"<doc><val>One<val>Two</val>Three</val></doc>\",\n            TextUtil.stripNewlines(xmlDoc.html()));\n        assertNotEquals(htmlDoc, xmlDoc);\n        assertFalse(htmlDoc.hasSameValue(xmlDoc));\n        assertTrue(xmlDoc.hasSameValue(autoXmlDoc));\n\n        assertEquals(1, htmlDoc.select(\"head\").size()); // html parser normalises\n        assertEquals(0, xmlDoc.select(\"head\").size()); // xml parser does not\n        assertEquals(0, autoXmlDoc.select(\"head\").size()); // xml parser does not\n    }\n\n    @Test public void imageXmlMimeType() throws IOException {\n        // test that we switch to XML, and that we support image/svg+xml\n        String mimetype = \"image/svg+xml\";\n\n        Connection con = Jsoup.connect(FileServlet.urlTo(\"/htmltests/osi-logo.svg\"))\n            .data(FileServlet.ContentTypeParam, mimetype);\n        Document doc = con.get();\n\n        assertEquals(mimetype, con.response().contentType());\n        assertTrue(doc.parser().getTreeBuilder() instanceof XmlTreeBuilder);\n        assertEquals(Document.OutputSettings.Syntax.xml, doc.outputSettings().syntax());\n        Node firstChild = doc.firstChild();\n        XmlDeclaration decl = (XmlDeclaration) firstChild;\n        assertEquals(\"no\", decl.attr(\"standalone\"));\n        Element svg = doc.expectFirst(\"svg\");\n        Element flowRoot = svg.expectFirst(\"flowRoot\");\n        assertEquals(\"flowRoot\", flowRoot.tagName());\n        assertEquals(\"preserve\", flowRoot.attr(\"xml:space\"));\n    }\n\n    @Test\n    public void canFetchBinaryAsBytes() throws IOException {\n        String path = \"/htmltests/thumb.jpg\";\n        int actualSize = 1052;\n\n        Connection.Response res = Jsoup.connect(FileServlet.urlTo(path))\n            .data(FileServlet.ContentTypeParam, \"image/jpeg\")\n            .ignoreContentType(true)\n            .execute();\n\n        byte[] resBytes = res.bodyAsBytes();\n        assertEquals(actualSize, resBytes.length);\n\n        // compare the content of the file and the bytes:\n        Path filePath = ParseTest.getPath(path);\n        byte[] fileBytes = Files.readAllBytes(filePath);\n        assertEquals(actualSize, fileBytes.length);\n        assertArrayEquals(fileBytes, resBytes);\n    }\n\n    @Test\n    public void handlesUnknownEscapesAcrossBuffer() throws IOException {\n        String localPath = \"/htmltests/escapes-across-buffer.html\";\n        String localUrl = FileServlet.urlTo(localPath);\n\n        Document docFromLocalServer = Jsoup.connect(localUrl).get();\n        Document docFromFileRead = Jsoup.parse(ParseTest.getFile(localPath), \"UTF-8\");\n\n        String text = docFromLocalServer.body().text();\n        assertEquals(14766, text.length());\n        assertEquals(text, docFromLocalServer.body().text());\n        assertEquals(text, docFromFileRead.body().text());\n    }\n\n    /**\n     * Test fetching a form, and submitting it with a file attached.\n     */\n    @Test\n    public void postHtmlFile() throws IOException {\n        Document index = Jsoup.connect(FileServlet.urlTo(\"/htmltests/upload-form.html\")).get();\n        List<FormElement> forms = index.select(\"[name=tidy]\").forms();\n        assertEquals(1, forms.size());\n        FormElement form = forms.get(0);\n        Connection post = form.submit();\n\n        File uploadFile = ParseTest.getFile(\"/htmltests/large.html\");\n        FileInputStream stream = new FileInputStream(uploadFile);\n\n        Connection.KeyVal fileData = post.data(\"_file\");\n        assertNotNull(fileData);\n        fileData.value(\"check.html\");\n        fileData.inputStream(stream);\n\n        Connection.Response res;\n        try {\n            res = post.execute();\n        } finally {\n            stream.close();\n        }\n\n        Document doc = res.parse();\n        assertEquals(ihVal(\"Method\", doc), \"POST\"); // from form action\n        assertEquals(ihVal(\"Part _file Filename\", doc), \"check.html\");\n        assertEquals(ihVal(\"Part _file Name\", doc), \"_file\");\n        assertEquals(ihVal(\"_function\", doc), \"tidy\");\n    }\n\n    @Test\n    public void fetchHandlesXml() throws IOException {\n        String[] types = {\"text/xml\", \"application/xml\", \"application/rss+xml\", \"application/xhtml+xml\"};\n        for (String type : types) {\n            fetchHandlesXml(type);\n        }\n    }\n\n    void fetchHandlesXml(String contentType) throws IOException {\n        // should auto-detect xml and use XML parser, unless explicitly requested the html parser\n        String xmlUrl = FileServlet.urlTo(\"/htmltests/xml-test.xml\");\n        Connection con = Jsoup.connect(xmlUrl);\n        con.data(FileServlet.ContentTypeParam, contentType);\n        Document doc = con.get();\n        Connection.Request req = con.request();\n        assertTrue(req.parser().getTreeBuilder() instanceof XmlTreeBuilder);\n        assertEquals(\"<doc><val>One<val>Two</val>Three</val></doc>\\n\", doc.outerHtml());\n        assertEquals(con.response().contentType(), contentType);\n    }\n\n    @Test\n    public void fetchHandlesXmlAsHtmlWhenParserSet() throws IOException {\n        // should auto-detect xml and use XML parser, unless explicitly requested the html parser\n        String xmlUrl = FileServlet.urlTo(\"/htmltests/xml-test.xml\");\n        Connection con = Jsoup.connect(xmlUrl).parser(Parser.htmlParser()); // which will also use the pretty printer by default\n        con.data(FileServlet.ContentTypeParam, \"application/xml\");\n        Document doc = con.get();\n        Connection.Request req = con.request();\n        assertTrue(req.parser().getTreeBuilder() instanceof HtmlTreeBuilder);\n        assertEquals(\"<html>\\n <head></head>\\n <body>\\n  <doc>\\n   <val>\\n    One<val>Two</val>Three\\n   </val>\\n  </doc>\\n </body>\\n</html>\", doc.outerHtml());\n    }\n\n    @Test\n    public void combinesSameHeadersWithComma() throws IOException {\n        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\n        Connection con = Jsoup.connect(echoUrl);\n        con.get();\n\n        Connection.Response res = con.response();\n        assertEquals(\"text/html;charset=utf-8\", res.header(\"Content-Type\"));\n        assertEquals(\"no-cache, no-store\", res.header(\"Cache-Control\"));\n\n        List<String> header = res.headers(\"Cache-Control\");\n        assertEquals(2, header.size());\n        assertEquals(\"no-cache\", header.get(0));\n        assertEquals(\"no-store\", header.get(1));\n    }\n\n    @Test\n    public void sendHeadRequest() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/xml-test.xml\");\n        Connection con = Jsoup.connect(url)\n            .method(Connection.Method.HEAD)\n            .data(FileServlet.ContentTypeParam, \"text/xml\");\n        final Connection.Response response = con.execute();\n        assertEquals(\"text/xml\", response.header(\"Content-Type\"));\n        assertEquals(\"\", response.body()); // head ought to have no body\n        Document doc = response.parse();\n        assertEquals(\"\", doc.text());\n    }\n\n    @Test\n    public void fetchToW3c() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/upload-form.html\");\n        Document doc = Jsoup.connect(url).get();\n\n        W3CDom dom = new W3CDom();\n        org.w3c.dom.Document wDoc = dom.fromJsoup(doc);\n        assertEquals(url, wDoc.getDocumentURI());\n        String html = dom.asString(wDoc);\n        assertTrue(html.contains(\"Upload\"));\n    }\n\n    @Test\n    public void baseHrefCorrectAfterHttpEquiv() throws IOException {\n        // https://github.com/jhy/jsoup/issues/440\n        Connection.Response res = Jsoup.connect(FileServlet.urlTo(\"/htmltests/charset-base.html\")).execute();\n        Document doc = res.parse();\n        assertEquals(\"http://example.com/foo.jpg\", doc.select(\"img\").first().absUrl(\"src\"));\n    }\n\n    @Test\n    public void maxBodySize() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n\n        Connection.Response defaultRes = Jsoup.connect(url).execute();\n        Connection.Response smallRes = Jsoup.connect(url).maxBodySize(50 * 1024).execute(); // crops\n        Connection.Response mediumRes = Jsoup.connect(url).maxBodySize(200 * 1024).execute(); // crops\n        Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop\n        Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute();\n\n        int actualDocText = LargeDocTextLen;\n        assertEquals(actualDocText, defaultRes.parse().text().length());\n        assertEquals(49165, smallRes.parse().text().length());\n        assertEquals(196577, mediumRes.parse().text().length());\n        assertEquals(actualDocText, largeRes.parse().text().length());\n        assertEquals(actualDocText, unlimitedRes.parse().text().length());\n    }\n\n    @Test public void repeatable() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n        Connection con = Jsoup.connect(url).parser(Parser.xmlParser());\n        Document doc1 = con.get();\n        Document doc2 = con.get();\n        assertEquals(\"Large HTML\", doc1.title());\n        assertEquals(\"Large HTML\", doc2.title());\n    }\n\n    @Test\n    public void maxBodySizeInReadToByteBuffer() throws IOException {\n        // https://github.com/jhy/jsoup/issues/1774\n        // when calling readToByteBuffer, contents were not buffered up\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n\n        Connection.Response defaultRes = Jsoup.connect(url).execute();\n        Connection.Response smallRes = Jsoup.connect(url).maxBodySize(50 * 1024).execute(); // crops\n        Connection.Response mediumRes = Jsoup.connect(url).maxBodySize(200 * 1024).execute(); // crops\n        Connection.Response largeRes = Jsoup.connect(url).maxBodySize(300 * 1024).execute(); // does not crop\n        Connection.Response unlimitedRes = Jsoup.connect(url).maxBodySize(0).execute();\n\n        int actualDocText = 280735;\n        assertEquals(actualDocText, defaultRes.body().length());\n        assertEquals(50 * 1024, smallRes.body().length());\n        assertEquals(200 * 1024, mediumRes.body().length());\n        assertEquals(actualDocText, largeRes.body().length());\n        assertEquals(actualDocText, unlimitedRes.body().length());\n\n        assertEquals(actualDocText, defaultRes.readBody().length());\n        assertEquals(50 * 1024, smallRes.readBody().length());\n        assertEquals(200 * 1024, mediumRes.readBody().length());\n        assertEquals(actualDocText, largeRes.readBody().length());\n        assertEquals(actualDocText, unlimitedRes.readBody().length());\n    }\n\n    @Test void formLoginFlow() throws IOException {\n        String echoUrl = EchoServlet.Url;\n        String cookieUrl = CookieServlet.Url;\n\n        String startUrl = FileServlet.urlTo(\"/htmltests/form-tests.html\");\n        Document loginDoc = Jsoup.connect(startUrl).get();\n        FormElement form = loginDoc.expectForm(\"#login\");\n        assertNotNull(form);\n        form.expectFirst(\"[name=username]\").val(\"admin\");\n        form.expectFirst(\"[name=password]\").val(\"Netscape engineers are weenies!\");\n\n        // post it- should go to Cookie then bounce to Echo\n        Connection submit = form.submit();\n        assertEquals(Connection.Method.POST, submit.request().method());\n        Connection.Response postRes = submit.execute();\n        assertEquals(echoUrl, postRes.url().toExternalForm());\n        assertEquals(Connection.Method.GET, postRes.method());\n        Document resultDoc = postRes.parse();\n        assertEquals(\"One=EchoServlet; One=Root\", ihVal(\"Cookie\", resultDoc));\n        // should be no form data sent to the echo redirect\n        assertEquals(\"\", ihVal(\"Query String\", resultDoc));\n\n        // new request to echo, should not have form data, but should have cookies from implicit session\n        Document newEcho = submit.newRequest(echoUrl).get();\n        assertEquals(\"One=EchoServlet; One=Root\", ihVal(\"Cookie\", newEcho));\n        assertEquals(\"\", ihVal(\"Query String\", newEcho));\n\n        Document cookieDoc = submit.newRequest(cookieUrl).get();\n        assertEquals(\"CookieServlet\", ihVal(\"One\", cookieDoc)); // different cookie path\n\n    }\n\n    @Test void formLoginFlow2() throws IOException {\n        String echoUrl = EchoServlet.Url;\n        String cookieUrl = CookieServlet.Url;\n        String startUrl = FileServlet.urlTo(\"/htmltests/form-tests.html\");\n\n        Connection session = Jsoup.newSession();\n        Document loginDoc = session.newRequest(startUrl).get();\n        FormElement form = loginDoc.expectForm(\"#login2\");\n        assertNotNull(form);\n        String username = \"admin\";\n        form.expectFirst(\"[name=username]\").val(username);\n        String password = \"Netscape engineers are weenies!\";\n        form.expectFirst(\"[name=password]\").val(password);\n\n        Connection submit = form.submit();\n        assertEquals(username, submit.data(\"username\").value());\n        assertEquals(password, submit.data(\"password\").value());\n\n        Connection.Response postRes = submit.execute();\n        assertEquals(cookieUrl, postRes.url().toExternalForm());\n        assertEquals(Connection.Method.POST, postRes.method());\n        Document resultDoc = postRes.parse();\n\n        Document echo2 = resultDoc.connection().newRequest(echoUrl).get();\n        assertEquals(\"\", ihVal(\"Query String\", echo2)); // should not re-send the data\n        assertEquals(\"One=EchoServlet; One=Root\", ihVal(\"Cookie\", echo2));\n    }\n\n    @Test void preservesUrlFragment() throws IOException {\n        // confirms https://github.com/jhy/jsoup/issues/1686\n        String url = EchoServlet.Url + \"#fragment\";\n        Document doc = Jsoup.connect(url).get();\n        assertEquals(url, doc.location());\n    }\n\n    @Test void fetchUnicodeUrl() throws IOException {\n        String url = EchoServlet.Url + \"/✔/?鍵=値\";\n        Document doc = Jsoup.connect(url).get();\n\n        assertEquals(\"/✔/\", ihVal(\"Path Info\", doc));\n        assertEquals(\"%E9%8D%B5=%E5%80%A4\", ihVal(\"Query String\", doc));\n        assertEquals(\"鍵=値\", URLDecoder.decode(ihVal(\"Query String\", doc), DataUtil.UTF_8.name()));\n    }\n\n    @Test void willEscapePathInRedirect() throws IOException {\n        String append = \"/Foo{bar}<>%/\";\n        String url = EchoServlet.Url + append;\n        Document doc = Jsoup\n            .connect(RedirectServlet.Url)\n            .data(RedirectServlet.LocationParam, url)\n            .get();\n\n        String path = ihVal(\"Path Info\", doc);\n        assertEquals(append, path);\n        assertEquals(\"/EchoServlet/Foo%7Bbar%7D%3C%3E%25/\", ihVal(\"Request URI\", doc));\n    }\n\n    /**\n     Provides HTTP and HTTPS EchoServlet URLs\n     */\n    private static Stream<String> echoUrls() {\n        return Stream.of(EchoServlet.Url, EchoServlet.TlsUrl);\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\")\n    void failsIfNotAuthenticated(String url) throws IOException {\n        String password = AuthFilter.newServerPassword(); // we don't send it, but ensures cache won't hit\n        Connection.Response res = Jsoup.connect(url)\n            .header(AuthFilter.WantsServerAuthentication, \"1\")\n            .ignoreHttpErrors(true)\n            .execute();\n\n        assertEquals(401, res.statusCode());\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\")\n    void canAuthenticate(String url) throws IOException {\n        AtomicInteger count = new AtomicInteger(0);\n        String password = AuthFilter.newServerPassword();\n        Connection.Response res = Jsoup.connect(url)\n            .header(AuthFilter.WantsServerAuthentication, \"1\")\n            .auth(ctx -> {\n                count.incrementAndGet();\n                assertEquals(Authenticator.RequestorType.SERVER, ctx.type());\n                assertEquals(\"localhost\", ctx.url().getHost());\n                assertEquals(AuthFilter.ServerRealm, ctx.realm());\n\n                return ctx.credentials(AuthFilter.ServerUser, password);\n            })\n            .execute();\n\n        assertEquals(1, count.get());\n\n        Document doc = res.parse();\n        assertTrue(ihVal(\"Authorization\", doc).startsWith(\"Basic \")); // tests we set the auth header\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\")\n    void incorrectAuth(String url) throws IOException {\n        Connection session = Jsoup.newSession()\n            .header(AuthFilter.WantsServerAuthentication, \"1\")\n            .ignoreHttpErrors(true);\n\n        String password = AuthFilter.newServerPassword();\n        int code = session.newRequest(url).execute().statusCode(); // no auth sent\n        assertEquals(HttpServletResponse.SC_UNAUTHORIZED, code);\n\n        AtomicInteger count = new AtomicInteger(0);\n        try {\n            Connection.Response res = session.newRequest(url)\n                .auth(ctx -> {\n                    count.incrementAndGet();\n                    return ctx.credentials(AuthFilter.ServerUser, password + \"wrong\"); // incorrect\n                })\n                .execute();\n            assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.statusCode());\n        } catch (IOException e) {\n            assertEquals(\"No credentials provided\", e.getMessage());\n            // In HttpClient, will throw IOE if our password delegate stops providing credentials after too many attempts. So we'll get this error.\n            // In HttpUrlConnection, which would otherwise try 20 times, when the auth stops providing it will cascade to the underyling 401 response (which seems a better path IMO)\n        }\n        assertEquals(MaxAttempts, count.get());\n\n        AtomicInteger successCount = new AtomicInteger(0);\n        Connection.Response successRes = session.newRequest(url)\n            .auth(ctx -> {\n                successCount.incrementAndGet();\n                return ctx.credentials(AuthFilter.ServerUser, password); // correct\n            })\n            .execute();\n        assertEquals(1, successCount.get());\n        assertEquals(HttpServletResponse.SC_OK, successRes.statusCode());\n    }\n\n    // proxy connection tests are in ProxyTest\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"/htmltests/large.html\",\n        \"/htmltests/large.html?\" + FileServlet.SuppressContentLength\n    })\n    void progressListener(String path) throws IOException {\n        String url = FileServlet.urlTo(path);\n        boolean knownContentLength = !url.contains(FileServlet.SuppressContentLength);\n\n        AtomicBoolean seenProgress = new AtomicBoolean(false);\n        AtomicBoolean completed = new AtomicBoolean(false);\n        AtomicInteger numProgress = new AtomicInteger();\n\n        Connection con = Jsoup.connect(url).onResponseProgress((processed, total, percent, response) -> {\n            //System.out.println(\"Processed: \" + processed + \" of \" + total + \" (\" + percent + \"%)\");\n            if (!seenProgress.get()) {\n                seenProgress.set(true);\n                assertEquals(0, processed);\n                assertEquals(knownContentLength ? LargeDocFileLen : -1, total);\n                assertEquals(0.0f, percent);\n\n                assertEquals(200, response.statusCode());\n                String contentLength = response.header(\"Content-Length\");\n                if (knownContentLength) {\n                    assertNotNull(contentLength);\n                    assertEquals(String.valueOf(LargeDocFileLen), contentLength);\n                } else {\n                    assertNull(contentLength);\n                }\n                assertEquals(url, response.url().toExternalForm());\n            }\n            numProgress.getAndIncrement();\n\n            if (percent == 100.0f) {\n                // even if the content-length is not set, we get 100% when the read is completed\n                completed.set(true);\n                assertEquals(LargeDocFileLen, processed);\n            }\n\n        });\n        Document document = con.get();\n\n        assertTrue(seenProgress.get());\n        assertTrue(completed.get());\n\n        // should expect to see events relative to how large the buffer is.\n        int expected = LargeDocFileLen / 8192;\n\n        int num = numProgress.get();\n        // debug log if not in those ranges:\n        if (num < expected * 0.75 || num > expected * 2.5) {\n            System.err.println(\"Expected: \" + expected + \", got: \" + num);\n        }\n        assertTrue(num > expected * 0.75);\n        assertTrue(num < expected * 2.5);\n\n        // check the document works\n        assertEquals(LargeDocTextLen, document.text().length());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/FuzzFixesIT.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n Tests fixes for issues raised by the <a href=\"https://oss-fuzz.com/testcases?project=jsoup\">OSS Fuzz project</a>. As\n some of these are timeout tests - run each file 100 times and ensure under time.\n */\npublic class FuzzFixesIT {\n    static int numIters = 50;\n    static int timeout = 30; // external fuzzer is set to 60 for 100 runs\n    static File testDir = ParseTest.getFile(\"/fuzztests/\");\n\n    private static Stream<File> testFiles() {\n        File[] files = testDir.listFiles();\n        assertNotNull(files);\n        assertTrue(files.length > 10);\n\n        return Stream.of(files);\n    }\n\n    @Disabled // disabled, as these soak up build time and the outcome oughtn't change unless we are refactoring the tree builders. manually execute as desired.\n    @ParameterizedTest\n    @MethodSource(\"testFiles\")\n    void testHtmlParse(File file) throws IOException {\n        long startTime = System.currentTimeMillis();\n        long completeBy = startTime + timeout * 1000L;\n\n        for (int i = 0; i < numIters; i++) {\n            Document doc = Jsoup.parse(file, \"UTF-8\", \"https://example.com/\");\n            assertNotNull(doc);\n            if (System.currentTimeMillis() > completeBy)\n                Assertions.fail(String.format(\"Timeout: only completed %d iters of [%s] in %d seconds\", i, file.getName(), timeout));\n        }\n    }\n    \n    @Disabled // disabled, as these soak up build time and the outcome oughtn't change unless we are refactoring the tree builders. manually execute as desired.\n    @ParameterizedTest\n    @MethodSource(\"testFiles\")\n    void testXmlParse(File file) throws IOException {\n        long startTime = System.currentTimeMillis();\n        long completeBy = startTime + timeout * 1000L;\n\n        for (int i = 0; i < numIters; i++) {\n            Document doc = Jsoup.parse(file, \"UTF-8\", \"https://example.com/\", Parser.xmlParser());\n            assertNotNull(doc);\n            if (System.currentTimeMillis() > completeBy)\n                Assertions.fail(String.format(\"Timeout: only completed %d iters of [%s] in %d seconds\", i, file.getName(), timeout));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/FuzzFixesTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n Tests fixes for issues raised by the OSS Fuzz project @ https://oss-fuzz.com/testcases?project=jsoup. Contains inline\n string cases causing exceptions. Timeout tests are in FuzzFixesIT.\n */\npublic class FuzzFixesTest {\n\n    private static Stream<File> testFiles() {\n        File[] files = FuzzFixesIT.testDir.listFiles();\n        assertNotNull(files);\n        assertTrue(files.length > 10);\n\n        return Stream.of(files);\n    }\n\n    @Test\n    public void blankAbsAttr() {\n        // https://github.com/jhy/jsoup/issues/1541\n        String html = \"b<bodY abs: abs:abs: abs:abs:abs>\";\n        Document doc = Jsoup.parse(html);\n        assertNotNull(doc);\n    }\n\n    @Test\n    public void bookmark() {\n        // https://github.com/jhy/jsoup/issues/1576\n        String html = \"<?a<U<P<A \";\n        Document doc = Jsoup.parse(html);\n        assertNotNull(doc);\n\n        Document xmlDoc = Parser.xmlParser().parseInput(html, \"\");\n        assertNotNull(xmlDoc);\n    }\n\n    @Test void fragment() {\n        Parser.htmlParser().parseFragmentInput(\"<frameset>>l\\u0000<\\u0000<ditl>\\u0000< \\\\\", new Element(\"colgroup\"), \"\");\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"testFiles\")\n    void testHtmlParse(File file) throws IOException {\n        Document doc = Jsoup.parse(file, \"UTF-8\", \"https://example.com/\");\n        assertNotNull(doc);\n        doc = Jsoup.parse(file, \"UTF-8\", \"\"); // no base href attr; so same as a parse(string), which can have subtly different semantics\n        assertNotNull(doc);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"testFiles\")\n    void testHtmlFragmentParse(File file) throws IOException {\n        String html = ParseTest.getFileAsString(file);\n        Document doc = Jsoup.parseBodyFragment(html);\n        assertNotNull(doc);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"testFiles\")\n    void testXmlParse(File file) throws IOException {\n        Document doc = Jsoup.parse(file, \"UTF-8\", \"https://example.com/\", Parser.xmlParser());\n        assertNotNull(doc);\n        doc = Jsoup.parse(file, \"UTF-8\", \"\", Parser.xmlParser()); // no base href attr\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/ParseTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.parser.ParseErrorList;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.*;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.zip.GZIPInputStream;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Integration test: parses from real-world example HTML.\n *\n * @author Jonathan Hedley, jonathan@hedley.net\n */\npublic class ParseTest {\n    @Test\n    public void testHtml5Charset() throws IOException {\n        // test that <meta charset=\"gb2312\"> works\n        File in = getFile(\"/htmltests/meta-charset-1.html\");\n        Document doc = Jsoup.parse(in, null, \"http://example.com/\"); //gb2312, has html5 <meta charset>\n        assertEquals(\"新\", doc.text());\n        assertEquals(\"GB2312\", doc.outputSettings().charset().displayName());\n\n        // double check, no charset, falls back to utf8 which is incorrect\n        in = getFile(\"/htmltests/meta-charset-2.html\"); //\n        doc = Jsoup.parse(in, null, \"http://example.com\"); // gb2312, no charset\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().displayName());\n        assertNotEquals(\"新\", doc.text());\n\n        // confirm fallback to utf8\n        in = getFile(\"/htmltests/meta-charset-3.html\");\n        doc = Jsoup.parse(in, null, \"http://example.com/\"); // utf8, no charset\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().displayName());\n        assertEquals(\"新\", doc.text());\n    }\n\n    @Test\n    public void testBrokenHtml5CharsetWithASingleDoubleQuote() throws IOException {\n        InputStream in = inputStreamFrom(\"<html>\\n\" +\n                \"<head><meta charset=UTF-8\\\"></head>\\n\" +\n                \"<body></body>\\n\" +\n                \"</html>\");\n        Document doc = Jsoup.parse(in, null, \"http://example.com/\");\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().displayName());\n    }\n\n    @Test\n    public void testLowercaseUtf8Charset() throws IOException {\n        File in = getFile(\"/htmltests/lowercase-charset-test.html\");\n        Document doc = Jsoup.parse(in, null);\n\n        Element form = doc.select(\"#form\").first();\n        assertEquals(2, form.children().size());\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().name());\n    }\n\n    @Test\n    public void testXwiki() throws IOException {\n        // https://github.com/jhy/jsoup/issues/1324\n        // this tests that when in CharacterReader we hit a buffer while marked, we preserve the mark when buffered up and can rewind\n        File in = getFile(\"/htmltests/xwiki-1324.html.gz\");\n        Document doc = Jsoup.parse(in, null, \"https://localhost/\");\n        assertEquals(\"XWiki Jetty HSQLDB 12.1-SNAPSHOT\", doc.select(\"#xwikiplatformversion\").text());\n\n        // was getting busted at =userdirectory, because it hit the bufferup point but the mark was then lost. so\n        // updated to preserve the mark.\n        String wantHtml = \"<a class=\\\"list-group-item\\\" data-id=\\\"userdirectory\\\" href=\\\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=userdirectory\\\" title=\\\"Customize the user directory live table.\\\">User Directory</a>\";\n        assertEquals(wantHtml, doc.select(\"[data-id=userdirectory]\").outerHtml());\n    }\n\n    @Test\n    public void testXwikiExpanded() throws IOException {\n        // https://github.com/jhy/jsoup/issues/1324\n        // this tests that if there is a huge illegal character reference, we can get through a buffer and rewind, and still catch that it's an invalid refence,\n        // and the parse tree is correct.\n        File in = getFile(\"/htmltests/xwiki-edit.html.gz\");\n        Parser parser = Parser.htmlParser();\n        Document doc = Jsoup.parse(new GZIPInputStream(new FileInputStream(in)), \"UTF-8\", \"https://localhost/\", parser.setTrackErrors(100));\n        ParseErrorList errors = parser.getErrors();\n\n        assertEquals(\"XWiki Jetty HSQLDB 12.1-SNAPSHOT\", doc.select(\"#xwikiplatformversion\").text());\n        assertEquals(0, errors.size()); // not an invalid reference because did not look legit\n\n        // was getting busted at =userdirectory, because it hit the bufferup point but the mark was then lost. so\n        // updated to preserve the mark.\n        String wantHtml = \"<a class=\\\"list-group-item\\\" data-id=\\\"userdirectory\\\" href=\\\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;RIGHTHERERIGHTHERERIGHTHERERIGHTHERE\";\n        assertTrue(doc.select(\"[data-id=userdirectory]\").outerHtml().startsWith(wantHtml));\n    }\n\n    @Test public void testWikiExpandedFromString() throws IOException {\n        File in = getFile(\"/htmltests/xwiki-edit.html.gz\");\n        String html = getFileAsString(in);\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"XWiki Jetty HSQLDB 12.1-SNAPSHOT\", doc.select(\"#xwikiplatformversion\").text());\n        String wantHtml = \"<a class=\\\"list-group-item\\\" data-id=\\\"userdirectory\\\" href=\\\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;RIGHTHERERIGHTHERERIGHTHERERIGHTHERE\";\n        assertTrue(doc.select(\"[data-id=userdirectory]\").outerHtml().startsWith(wantHtml));\n    }\n\n    @Test public void testWikiFromString() throws IOException {\n        File in = getFile(\"/htmltests/xwiki-1324.html.gz\");\n        String html = getFileAsString(in);\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"XWiki Jetty HSQLDB 12.1-SNAPSHOT\", doc.select(\"#xwikiplatformversion\").text());\n        String wantHtml = \"<a class=\\\"list-group-item\\\" data-id=\\\"userdirectory\\\" href=\\\"/xwiki/bin/admin/XWiki/XWikiPreferences?editor=globaladmin&amp;section=userdirectory\\\" title=\\\"Customize the user directory live table.\\\">User Directory</a>\";\n        assertEquals(wantHtml, doc.select(\"[data-id=userdirectory]\").outerHtml());\n    }\n\n    @Test public void testFileParseNoCharsetMethod() throws IOException {\n        File in = getFile(\"/htmltests/xwiki-1324.html.gz\");\n        Document doc = Jsoup.parse(in);\n        assertEquals(\"XWiki Jetty HSQLDB 12.1-SNAPSHOT\", doc.select(\"#xwikiplatformversion\").text());\n    }\n\n\n    public static File getFile(String resourceName) {\n        try {\n            URL resource = ParseTest.class.getResource(resourceName);\n            return resource != null ? new File(resource.toURI()) : new File(\"/404\");\n        } catch (URISyntaxException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    public static Path getPath(String resourceName) {\n        try {\n            URL resource = ParseTest.class.getResource(resourceName);\n            return resource != null ? Paths.get(resource.toURI()) : Paths.get(\"/404\");\n        } catch (URISyntaxException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    public static InputStream inputStreamFrom(String s) {\n        return new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));\n    }\n\n    public static String getFileAsString(File file) throws IOException {\n        byte[] bytes;\n        if (file.getName().endsWith(\".gz\")) {\n            InputStream stream = new GZIPInputStream(new FileInputStream(file));\n            ByteBuffer byteBuffer = DataUtil.readToByteBuffer(stream, 0);\n            bytes = new byte[byteBuffer.limit()];\n            System.arraycopy(byteBuffer.array(), 0, bytes, 0, byteBuffer.limit());\n        } else {\n            bytes = Files.readAllBytes(file.toPath());\n        }\n        return new String(bytes);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/ProxyTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.integration.servlets.AuthFilter;\nimport org.jsoup.integration.servlets.EchoServlet;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.integration.servlets.HelloServlet;\nimport org.jsoup.integration.servlets.ProxyServlet;\nimport org.jsoup.integration.servlets.RedirectServlet;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.helper.AuthenticationHandlerTest.MaxAttempts;\nimport static org.jsoup.integration.ConnectTest.ihVal;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests Jsoup.connect proxy support */\npublic class ProxyTest {\n    private static String echoUrl;\n    private static TestServer.ProxySettings proxy;\n\n    @BeforeAll\n    public static void setUp() {\n        echoUrl = EchoServlet.Url;\n        proxy = ProxyServlet.ProxySettings;\n    }\n\n    @ParameterizedTest @MethodSource(\"helloUrls\")\n    void fetchViaProxy(String url) throws IOException {\n        Connection con = Jsoup.connect(url)\n            .proxy(proxy.hostname, proxy.port);\n\n        Connection.Response res = con.execute();\n        if (url.startsWith(\"http:/\")) assertVia(res); // HTTPS CONNECT won't have Via\n\n        Document doc = res.parse();\n        Element p = doc.expectFirst(\"p\");\n        assertEquals(\"Hello, World!\", p.text());\n    }\n\n    private static Stream<String> helloUrls() {\n        return Stream.of(HelloServlet.Url, HelloServlet.TlsUrl);\n    }\n\n    private static Stream<String> echoUrls() {\n        return Stream.of(EchoServlet.Url, EchoServlet.TlsUrl);\n    }\n\n    private static void assertVia(Connection.Response res) {\n        assertEquals(res.header(\"Via\"), ProxyServlet.Via);\n    }\n\n    @Test void redirectViaProxy() throws IOException {\n        Connection.Response res = Jsoup\n            .connect(RedirectServlet.Url)\n            .data(RedirectServlet.LocationParam, echoUrl)\n            .header(\"Random-Header-name\", \"hello\")\n            .proxy(proxy.hostname, proxy.port)\n            .execute();\n\n        assertVia(res);\n        Document doc = res.parse();\n        assertEquals(echoUrl, doc.location());\n        assertEquals(\"hello\", ihVal(\"Random-Header-name\", doc));\n        assertVia(res);\n    }\n\n    @Test void proxyForSession() throws IOException {\n        Connection session = Jsoup.newSession().proxy(proxy.hostname, proxy.port);\n\n        Connection.Response medRes = session.newRequest(FileServlet.urlTo(\"/htmltests/medium.html\")).execute();\n        Connection.Response largeRes = session.newRequest(FileServlet.urlTo(\"/htmltests/large.html\")).execute();\n\n        assertVia(medRes);\n        assertVia(largeRes);\n        assertEquals(\"Medium HTML\", medRes.parse().title());\n        assertEquals(\"Large HTML\", largeRes.parse().title());\n\n        Connection.Response smedRes = session.newRequest(FileServlet.tlsUrlTo(\"/htmltests/medium.html\")).execute();\n        Connection.Response slargeRes = session.newRequest(FileServlet.tlsUrlTo(\"/htmltests/large.html\")).execute();\n\n        assertEquals(\"Medium HTML\", smedRes.parse().title());\n        assertEquals(\"Large HTML\", slargeRes.parse().title());\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\")\n    void canAuthenticateToProxy(String url) throws IOException {\n        int closed = TestServer.closeAuthedProxyConnections(); // reset any existing authed connections from previous tests, so we can test the auth flow\n\n        // the proxy wants auth, but not the server. HTTP and HTTPS, so tests direct proxy and CONNECT\n        Connection session = Jsoup.newSession()\n            .proxy(proxy.hostname, proxy.authedPort)\n            .ignoreHttpErrors(true)\n            .ignoreContentType(true); // ignore content type, as error served may not have a content type\n        String password = AuthFilter.newProxyPassword();\n\n        // fail first\n        try {\n            Connection.Response execute = session.newRequest(url)\n                .execute();\n            int code = execute.statusCode(); // no auth sent\n            assertEquals(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED, code);\n        } catch (IOException e) {\n            assertAuthRequiredException(e);\n        }\n\n        try {\n            AtomicInteger count = new AtomicInteger(0);\n            Connection.Response res = session.newRequest(url)\n                .auth(ctx -> {\n                    count.incrementAndGet();\n                    return ctx.credentials(AuthFilter.ProxyUser, password + \"wrong\"); // incorrect\n                })\n                .execute();\n            assertEquals(MaxAttempts, count.get());\n            assertEquals(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED, res.statusCode());\n        } catch (IOException e) {\n            assertAuthRequiredException(e);\n        }\n\n        AtomicInteger successCount = new AtomicInteger(0);\n        Connection.Response successRes = session.newRequest(url)\n            .auth(ctx -> {\n                successCount.incrementAndGet();\n                return ctx.credentials(AuthFilter.ProxyUser, password); // correct\n            })\n            .execute();\n        assertEquals(1, successCount.get());\n        assertEquals(HttpServletResponse.SC_OK, successRes.statusCode());\n    }\n\n    static void assertAuthRequiredException(IOException e) {\n        // in CONNECT (for the HTTPS url), URLConnection will throw the proxy connect as a Stringly typed IO exception - \"Unable to tunnel through proxy. Proxy returns \"HTTP/1.1 407 Proxy Authentication Required\"\". (Not a response code)\n        // Alternatively, some platforms (?) will report: \"No credentials provided\"\n        String err = e.getMessage();\n        if (!(err.contains(\"407\") || err.contains(\"No credentials provided\") || err.contains(\"exch.exchImpl\"))) {\n            // https://github.com/jhy/jsoup/pull/2403 - Ubuntu Azul 25 throws `Cannot invoke \"jdk.internal.net.http.ExchangeImpl.cancel(java.io.IOException)\" because \"exch.exchImpl\" is null` here but is just from cancelling the 407 req\n            System.err.println(\"Not a 407 exception? \" + e.getClass());\n            e.printStackTrace(System.err);\n            fail(\"Expected 407 Proxy Authentication Required, got: \" + err);\n        }\n    }\n\n    @ParameterizedTest @MethodSource(\"echoUrls\")\n    void canAuthToProxyAndServer(String url) throws IOException {\n        String serverPassword = AuthFilter.newServerPassword();\n        String proxyPassword = AuthFilter.newProxyPassword();\n        AtomicInteger count = new AtomicInteger(0);\n\n        Connection session = Jsoup.newSession() // both proxy and server will want auth\n            .proxy(proxy.hostname, proxy.authedPort)\n            .header(AuthFilter.WantsServerAuthentication, \"1\")\n            .auth(auth -> {\n                count.incrementAndGet();\n\n                if (auth.isServer()) {\n                    assertEquals(url, auth.url().toString());\n                    assertEquals(AuthFilter.ServerRealm, auth.realm());\n                    return auth.credentials(AuthFilter.ServerUser, serverPassword);\n                } else {\n                    assertTrue(auth.isProxy());\n                    return auth.credentials(AuthFilter.ProxyUser, proxyPassword);\n                }\n            });\n\n\n        Connection.Response res = session.newRequest(url).execute();\n        assertEquals(200, res.statusCode());\n        assertEquals(2, count.get()); // hit server and proxy auth stages\n        assertEquals(\"Webserver Environment Variables\", res.parse().title());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/SafelistExtensionTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.safety.Safelist;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n Check that we can extend Safelist methods\n */\npublic class SafelistExtensionTest {\n    @Test public void canCustomizeSafeTests() {\n        OpenSafelist openSafelist = new OpenSafelist(Safelist.relaxed());\n        Safelist safelist = Safelist.relaxed();\n\n        String html = \"<p><opentag openattr>Hello</opentag></p>\";\n\n        String openClean = Jsoup.clean(html, openSafelist);\n        String clean = Jsoup.clean(html, safelist);\n\n        assertEquals(\"<p><opentag openattr=\\\"\\\">Hello</opentag></p>\", TextUtil.stripNewlines(openClean));\n        assertEquals(\"<p>Hello</p>\", clean);\n    }\n\n    // passes tags and attributes starting with \"open\"\n    private static class OpenSafelist extends Safelist {\n        public OpenSafelist(Safelist safelist) {\n            super(safelist);\n        }\n\n        @Override\n        public boolean isSafeAttribute(String tagName, Element el, Attribute attr) {\n            if (attr.getKey().startsWith(\"open\"))\n                return true;\n            return super.isSafeAttribute(tagName, el, attr);\n        }\n\n        @Override\n        public boolean isSafeTag(String tag) {\n            if (tag.startsWith(\"open\"))\n                return true;\n            return super.isSafeTag(tag);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/SessionIT.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.integration.servlets.SlowRider;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.io.UncheckedIOException;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/** Integration tests to test longer running Connection */\npublic class SessionIT {\n    @BeforeAll\n    public static void setUp() {\n        TestServer.start();\n    }\n\n    @Test\n    public void multiThread() throws InterruptedException {\n        int numThreads = 20;\n        int numThreadLoops = 5;\n        String[] urls = {\n            FileServlet.urlTo(\"/htmltests/medium.html\"),\n            FileServlet.urlTo(\"/htmltests/upload-form.html\"),\n            FileServlet.urlTo(\"/htmltests/comments.html\"),\n            FileServlet.urlTo(\"/htmltests/large.html\"),\n        };\n        String[] titles = {\n            \"Medium HTML\",\n            \"Upload Form Test\",\n            \"A Certain Kind of Test\",\n            \"Large HTML\"\n        };\n        ThreadCatcher catcher = new ThreadCatcher();\n\n        Connection session = Jsoup.newSession();\n\n        Thread[] threads = new Thread[numThreads];\n        for (int threadNum = 0; threadNum < numThreads; threadNum++) {\n            Thread thread = new Thread(() -> {\n                for (int loop = 0; loop < numThreadLoops; loop++) {\n                    for (int i = 0; i < urls.length; i++) {\n                        try {\n                            Document doc = session.newRequest().url(urls[i]).get();\n                            assertEquals(titles[i], doc.title());\n                        } catch (IOException e) {\n                            throw new UncheckedIOException(e);\n                        }\n                    }\n                }\n            });\n            thread.setName(\"Runner-\" + threadNum);\n            thread.start();\n            thread.setUncaughtExceptionHandler(catcher);\n            threads[threadNum] = thread;\n        }\n\n        // now join them all\n        for (Thread thread : threads) {\n            thread.join();\n        }\n\n        assertEquals(0, catcher.exceptionCount.get());\n    }\n\n    // test that we throw a nice clear exception if you try to multi-thread by forget .newRequest()\n    @Test\n    public void multiThreadWithoutNewRequestBlowsUp() throws InterruptedException {\n        int numThreads = 5;\n        String url = SlowRider.Url + \"?\" + SlowRider.MaxTimeParam + \"=20000\"; // this makes sure that the first req is still executing whilst the others run\n        String title = \"Slow Rider\";\n\n        ThreadCatcher catcher = new ThreadCatcher();\n        Connection session = Jsoup.newSession();\n\n        // run first slow request\n        AtomicInteger successful = new AtomicInteger();\n        Thread slow = new Thread(() -> {\n            try {\n                Document doc = session.url(url).get();\n                assertNotNull(doc);\n            } catch (IOException e) {\n                if (!isInterruptedException(e))\n                    throw new UncheckedIOException(e);\n            }\n        });\n        slow.start();\n        Thread.sleep(100); // yield so that thread can start before the next do\n\n        // spawn others, should fail\n        Thread[] threads = new Thread[numThreads];\n        for (int threadNum = 0; threadNum < numThreads; threadNum++) {\n            Thread thread = new Thread(() -> {\n                try {\n                    Document doc = session.url(url).get();\n                    assertEquals(title, doc.title());\n                    successful.getAndIncrement();\n                } catch (IOException e) {\n                    throw new UncheckedIOException(e);\n                }\n            });\n            thread.setName(\"Runner-\" + threadNum);\n            thread.setUncaughtExceptionHandler(catcher);\n            thread.start();\n            threads[threadNum] = thread;\n        }\n\n        // now join them all\n        for (Thread thread : threads) {\n            thread.join();\n        }\n        // cancel the slow runner so we can wrap up the test quicker\n        slow.interrupt();\n\n        // only one should have passed, rest should have blown up (assuming the started whilst other was running)\n        //assertEquals(numThreads - 1, catcher.multiThreadExceptions.get());\n        //assertEquals(numThreads - 1, catcher.exceptionCount.get());\n\n        /* The checks above work when all 20 threads are executed within 10 seconds. However, depending on how cloudy it\n         is when the CI jobs are run, they may not all complete in time. As of writing that appears most commonly on the\n         macOS runners, which appear overtaxed. That makes this test flaky. So we relax the test conditions, and make\n         sure at least just one passed and one failed. That's OK in prod as well, because we are only concerned about\n         concurrent execution, which the impl does detect correctly. */\n        assertEquals(0, successful.get());\n        assertTrue(catcher.multiThreadExceptions.get() > 0);\n        assertEquals(catcher.multiThreadExceptions.get(), catcher.exceptionCount.get()); // all exceptions are multi-threaded\n    }\n\n    @Test\n    public void multiThreadWithProgressListener() throws InterruptedException {\n        // tests that we can use one progress listener for multiple URLs and threads.\n        int numThreads = 10;\n        String[] urls = {\n            FileServlet.urlTo(\"/htmltests/medium.html\"),\n            FileServlet.urlTo(\"/htmltests/upload-form.html\"),\n            FileServlet.urlTo(\"/htmltests/comments.html\"),\n            FileServlet.urlTo(\"/htmltests/large.html\"),\n        };\n        Set<String> seenUrls = ConcurrentHashMap.newKeySet();\n        AtomicInteger completedCount = new AtomicInteger(0);\n        ThreadCatcher catcher = new ThreadCatcher();\n\n        Connection session = Jsoup.newSession()\n            .onResponseProgress((processed, total, percent, response) -> {\n                if (percent == 100.0f) {\n                    //System.out.println(\"Completed \" + Thread.currentThread().getName() + \"- \" + response.url());\n                    seenUrls.add(response.url().toExternalForm());\n                    completedCount.incrementAndGet();\n                }\n            });\n\n        Thread[] threads = new Thread[numThreads];\n        for (int threadNum = 0; threadNum < numThreads; threadNum++) {\n            Thread thread = new Thread(() -> {\n                for (String url : urls) {\n                    try {\n                        Connection con = session.newRequest().url(url);\n                        con.get();\n                    } catch (IOException e) {\n                        throw new UncheckedIOException(e);\n                    }\n                }\n            });\n            thread.setName(\"Runner-\" + threadNum);\n            thread.setUncaughtExceptionHandler(catcher);\n            thread.start();\n            threads[threadNum] = thread;\n        }\n\n        // now join them all\n        for (Thread thread : threads) {\n            thread.join();\n        }\n\n        assertEquals(0, catcher.exceptionCount.get());\n        assertEquals(urls.length, seenUrls.size());\n        assertEquals(urls.length * numThreads, completedCount.get());\n    }\n\n\n    static class ThreadCatcher implements Thread.UncaughtExceptionHandler {\n        AtomicInteger exceptionCount = new AtomicInteger();\n        AtomicInteger multiThreadExceptions = new AtomicInteger();\n\n        @Override\n        public void uncaughtException(Thread t, Throwable e) {\n            if (e.getMessage() != null && e.getMessage().contains(\"Multiple threads\")) {\n                multiThreadExceptions.incrementAndGet();\n            } else if (!isInterruptedException(e)) {\n                e.printStackTrace();\n            }\n            exceptionCount.incrementAndGet();\n        }\n    }\n\n    private static boolean isInterruptedException(Throwable e) {\n        Throwable cause = e;\n        while (cause != null) {\n            if (cause instanceof InterruptedException) return true;\n            cause = cause.getCause();\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/SessionTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.integration.servlets.CookieServlet;\nimport org.jsoup.integration.servlets.EchoServlet;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.parser.TagSet;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class SessionTest {\n    @BeforeAll\n    public static void setUp() {\n        TestServer.start();\n    }\n\n    private static Elements keyEls(String key, Document doc) {\n        return doc.select(\"th:contains(\" + key + \") + td\");\n    }\n\n    private static String keyText(String key, Document doc) {\n        return doc.selectFirst(\"th:contains(\" + key + \") + td\").text();\n    }\n\n    @Test\n    public void testPathScopedCookies() throws IOException {\n        final Connection session = Jsoup.newSession();\n        final String userAgent = \"Jsoup Testalot v0.1\";\n\n        session.userAgent(userAgent);\n        session.url(CookieServlet.Url);\n\n        // should have no cookies:\n        Connection con1 = session.newRequest();\n        Document doc1 = con1.get();\n        assertEquals(0, doc1.select(\"table tr\").size()); // none sent to servlet\n\n        // set the cookies\n        Connection con2 = session.newRequest().data(CookieServlet.SetCookiesParam, \"1\");\n        Document doc2 = con2.get();\n        assertEquals(0, doc2.select(\"table tr\").size());  // none sent to servlet - we just got them!\n        Map<String, String> cookies = con2.response().cookies(); // simple cookie response, all named \"One\", so should be last sent\n        assertEquals(2, cookies.size());\n        assertEquals(\"EchoServlet\", cookies.get(\"One\"));\n\n        // test that all response cookies are present, even if would not be sent for this request path (i.e. cookies() res can be set on a req, without using sessions)\n        assertEquals(\"Override\", cookies.get(\"Two\"));\n\n        // todo - interrogate cookie-store\n\n        // check that they are sent and filtered to the right path\n        Connection con3 = session.newRequest();\n        Document doc3 = con3.get();\n        assertCookieServlet(doc3);\n\n        Document echo = session.newRequest().url(EchoServlet.Url).get();\n        assertEchoServlet(echo);\n        assertEquals(userAgent, keyText(\"User-Agent\", echo)); // check that customer user agent sent on session arrived\n\n        // check that cookies aren't set out of the session\n        Document doc4 = Jsoup.newSession().url(CookieServlet.Url).get();\n        assertEquals(0, doc4.select(\"table tr\").size()); // none sent to servlet\n\n        // check can add local ones also\n        Document doc5 = session.newRequest().cookie(\"Bar\", \"Qux\").get();\n        Elements doc5Bar = keyEls(\"Bar\", doc5);\n        assertEquals(\"Qux\", doc5Bar.first().text());\n    }\n\n    // validate that only cookies set by cookie servlet get to the cookie servlet path\n    private void assertCookieServlet(Document doc) {\n        assertEquals(2, doc.select(\"table tr\").size());  // two of three sent to servlet (/ and /CookieServlet)\n        Elements doc3Els = keyEls(\"One\", doc);\n        assertEquals(2, doc3Els.size());\n        assertEquals(\"CookieServlet\", doc3Els.get(0).text()); // ordered by most specific path\n        assertEquals(\"Root\", doc3Els.get(1).text()); // ordered by most specific path\n    }\n\n    // validate that only for echo servlet\n    private void assertEchoServlet(Document doc) {\n        Elements echoEls = keyEls(\"Cookie: One\", doc);  // two of three sent to servlet (/ and /EchoServlet)\n        assertEquals(2, echoEls.size());\n        assertEquals(\"EchoServlet\", echoEls.get(0).text()); // ordered by most specific path - /Echo\n        assertEquals(\"Root\", echoEls.get(1).text()); // ordered by most specific path - /\n    }\n\n    @Test\n    public void testPathScopedCookiesOnRedirect() throws IOException {\n        Connection session = Jsoup.newSession();\n\n        Document doc1 = session.newRequest()\n            .url(CookieServlet.Url)\n            .data(CookieServlet.LocationParam, EchoServlet.Url)\n            .data(CookieServlet.SetCookiesParam, \"1\")\n            .get();\n\n        // we should be redirected to the echo servlet with cookies\n        assertEquals(EchoServlet.Url, doc1.location());\n        assertEchoServlet(doc1); // checks we only have /echo cookies\n\n        Document doc2 = session.newRequest()\n            .url(EchoServlet.Url)\n            .get();\n        assertEchoServlet(doc2); // test retained in session\n\n        Document doc3 = session.newRequest()\n            .url(CookieServlet.Url)\n            .get();\n        assertCookieServlet(doc3); // and so were the /cookie cookies\n    }\n\n    @Test\n    public void testCanChangeParsers() throws IOException {\n        Connection session = Jsoup.newSession().parser(Parser.xmlParser());\n\n        String xmlUrl = FileServlet.urlTo(\"/htmltests/xml-test.xml\");\n        String xmlVal = \"<doc><val>One<val>Two</val>Three</val></doc>\\n\";\n\n        Document doc1 = session.newRequest().url(xmlUrl).get();\n        assertEquals(xmlVal, doc1.html()); // not HTML normed, used XML parser\n\n        Document doc2 = session.newRequest().parser(Parser.htmlParser()).url(xmlUrl).get();\n        assertTrue(doc2.html().startsWith(\"<html>\"));\n\n        Document doc3 = session.newRequest().url(xmlUrl).get();\n        assertEquals(xmlVal, doc3.html()); // did not blow away xml default\n    }\n\n    @Test\n    public void sessionTagSetDoesNotMutateRoot() {\n        Connection session = Jsoup.newSession();\n        TagSet rootTags = session.request().parser().tagSet();\n\n        int rootNamespacesBefore = tagSetNamespaceCount(rootTags);\n\n        Connection request = session.newRequest();\n        Parser parser = request.request().parser();\n        parser.parseInput(\"<custom>One <b>Two</b></custom>\", \"http://example.com/\");\n\n        int rootNamespacesAfter = tagSetNamespaceCount(rootTags);\n        assertEquals(rootNamespacesBefore, rootNamespacesAfter);\n    }\n\n    @Test\n    public void sessionTagSetCustomizerDoesNotMutateRoot() {\n        Connection session = Jsoup.newSession();\n        TagSet rootTags = session.request().parser().tagSet();\n        rootTags.onNewTag(tag -> {\n            if (!tag.isKnownTag())\n                tag.set(Tag.RcData);\n        });\n\n        int rootNamespacesBefore = tagSetNamespaceCount(rootTags);\n\n        Connection request = session.newRequest();\n        Parser parser = request.request().parser();\n        Document doc = parser.parseInput(\"<custom>One <b>Two</b></custom>\", \"https://example.com/\");\n        assertEquals(0, doc.select(\"custom b\").size());\n\n        int rootNamespacesAfter = tagSetNamespaceCount(rootTags);\n        assertEquals(rootNamespacesBefore, rootNamespacesAfter);\n    }\n\n    private static int tagSetNamespaceCount(TagSet tagSet) {\n        try {\n            Field tagsField = TagSet.class.getDeclaredField(\"tags\");\n            tagsField.setAccessible(true);\n            Map<?, ?> tags = (Map<?, ?>) tagsField.get(tagSet);\n            return tags.size();\n        } catch (ReflectiveOperationException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/TestServer.java",
    "content": "package org.jsoup.integration;\n\nimport org.eclipse.jetty.http.HttpVersion;\nimport org.eclipse.jetty.server.Connector;\nimport org.eclipse.jetty.server.HttpConfiguration;\nimport org.eclipse.jetty.server.HttpConnectionFactory;\nimport org.eclipse.jetty.server.SecureRequestCustomizer;\nimport org.eclipse.jetty.server.Server;\nimport org.eclipse.jetty.server.ServerConnector;\nimport org.eclipse.jetty.server.SslConnectionFactory;\nimport org.eclipse.jetty.server.handler.HandlerWrapper;\nimport org.eclipse.jetty.servlet.FilterHolder;\nimport org.eclipse.jetty.servlet.FilterMapping;\nimport org.eclipse.jetty.servlet.ServletHandler;\nimport org.eclipse.jetty.util.log.Log;\nimport org.eclipse.jetty.util.log.StdErrLog;\nimport org.eclipse.jetty.util.ssl.SslContextFactory;\nimport org.jsoup.integration.servlets.AuthFilter;\nimport org.jsoup.integration.servlets.BaseServlet;\nimport org.jsoup.integration.servlets.ProxyServlet;\n\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.nio.file.Files;\nimport java.security.KeyManagementException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class TestServer {\n    static int Port;\n    static int TlsPort;\n\n    private static final String Localhost = \"localhost\";\n    private static final String KeystorePassword = \"hunter2\";\n\n    private static final Server Jetty = newServer();\n    private static final ServletHandler JettyHandler = new ServletHandler();\n    private static final Server Proxy = newServer();\n    private static final Server AuthedProxy = newServer();\n    private static final HandlerWrapper ProxyHandler = new HandlerWrapper();\n    private static final HandlerWrapper AuthedProxyHandler = new HandlerWrapper();\n    private static final ProxySettings ProxySettings = new ProxySettings();\n\n    private static Server newServer() {\n        // logs to stdout, so not highlighted as errors in Maven test runs\n        StdErrLog logger = new StdErrLog();\n        logger.setStdErrStream(System.out);\n        Log.setLog(logger);\n\n        return new Server(new InetSocketAddress(Localhost, 0));\n    }\n\n    static {\n        Jetty.setHandler(JettyHandler);\n        Proxy.setHandler(ProxyHandler);\n        AuthedProxy.setHandler(AuthedProxyHandler);\n\n        // TLS setup:\n        try {\n            File keystoreFile = ParseTest.getFile(\"/local-cert/server.pfx\");\n            if (!keystoreFile.exists()) throw new FileNotFoundException(keystoreFile.toString());\n            addHttpsConnector(keystoreFile, Jetty);\n            setupDefaultTrust(keystoreFile);\n        } catch (Exception e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    private TestServer() {\n    }\n\n    public static void start() {\n        synchronized (Jetty) {\n            if (Jetty.isStarted()) return;\n\n            try {\n                Jetty.start();\n                JettyHandler.addFilterWithMapping(new FilterHolder(new AuthFilter(false, false)), \"/*\", FilterMapping.ALL);\n                Connector[] jcons = Jetty.getConnectors();\n                Port = ((ServerConnector) jcons[0]).getLocalPort();\n                TlsPort = ((ServerConnector) jcons[1]).getLocalPort();\n\n                ProxyHandler.setHandler(ProxyServlet.createHandler(false)); // includes proxy, CONNECT proxy, and Auth filters\n                Proxy.start();\n                ProxySettings.port = ((ServerConnector) Proxy.getConnectors()[0]).getLocalPort();\n\n                AuthedProxyHandler.setHandler(ProxyServlet.createHandler(true));\n                AuthedProxy.start();\n                ProxySettings.authedPort = ((ServerConnector) AuthedProxy.getConnectors()[0]).getLocalPort();\n            } catch (Exception e) {\n                throw new IllegalStateException(e);\n            }\n        }\n    }\n\n    /**\n     Close any current connections to the authed proxy. Tunneled connections only authenticate in their first\n     CONNECT, and may be kept alive and reused. So when we want to test unauthed - authed flows, we need to disconnect\n     them first.\n     */\n    static int closeAuthedProxyConnections() {\n        ServerConnector connector = (ServerConnector) AuthedProxy.getConnectors()[0];\n        AtomicInteger count = new AtomicInteger();\n        connector.getConnectedEndPoints().forEach(endPoint -> {\n            endPoint.close();\n            count.getAndIncrement();\n        });\n        return count.get();\n    }\n\n    public static ServletUrls map(Class<? extends BaseServlet> servletClass) {\n        synchronized (Jetty) {\n            if (!Jetty.isStarted())\n                start(); // if running out of the test cases\n\n            String path = \"/\" + servletClass.getSimpleName();\n            JettyHandler.addServletWithMapping(servletClass, path + \"/*\");\n            String url = \"http://\" + Localhost + \":\" + Port + path;\n            String tlsUrl = \"https://\" + Localhost + \":\" + TlsPort + path;\n\n            return new ServletUrls(url, tlsUrl);\n        }\n    }\n\n    public static class ServletUrls {\n        public final String url;\n        public final String tlsUrl;\n\n        public ServletUrls(String url, String tlsUrl) {\n            this.url = url;\n            this.tlsUrl = tlsUrl;\n        }\n    }\n\n    public static ProxySettings proxySettings() {\n        synchronized (Jetty) {\n            if (!Jetty.isStarted())\n                start();\n\n            return ProxySettings;\n        }\n    }\n\n    //public static String proxy\n    public static class ProxySettings {\n        final String hostname = Localhost;\n        int port;\n        int authedPort;\n    }\n\n    private static void addHttpsConnector(File keystoreFile, Server server) {\n        // Cribbed from https://github.com/jetty/jetty.project/blob/jetty-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java\n        SslContextFactory sslContextFactory = new SslContextFactory.Server();\n        String path = keystoreFile.getAbsolutePath();\n        sslContextFactory.setKeyStorePath(path);\n        sslContextFactory.setKeyStorePassword(KeystorePassword);\n        sslContextFactory.setKeyManagerPassword(KeystorePassword);\n        sslContextFactory.setTrustStorePath(path);\n        sslContextFactory.setTrustStorePassword(KeystorePassword);\n\n        HttpConfiguration httpConfig = new HttpConfiguration();\n        httpConfig.setSecureScheme(\"https\");\n        HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);\n        httpsConfig.addCustomizer(new SecureRequestCustomizer());\n\n        ServerConnector sslConnector = new ServerConnector(\n            server,\n            new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),\n            new HttpConnectionFactory(httpsConfig));\n        sslConnector.setHost(Localhost);\n        server.addConnector(sslConnector);\n    }\n\n    private static void setupDefaultTrust(File keystoreFile) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException {\n        // Configure HttpsUrlConnection (jsoup) to trust (only) this cert\n        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());\n        trustStore.load(Files.newInputStream(keystoreFile.toPath()), KeystorePassword.toCharArray());\n        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n        trustManagerFactory.init(trustStore);\n        TrustManager[] managers = trustManagerFactory.getTrustManagers();\n        SSLContext tls = SSLContext.getInstance(\"TLS\");\n        tls.init(null, managers, null);\n        SSLContext.setDefault(tls);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/AuthFilter.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport javax.servlet.Filter;\nimport javax.servlet.FilterChain;\nimport javax.servlet.FilterConfig;\nimport javax.servlet.ServletException;\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\n\n/**\n A filter to test basic authenticated requests. If the request header \"X-Wants-Authentication\" is set, or if\n alwaysWantsAuth is enabled, the filter is invoked, and requests must send the correct user authentication details.\n */\npublic class AuthFilter implements Filter {\n    public static final String WantsServerAuthentication = \"X-Wants-ServerAuthentication\";\n    public static final String ServerUser = \"admin\";\n    public static final String ServerRealm = \"jsoup test server authentication realm\";\n    private static volatile String ServerPassword = newServerPassword();\n\n    public static final String WantsProxyAuthentication = \"X-Wants-ProxyAuthentication\";\n    public static final String ProxyUser = \"foxyproxy\";\n    public static final String ProxyRealm = \"jsoup test proxy authentication realm\";\n    private static volatile String ProxyPassword = newProxyPassword();\n\n    private final boolean alwaysWantsAuth; // we run a particular port that always wants auth - so the CONNECT tunnels can be authed. (The Java proxy tunnel CONNECT request strips the wants-auth headers)\n    private final boolean forProxy;\n    private final String wantsHeader;\n    private final String authorizationHeader;\n\n    /**\n     Creates an Authentication Filter with hardcoded credential expectations.\n     * @param alwaysWantsAuth true if this filter should always check for authentication, regardless of the Wants Auth header\n     * @param forProxy true if this wraps a Proxy and should use Proxy-Authenticate headers, credentials etc. False\n     * if wrapping the web server.\n     */\n    public AuthFilter(boolean alwaysWantsAuth, boolean forProxy) {\n        this.alwaysWantsAuth = alwaysWantsAuth;\n        this.forProxy = forProxy;\n\n        wantsHeader = forProxy ? WantsProxyAuthentication : WantsServerAuthentication;\n        authorizationHeader = forProxy ? \"Proxy-Authorization\" : \"Authorization\";\n    }\n\n    private static String newPassword() {\n        return \"pass-\" + Math.random();\n    }\n\n    // passwords get rotated in tests so that Java's auth cache is invalidated and a new auth callback occurs.\n    // requires tests hitting these are called serially.\n    public static String newServerPassword() {\n        return ServerPassword = newPassword() + \"-server\";\n    }\n\n    public static String newProxyPassword() {\n        return ProxyPassword = newPassword() + \"-proxy\";\n    }\n\n    @Override public void init(FilterConfig filterConfig) throws ServletException {}\n\n    @Override\n    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {\n        HttpServletRequest req = (HttpServletRequest) request;\n        HttpServletResponse res = (HttpServletResponse) response;\n\n        boolean accessGranted = checkAuth(req);\n        if (accessGranted) {\n            chain.doFilter(request, response);\n            return;\n        }\n\n        // Wants but failed auth - send appropriate header:\n        if (forProxy) {\n            res.setHeader(\"Proxy-Authenticate\", \"Basic realm=\\\"\" + ProxyRealm + \"\\\"\");\n            // ^^ Duped in ProxyServlet for CONNECT\n            res.sendError(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);\n        } else {\n            res.setHeader(\"WWW-Authenticate\", \"Basic realm=\\\"\" + ServerRealm + \"\\\"\");\n            res.sendError(HttpServletResponse.SC_UNAUTHORIZED);\n        }\n    }\n\n    @Override public void destroy() {}\n\n    public boolean checkAuth(HttpServletRequest req) {\n        if (alwaysWantsAuth || req.getHeader(wantsHeader) != null) {\n            String authHeader = req.getHeader(authorizationHeader);\n            if (authHeader != null) {\n                int space = authHeader.indexOf(' ');\n                if (space > 0) {\n                    String value = authHeader.substring(space + 1);\n                    String expected = forProxy ?\n                        (ProxyUser + \":\" + ProxyPassword) :\n                        (ServerUser + \":\" + ServerPassword);\n                    String base64 = Base64.getEncoder().encodeToString(expected.getBytes(StandardCharsets.UTF_8));\n                    return base64.equals(value); // if passed auth\n                }\n            }\n            return false; // unexpected header value\n        }\n        return true; // auth not required\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/BaseServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic abstract class BaseServlet extends HttpServlet {\n    static final String TextHtml = \"text/html; charset=UTF-8\";\n\n    abstract protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException;\n\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {\n        doIt(req, res);\n    }\n\n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {\n        doIt(req, res);\n    }\n\n    @Override\n    protected void doPut(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {\n        doIt(req, res);\n    }\n\n    @Override\n    protected void doDelete(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {\n        doIt(req, res);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/CookieServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\npublic class CookieServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(CookieServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n    public static final String SetCookiesParam = \"setCookies\";\n    public static final String LocationParam = \"loc\";\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        // Do we want to set cookies?\n        if (req.getParameter(SetCookiesParam) != null)\n            setCookies(res);\n\n        // Do we want to redirect elsewhere?\n        String loc = req.getParameter(LocationParam);\n        if (loc != null) {\n            res.sendRedirect(loc);\n            return;\n        }\n\n        // print out the cookies that were received\n        res.setContentType(TextHtml);\n        res.setStatus(200);\n\n        PrintWriter w = res.getWriter();\n        w.println(\"<table>\");\n        final Cookie[] cookies = req.getCookies();\n        if (cookies != null) {\n            for (Cookie cookie : cookies) {\n                EchoServlet.write(w, cookie.getName(), cookie.getValue());\n            }\n        }\n        w.println(\"</table>\");\n    }\n\n    private void setCookies(HttpServletResponse res) {\n        Cookie one = new Cookie(\"One\", \"Root\");\n        one.setPath(\"/\");\n        res.addCookie(one);\n\n        Cookie two = new Cookie(\"One\", \"CookieServlet\");\n        two.setPath(\"/CookieServlet\");\n        two.setHttpOnly(true);\n        two.setComment(\"Quite nice\");\n        res.addCookie(two);\n\n        Cookie three = new Cookie(\"One\", \"EchoServlet\");\n        three.setPath(\"/EchoServlet\");\n        res.addCookie(three);\n\n        Cookie four = new Cookie(\"Two\", \"NoSuchPath\");\n        four.setPath(\"/bogus\");\n        res.addCookie(four);\n\n        Cookie five = new Cookie(\"Two\", \"Override\");\n        five.setPath(\"/bogus\");\n        res.addCookie(five);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/DeflateServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.zip.Deflater;\nimport java.util.zip.DeflaterOutputStream;\n\npublic class DeflateServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(DeflateServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        res.setContentType(TextHtml);\n        res.setStatus(HttpServletResponse.SC_OK);\n        res.setHeader(\"Content-Encoding\", \"deflate\");\n\n        String doc = \"<p>Hello, World!<p>That should be enough, right?<p>Hello, World!<p>That should be enough, right?\";\n\n        DeflaterOutputStream stream = new DeflaterOutputStream(\n            res.getOutputStream(),\n            new Deflater(Deflater.BEST_COMPRESSION, true)); // true = nowrap zlib headers\n\n       stream.write(doc.getBytes(StandardCharsets.UTF_8));\n       stream.close();\n    }\n\n    // allow the servlet to run as a main program, for local test\n    public static void main(String[] args) {\n        TestServer.start();\n        System.out.println(Url);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/EchoServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.eclipse.jetty.server.Request;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.MultipartConfigElement;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.Part;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.Enumeration;\n\nimport static org.jsoup.nodes.Entities.escape;\n\npublic class EchoServlet extends BaseServlet {\n    public static final String CodeParam = \"code\";\n    private static final int DefaultCode = HttpServletResponse.SC_OK;\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(EchoServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {\n        int intCode = DefaultCode;\n        String code = req.getHeader(CodeParam);\n        if (code != null)\n            intCode = Integer.parseInt(code);\n\n        boolean isMulti = maybeEnableMultipart(req);\n\n        res.setContentType(TextHtml);\n        res.setStatus(intCode);\n        // no-cache headers for test\n        res.addHeader(\"Cache-Control\", \"no-cache\");\n        res.addHeader(\"Cache-Control\", \"no-store\");\n\n        PrintWriter w = res.getWriter();\n\n        w.write(\"<title>Webserver Environment Variables</title>\\n\" +\n            \"    <style type=\\\"text/css\\\">\\n\" +\n            \"      body, td, th {font: 10pt Verdana, Arial, sans-serif; text-align: left}\\n\" +\n            \"      th {font-weight: bold}        \\n\" +\n            \"    </style>\\n\" +\n            \"    <body>\\n\" +\n            \"    <table border=\\\"0\\\">\");\n\n        // some get items\n        write(w, \"Method\", req.getMethod());\n        write(w, \"Request URI\", req.getRequestURI());\n        write(w, \"Path Info\", req.getPathInfo());\n        write(w, \"Query String\", req.getQueryString());\n\n        // request headers (why is it an enumeration?)\n        Enumeration<String> headerNames = req.getHeaderNames();\n        while (headerNames.hasMoreElements()) {\n            String header = headerNames.nextElement();\n            Enumeration<String> headers = req.getHeaders(header);\n            while (headers.hasMoreElements()) {\n                write(w, header, headers.nextElement());\n            }\n        }\n\n        // cookies\n        final Cookie[] cookies = req.getCookies();\n        if (cookies != null) {\n            for (Cookie cookie : cookies) {\n                EchoServlet.write(w, \"Cookie: \" + cookie.getName(), cookie.getValue());\n            }\n        }\n\n        // the request params\n        Enumeration<String> parameterNames = req.getParameterNames();\n        while (parameterNames.hasMoreElements()) {\n            String name = parameterNames.nextElement();\n            String[] values = req.getParameterValues(name);\n            write(w, name, StringUtil.join(values, \", \"));\n        }\n\n        // post body\n        ByteBuffer byteBuffer = DataUtil.readToByteBuffer(req.getInputStream(), 0);\n        String postData = new String(byteBuffer.array(), byteBuffer.arrayOffset(), byteBuffer.limit(), StandardCharsets.UTF_8);\n        if (!StringUtil.isBlank(postData)) {\n            write(w, \"Post Data\", postData);\n        }\n\n        // file uploads\n        if (isMulti) {\n            Collection<Part> parts = req.getParts();\n            write(w, \"Parts\", String.valueOf(parts.size()));\n\n            for (Part part : parts) {\n                String name = part.getName();\n                write(w, \"Part \" + name + \" ContentType\", part.getContentType());\n                write(w, \"Part \" + name + \" Name\", name);\n                write(w, \"Part \" + name + \" Filename\", part.getSubmittedFileName());\n                write(w, \"Part \" + name + \" Size\", String.valueOf(part.getSize()));\n                part.delete();\n            }\n        }\n\n        w.println(\"</table>\");\n    }\n\n    static void write(PrintWriter w, String key, String val) {\n        w.println(\"<tr><th>\" + escape(key) + \"</th><td>\" + escape(val) + \"</td></tr>\");\n    }\n\n    // allow the servlet to run as a main program, for local test\n    public static void main(String[] args) {\n        TestServer.start();\n        System.out.println(\"Listening on \" + Url + \" and \" + TlsUrl);\n    }\n\n    private static boolean maybeEnableMultipart(HttpServletRequest req) {\n        boolean isMulti = req.getContentType() != null\n            && req.getContentType().startsWith(\"multipart/form-data\");\n\n        if (isMulti) {\n            req.setAttribute(Request.MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(\n                System.getProperty(\"java.io.tmpdir\")));\n        }\n        return isMulti;\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/FileServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.ServletOutputStream;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\n\npublic class FileServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(FileServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n    public static final String ContentTypeParam = \"contentType\";\n    public static final String HtmlType = \"text/html\";\n    static final String XmlType = \"text/xml\";\n    public static final String SuppressContentLength = \"surpriseMe\";\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        String contentType = req.getParameter(ContentTypeParam);\n        if (contentType == null) {\n            contentType = HtmlType;\n            if (req.getPathInfo().contains(\".xml\")) contentType = XmlType;\n        }\n        String location = req.getPathInfo();\n\n        File file = ParseTest.getFile(location);\n        if (file.exists()) {\n            res.setContentType(contentType);\n            if (file.getName().endsWith(\"gz\"))\n                res.addHeader(\"Content-Encoding\", \"gzip\");\n            if (req.getParameter(SuppressContentLength) == null)\n                res.setContentLength((int) file.length());\n            res.setStatus(HttpServletResponse.SC_OK);\n\n            ServletOutputStream out = res.getOutputStream();\n            Files.copy(file.toPath(), out);\n            out.flush();\n        } else {\n            res.sendError(HttpServletResponse.SC_NOT_FOUND);\n        }\n    }\n\n    public static String urlTo(String path) {\n        return Url + path;\n    }\n\n    public static String tlsUrlTo(String path) {\n        return TlsUrl + path;\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/HelloServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic class HelloServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(HelloServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        res.setContentType(TextHtml);\n        res.setStatus(HttpServletResponse.SC_OK);\n\n        String doc = \"<p>Hello, World!\";\n        res.getWriter().write(doc);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\nimport org.jsoup.parser.CharacterReaderTest;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic class InterruptedServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(InterruptedServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n    public static final String Magnitude = \"magnitude\";\n    public static final String Larger = \"larger\";\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        String magnitude = req.getParameter(Magnitude);\n        magnitude  = magnitude == null ? \"\" : magnitude;\n        res.setContentType(TextHtml);\n        res.setStatus(HttpServletResponse.SC_OK);\n\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"<title>Something</title>\");\n        while (sb.length() <= 32 * 1024) {\n            sb.append(\"<div>A suitable amount of data.</div>\\n\");\n        }\n        sb.append(\"<p>Finale.</p>\");\n        String data = sb.toString();\n\n        int contentLength = magnitude.equals(Larger) ? data.length() * 2 : data.length() / 2;\n        res.setContentLength(contentLength);\n\n        res.getWriter().write(data);\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/ProxyServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.eclipse.jetty.client.api.Response;\nimport org.eclipse.jetty.proxy.AsyncProxyServlet;\nimport org.eclipse.jetty.proxy.ConnectHandler;\nimport org.eclipse.jetty.server.Handler;\nimport org.eclipse.jetty.servlet.FilterHolder;\nimport org.eclipse.jetty.servlet.FilterMapping;\nimport org.eclipse.jetty.servlet.ServletHandler;\nimport org.eclipse.jetty.servlet.ServletHolder;\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\nimport static org.jsoup.integration.servlets.AuthFilter.ProxyRealm;\n\npublic class ProxyServlet extends AsyncProxyServlet {\n    public static TestServer.ProxySettings ProxySettings = TestServer.proxySettings();\n    public static String Via = \"1.1 jsoup test proxy\";\n\n    static {\n        System.setProperty(\"jdk.http.auth.tunneling.disabledSchemes\", \"\");\n        // removes Basic, which is otherwise excluded from auth for CONNECT tunnels\n    }\n\n    public static Handler createHandler(boolean alwaysAuth) {\n        // ConnectHandler wraps this ProxyServlet and handles CONNECT, which sets up a tunnel for HTTPS requests and is\n        // opaque to the proxy. The ProxyServlet handles simple HTTP requests.\n        AuthFilter authFilter = new AuthFilter(alwaysAuth, true);\n        ConnectHandler connectHandler = new ConnectProxy(authFilter);\n        ServletHandler proxyHandler = new ServletHandler();\n        proxyHandler.addFilterWithMapping(new FilterHolder(authFilter), \"/*\", FilterMapping.ALL); // auth for HTTP proxy\n        ServletHolder proxyServletHolder = new ServletHolder(ProxyServlet.class); // Holder wraps as it requires maxThreads initialization\n        proxyServletHolder.setAsyncSupported(true);\n        proxyServletHolder.setInitParameter(\"maxThreads\", \"200\");\n        proxyHandler.addServletWithMapping(proxyServletHolder, \"/*\");\n        connectHandler.setHandler(proxyHandler);\n\n        return connectHandler;\n    }\n\n    @Override\n    protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse) {\n        super.onServerResponseHeaders(clientRequest, proxyResponse, serverResponse);\n        proxyResponse.addHeader(\"Via\", Via);\n    }\n\n    /** Supports CONNECT tunnels */\n    static class ConnectProxy extends ConnectHandler {\n        final AuthFilter authFilter;\n\n        public ConnectProxy(AuthFilter authFilter) {\n            this.authFilter = authFilter;\n        }\n\n        @Override\n        protected boolean handleAuthentication(HttpServletRequest req, HttpServletResponse res, String address) {\n            boolean accessGranted = authFilter.checkAuth(req);\n            //System.err.println(\"CONNECT AUTH: \" + accessGranted);\n\n            // need to add the desired auth header if not granted. Returning false here will also send 407 header\n            if (!accessGranted) {\n                res.setHeader(\"Proxy-Authenticate\", \"Basic realm=\\\"\" + ProxyRealm + \"\\\"\");\n            }\n            return accessGranted;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/RedirectServlet.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\npublic class RedirectServlet extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(RedirectServlet.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n    public static final String LocationParam = \"loc\";\n    public static final String CodeParam = \"code\";\n    public static final String SetCookiesParam = \"setCookies\";\n    private static final int DefaultCode = HttpServletResponse.SC_MOVED_TEMPORARILY;\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        String location = req.getParameter(LocationParam);\n        if (location == null)\n            location = \"\";\n\n        int intCode = DefaultCode;\n        String code = req.getParameter(CodeParam);\n        if (code != null)\n            intCode = Integer.parseInt(code);\n\n        if (req.getParameter(SetCookiesParam) != null) {\n            res.addCookie(new Cookie(\"token\", \"asdfg123\"));\n            res.addCookie(new Cookie(\"uid\", \"foobar\"));\n            res.addCookie(new Cookie(\"uid\", \"jhy\")); // dupe, should use latter\n        }\n\n        res.setHeader(\"Location\", location);\n        res.setStatus(intCode);\n        res.flushBuffer();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/integration/servlets/SlowRider.java",
    "content": "package org.jsoup.integration.servlets;\n\nimport org.jsoup.integration.TestServer;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n/**\n * Slowly, interminably writes output. For the purposes of testing timeouts and interrupts.\n */\npublic class SlowRider extends BaseServlet {\n    public static final String Url;\n    public static final String TlsUrl;\n    static {\n        TestServer.ServletUrls urls = TestServer.map(SlowRider.class);\n        Url = urls.url;\n        TlsUrl = urls.tlsUrl;\n    }\n    private static final int SleepTime = 2000;\n    public static final String MaxTimeParam = \"maxTime\";\n    public static final String IntroSizeParam = \"introSize\";\n\n    @Override\n    protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {\n        pause(1000);\n        res.setContentType(TextHtml);\n        res.setStatus(HttpServletResponse.SC_OK);\n        PrintWriter w = res.getWriter();\n\n        int maxTime = -1;\n        String maxTimeP = req.getParameter(MaxTimeParam);\n        if (maxTimeP != null) {\n            maxTime = Integer.parseInt(maxTimeP);\n        }\n\n        int introSize = 0;\n        String introSizeP = req.getParameter(IntroSizeParam);\n        if (introSizeP != null) {\n            introSize = Integer.parseInt(introSizeP);\n        }\n\n        long startTime = System.currentTimeMillis();\n        w.println(\"<title>Slow Rider</title>\");\n\n        // write out a bunch of stuff at the start before interim pauses, gets past some buffers\n        if (introSize != 0) {\n            StringBuilder s = new StringBuilder();\n            while (s.length() < introSize) {\n                s.append(\"<p>Hello and welcome to the Slow Rider!</p>\\n\");\n            }\n            w.println(s);\n            w.flush();\n        }\n\n        while (true) {\n            w.println(\"<p>Are you still there?\");\n            boolean err = w.checkError(); // flush, and check still ok\n            if (err) {\n                log(\"Remote connection lost\");\n                break;\n            }\n            if (pause(SleepTime)) break;\n\n            if (maxTime > 0 && System.currentTimeMillis() > startTime + maxTime) {\n                w.println(\"<h1>outatime</h1>\");\n                break;\n            }\n        }\n    }\n\n    private static boolean pause(int sleepTime) {\n        try {\n            Thread.sleep(sleepTime);\n        } catch (InterruptedException e) {\n            return true;\n        }\n        return false;\n    }\n\n    // allow the servlet to run as a main program, for local test\n    public static void main(String[] args) {\n        TestServer.start();\n        System.out.println(Url);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/internal/ControllableInputStreamTest.java",
    "content": "package org.jsoup.internal;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass ControllableInputStreamTest {\n\n    @Test\n    void respectsMaxCapDuringFill() throws IOException {\n        byte[] data = \"0123456789\".getBytes(); // 10 bytes\n        CountingInputStream counting = new CountingInputStream(new ByteArrayInputStream(data));\n\n        ControllableInputStream in = ControllableInputStream.wrap(counting, 5); // cap at 5 bytes\n        byte[] buf = new byte[10];\n\n        int read = in.read(buf);\n        assertEquals(5, read, \"should only read up to cap\");\n        assertEquals(5, counting.count, \"underlying stream should not be pulled past cap\");\n        assertFalse(in.baseReadFully(), \"cap hit is not EOF\");\n\n        int second = in.read(buf);\n        assertEquals(-1, second, \"further reads return -1 once cap is exhausted\");\n        assertFalse(in.baseReadFully(), \"still not true EOF\");\n        in.close();\n    }\n\n    @Test\n    void compactsBufferWithActiveMark() throws IOException {\n        int size = SharedConstants.DefaultBufferSize * 2;\n        byte[] data = new byte[size];\n        for (int i = 0; i < size; i++) data[i] = (byte) (i % 256);\n\n        ControllableInputStream in = ControllableInputStream.wrap(new ByteArrayInputStream(data), 0);\n\n        byte[] first = new byte[500];\n        assertEquals(500, in.read(first));\n\n        in.mark(SharedConstants.DefaultBufferSize); // mark at logical pos 500\n\n        byte[] consume = new byte[SharedConstants.DefaultBufferSize];\n        int firstRead = in.read(consume); // serves remainder of current buffer (BufferSize - 500)\n        assertEquals(SharedConstants.DefaultBufferSize - 500, firstRead);\n\n        byte[] more = new byte[1000];\n        int secondRead = in.read(more); // triggers fill() with active mark, then consumes from freshly filled buffer\n        assertEquals(SharedConstants.DefaultBufferSize - firstRead, secondRead);\n\n        in.reset(); // should rewind to mark despite prior compaction\n\n        byte[] reread = new byte[1000];\n        assertEquals(1000, in.read(reread));\n        for (int i = 0; i < reread.length; i++) {\n            assertEquals(data[500 + i], reread[i], \"byte mismatch at \" + i);\n        }\n        in.close();\n    }\n\n    private static final class CountingInputStream extends FilterInputStream {\n        int count = 0;\n\n        CountingInputStream(InputStream in) {\n            super(in);\n        }\n\n        @Override\n        public int read(byte[] b, int off, int len) throws IOException {\n            int r = super.read(b, off, len);\n            if (r > 0) count += r;\n            return r;\n        }\n\n        @Override\n        public int read() throws IOException {\n            int r = super.read();\n            if (r != -1) count++;\n            return r;\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/internal/QuietAppendableTest.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.SerializationException;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.CharArrayWriter;\nimport java.io.IOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass QuietAppendableTest {\n    @Test void wrap() {\n        assertInstanceOf(\n            QuietAppendable.StringBuilderAppendable.class,\n            QuietAppendable.wrap(new StringBuilder())\n        );\n\n        assertInstanceOf(\n            QuietAppendable.BaseAppendable.class,\n            QuietAppendable.wrap(new CharArrayWriter())\n        );\n    }\n\n    @Test void supplemental() {\n        // hits append(char[] chars, int offset, int len) with len 2 for supplemental codepoint\n        String expect = \"😀\";\n        char[] chars = new char[2];\n        chars[0] = expect.charAt(0);\n        chars[1] = expect.charAt(1);\n        assertEquals(2, expect.length());\n\n        QuietAppendable sb = QuietAppendable.wrap(new StringBuilder());\n        sb.append(chars, 0, 2);\n        String s = sb.toString();\n        assertEquals(expect, s);\n\n        CharArrayWriter cw = new CharArrayWriter();\n        QuietAppendable qa = QuietAppendable.wrap(cw);\n        qa.append(chars, 0, 2);\n        String out = cw.toString();\n        assertEquals(expect, out);\n    }\n\n    private static Appendable brokenAppender() {\n        // returns an Appendable that throws an IOException on any put\n        return new Appendable() {\n            @Override\n            public Appendable append(CharSequence csq) throws IOException {\n                throw new IOException(\"broken\");\n            }\n\n            @Override\n            public Appendable append(CharSequence csq, int start, int end) throws IOException {\n                throw new IOException(\"broken\");\n            }\n\n            @Override\n            public Appendable append(char c) throws IOException {\n                throw new IOException(\"broken\");\n            }\n        };\n    }\n\n    @Test void appendThrowsSerializationException() {\n        Document doc = Jsoup.parse(\"<div>\");\n        Appendable brokenWriter = brokenAppender();\n        boolean threw = false;\n        try {\n            doc.html(brokenWriter);\n        } catch (SerializationException e) {\n            threw = true;\n            Throwable cause = e.getCause();\n            assertEquals(\"broken\", cause.getMessage());\n            assertInstanceOf(IOException.class, cause);\n        }\n        assertTrue(threw);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/internal/ReaderTest.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.CharacterReader;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.jsoup.integration.ParseTest.getPath;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class ReaderTest {\n    @Test void readerOfStringAndFile() throws IOException {\n        // make sure that reading from a String and from a File produce the same bytes\n        Path path = getPath(\"/fuzztests/garble.html\");\n        byte[] bytes = Files.readAllBytes(path);\n        String fromBytes = new String(bytes, StandardCharsets.UTF_8);\n\n        SimpleStreamReader streamReader = getReader(path);\n        String fromStream = getString(streamReader);\n        assertEquals(fromBytes, fromStream);\n\n        SimpleStreamReader reader2 = getReader(path);\n        CharacterReader cr = new CharacterReader(reader2);\n        String fullRead = cr.consumeTo('X'); // does not exist in input\n        assertEquals(fromBytes, fullRead);\n    }\n\n    private static String getString(SimpleStreamReader streamReader) throws IOException {\n        // read streamreader to a string:\n        StringBuilder builder = new StringBuilder();\n        char[] cbuffer = new char[1024];\n        int read;\n        while ((read = streamReader.read(cbuffer)) != -1) {\n            builder.append(cbuffer, 0, read);\n        }\n        return builder.toString();\n    }\n\n    private static SimpleStreamReader getReader(Path path) throws IOException {\n        // set up a chain as in when we parse: simplebufferedinput -> controllableinputstream -> simplestreamreader -> characterreader\n        SimpleBufferedInput input = new SimpleBufferedInput(Files.newInputStream(path));\n        ControllableInputStream stream = ControllableInputStream.wrap(input, 0);\n        return new SimpleStreamReader(stream, StandardCharsets.UTF_8);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/internal/SoftPoolTest.java",
    "content": "package org.jsoup.internal;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SoftPoolTest {\n\n    private static final int BufSize = 12;\n    private static final int NumThreads = 5;\n    private static final int NumObjects = 3;\n\n    @Test\n    public void testSoftLocalPool() throws InterruptedException {\n        SoftPool<char[]> softLocalPool = new SoftPool<>(() -> new char[BufSize]);\n\n        ExecutorService executorService = Executors.newFixedThreadPool(NumThreads);\n        CountDownLatch latch = new CountDownLatch(NumThreads);\n\n        Set<char[]> allBuffers = new HashSet<>();\n        Set<char[]>[] threadLocalBuffers = new Set[NumThreads];\n\n        for (int i = 0; i < NumThreads; i++) {\n            threadLocalBuffers[i] = new HashSet<>();\n        }\n\n        AtomicInteger threadCount = new AtomicInteger();\n\n        Runnable task = () -> {\n            try {\n                int threadIndex = threadCount.getAndIncrement();\n                Set<char[]> localBuffers = new HashSet<>();\n                // First borrow\n                for (int i = 0; i < NumObjects; i++) {\n                    char[] buffer = softLocalPool.borrow();\n                    assertEquals(BufSize, buffer.length);\n                    localBuffers.add(buffer);\n                }\n\n                // Release buffers back to the pool\n                for (char[] buffer : localBuffers) {\n                    softLocalPool.release(buffer);\n                }\n\n                // Borrow again and ensure buffers are reused\n                for (int i = 0; i < NumObjects; i++) {\n                    char[] buffer = softLocalPool.borrow();\n                    assertTrue(localBuffers.contains(buffer), \"Buffer was not reused in the same thread\");\n                    threadLocalBuffers[threadIndex].add(buffer);\n                }\n\n                synchronized (allBuffers) {\n                    allBuffers.addAll(threadLocalBuffers[threadIndex]);\n                }\n            } finally {\n                latch.countDown();\n            }\n        };\n\n        // Run the tasks\n        for (int i = 0; i < NumThreads; i++) {\n            executorService.submit(task::run);\n        }\n\n        // Wait for all threads to complete\n        latch.await();\n        executorService.shutdown();\n\n        // Ensure no buffers are shared between threads\n        Set<char[]> uniqueBuffers = new HashSet<>();\n        for (Set<char[]> bufferSet : threadLocalBuffers) {\n            for (char[] buffer : bufferSet) {\n                assertTrue(uniqueBuffers.add(buffer), \"Buffer was shared between threads\");\n            }\n        }\n    }\n\n    @Test\n    public void testSoftReferenceBehavior() {\n        SoftPool<char[]> softLocalPool = new SoftPool<>(() -> new char[BufSize]);\n\n        // Borrow and release an object\n        char[] buffer = softLocalPool.borrow();\n        assertEquals(BufSize, buffer.length);\n        softLocalPool.release(buffer);\n\n        // Fake a GC\n        softLocalPool.threadLocalStack.get().clear();\n\n        // Ensure the object is garbage collected\n        assertNull(softLocalPool.threadLocalStack.get().get());\n\n        char[] second = softLocalPool.borrow();\n        // should be different, but same size\n        assertNotEquals(buffer, second);\n        assertEquals(BufSize, second.length);\n    }\n\n    @Test\n    public void testBorrowFromEmptyPool() {\n        SoftPool<char[]> softLocalPool = new SoftPool<>(() -> new char[BufSize]);\n\n        // Borrow from an empty pool\n        char[] buffer = softLocalPool.borrow();\n        assertNotNull(buffer, \"Borrowed null from an empty pool\");\n        assertEquals(BufSize, buffer.length);\n    }\n\n    @Test\n    public void testReleaseMoreThanMaxIdle() {\n        SoftPool<char[]> softLocalPool = new SoftPool<>(() -> new char[BufSize]);\n\n        // Borrow more than MaxIdle objects\n        List<char[]> borrowedBuffers = new ArrayList<>();\n        for (int i = 0; i < SoftPool.MaxIdle + 5; i++) {\n            char[] buffer = softLocalPool.borrow();\n            borrowedBuffers.add(buffer);\n        }\n\n        // Release all borrowed objects back to the pool\n        for (char[] buffer : borrowedBuffers) {\n            softLocalPool.release(buffer);\n        }\n\n        // Ensure the pool size does not exceed MaxIdle\n        ArrayDeque<char[]> stack = softLocalPool.getStack();\n        assertTrue(stack.size() <= SoftPool.MaxIdle, \"Pool size exceeded MaxIdle limit\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/internal/StringUtilTest.java",
    "content": "package org.jsoup.internal;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.Collections;\n\nimport static org.jsoup.internal.StringUtil.normaliseWhitespace;\nimport static org.jsoup.internal.StringUtil.resolve;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class StringUtilTest {\n\n    @Test\n    public void join() {\n        assertEquals(\"\", StringUtil.join(Collections.singletonList(\"\"), \" \"));\n        assertEquals(\"one\", StringUtil.join(Collections.singletonList(\"one\"), \" \"));\n        assertEquals(\"one two three\", StringUtil.join(Arrays.asList(\"one\", \"two\", \"three\"), \" \"));\n    }\n\n    @Test public void padding() {\n        assertEquals(\"\", StringUtil.padding(0));\n        assertEquals(\" \", StringUtil.padding(1));\n        assertEquals(\"  \", StringUtil.padding(2));\n        assertEquals(\"               \", StringUtil.padding(15));\n        assertEquals(\"                              \", StringUtil.padding(45)); // we default to tap out at 30\n\n        // memoization is up to 21 blocks (0 to 20 spaces) and exits early before min checks making maxPaddingWidth unused\n        assertEquals(\"\", StringUtil.padding(0, -1));\n        assertEquals(\"                    \", StringUtil.padding(20, -1));\n\n        // this test escapes memoization and continues through\n        assertEquals(\"                     \", StringUtil.padding(21, -1));\n\n        // this test escapes memoization and using unlimited length (-1) will allow requested spaces\n        assertEquals(\"                              \", StringUtil.padding(30, -1));\n        assertEquals(\"                                             \", StringUtil.padding(45, -1));\n\n        // we tap out at 0 for this test\n        assertEquals(\"\", StringUtil.padding(0, 0));\n\n        // as memoization is escaped, setting zero for max padding will not allow any requested width\n        assertEquals(\"\", StringUtil.padding(21, 0));\n\n        // we tap out at 30 for these tests making > 30 use 30\n        assertEquals(\"\", StringUtil.padding(0, 30));\n        assertEquals(\" \", StringUtil.padding(1, 30));\n        assertEquals(\"  \", StringUtil.padding(2, 30));\n        assertEquals(\"               \", StringUtil.padding(15, 30));\n        assertEquals(\"                              \", StringUtil.padding(45, 30));\n\n        // max applies regardless of memoized\n        assertEquals(5, StringUtil.padding(20, 5).length());\n    }\n\n    @Test public void paddingInACan() {\n        String[] padding = StringUtil.padding;\n        assertEquals(21, padding.length);\n        for (int i = 0; i < padding.length; i++) {\n            assertEquals(i, padding[i].length());\n        }\n    }\n\n    @Test public void isBlank() {\n        assertTrue(StringUtil.isBlank(null));\n        assertTrue(StringUtil.isBlank(\"\"));\n        assertTrue(StringUtil.isBlank(\"      \"));\n        assertTrue(StringUtil.isBlank(\"   \\r\\n  \"));\n\n        assertFalse(StringUtil.isBlank(\"hello\"));\n        assertFalse(StringUtil.isBlank(\"   hello   \"));\n    }\n\n    @Test public void isNumeric() {\n        assertFalse(StringUtil.isNumeric(null));\n        assertFalse(StringUtil.isNumeric(\" \"));\n        assertFalse(StringUtil.isNumeric(\"123 546\"));\n        assertFalse(StringUtil.isNumeric(\"hello\"));\n        assertFalse(StringUtil.isNumeric(\"123.334\"));\n\n        assertTrue(StringUtil.isNumeric(\"1\"));\n        assertTrue(StringUtil.isNumeric(\"1234\"));\n    }\n\n    @Test public void isWhitespace() {\n        assertTrue(StringUtil.isWhitespace('\\t'));\n        assertTrue(StringUtil.isWhitespace('\\n'));\n        assertTrue(StringUtil.isWhitespace('\\r'));\n        assertTrue(StringUtil.isWhitespace('\\f'));\n        assertTrue(StringUtil.isWhitespace(' '));\n\n        assertFalse(StringUtil.isWhitespace('\\u00a0'));\n        assertFalse(StringUtil.isWhitespace('\\u2000'));\n        assertFalse(StringUtil.isWhitespace('\\u3000'));\n    }\n\n    @Test public void normaliseWhiteSpace() {\n        assertEquals(\" \", normaliseWhitespace(\"    \\r \\n \\r\\n\"));\n        assertEquals(\" hello there \", normaliseWhitespace(\"   hello   \\r \\n  there    \\n\"));\n        assertEquals(\"hello\", normaliseWhitespace(\"hello\"));\n        assertEquals(\"hello there\", normaliseWhitespace(\"hello\\nthere\"));\n    }\n\n    @Test public void normaliseWhiteSpaceHandlesHighSurrogates() {\n        String test71540chars = \"\\ud869\\udeb2\\u304b\\u309a  1\";\n        String test71540charsExpectedSingleWhitespace = \"\\ud869\\udeb2\\u304b\\u309a 1\";\n\n        assertEquals(test71540charsExpectedSingleWhitespace, normaliseWhitespace(test71540chars));\n        String extractedText = Jsoup.parse(test71540chars).text();\n        assertEquals(test71540charsExpectedSingleWhitespace, extractedText);\n    }\n\n    @Test public void resolvesRelativeUrls() {\n        assertEquals(\"http://example.com/one/two?three\", resolve(\"http://example.com\", \"./one/two?three\"));\n        assertEquals(\"http://example.com/one/two?three\", resolve(\"http://example.com?one\", \"./one/two?three\"));\n        assertEquals(\"http://example.com/one/two?three#four\", resolve(\"http://example.com\", \"./one/two?three#four\"));\n        assertEquals(\"https://example.com/one\", resolve(\"http://example.com/\", \"https://example.com/one\"));\n        assertEquals(\"http://example.com/one/two.html\", resolve(\"http://example.com/two/\", \"../one/two.html\"));\n        assertEquals(\"https://example2.com/one\", resolve(\"https://example.com/\", \"//example2.com/one\"));\n        assertEquals(\"https://example.com:8080/one\", resolve(\"https://example.com:8080\", \"./one\"));\n        assertEquals(\"https://example2.com/one\", resolve(\"http://example.com/\", \"https://example2.com/one\"));\n        assertEquals(\"https://example.com/one\", resolve(\"wrong\", \"https://example.com/one\"));\n        assertEquals(\"https://example.com/one\", resolve(\"https://example.com/one\", \"\"));\n        assertEquals(\"\", resolve(\"wrong\", \"also wrong\"));\n        assertEquals(\"ftp://example.com/one\", resolve(\"ftp://example.com/two/\", \"../one\"));\n        assertEquals(\"ftp://example.com/one/two.c\", resolve(\"ftp://example.com/one/\", \"./two.c\"));\n        assertEquals(\"ftp://example.com/one/two.c\", resolve(\"ftp://example.com/one/\", \"two.c\"));\n        // examples taken from rfc3986 section 5.4.2\n        assertEquals(\"http://example.com/g\", resolve(\"http://example.com/b/c/d;p?q\", \"../../../g\"));\n        assertEquals(\"http://example.com/g\", resolve(\"http://example.com/b/c/d;p?q\", \"../../../../g\"));\n        assertEquals(\"http://example.com/g\", resolve(\"http://example.com/b/c/d;p?q\", \"/./g\"));\n        assertEquals(\"http://example.com/g\", resolve(\"http://example.com/b/c/d;p?q\", \"/../g\"));\n        assertEquals(\"http://example.com/b/c/g.\", resolve(\"http://example.com/b/c/d;p?q\", \"g.\"));\n        assertEquals(\"http://example.com/b/c/.g\", resolve(\"http://example.com/b/c/d;p?q\", \".g\"));\n        assertEquals(\"http://example.com/b/c/g..\", resolve(\"http://example.com/b/c/d;p?q\", \"g..\"));\n        assertEquals(\"http://example.com/b/c/..g\", resolve(\"http://example.com/b/c/d;p?q\", \"..g\"));\n        assertEquals(\"http://example.com/b/g\", resolve(\"http://example.com/b/c/d;p?q\", \"./../g\"));\n        assertEquals(\"http://example.com/b/c/g/\", resolve(\"http://example.com/b/c/d;p?q\", \"./g/.\"));\n        assertEquals(\"http://example.com/b/c/g/h\", resolve(\"http://example.com/b/c/d;p?q\", \"g/./h\"));\n        assertEquals(\"http://example.com/b/c/h\", resolve(\"http://example.com/b/c/d;p?q\", \"g/../h\"));\n        assertEquals(\"http://example.com/b/c/g;x=1/y\", resolve(\"http://example.com/b/c/d;p?q\", \"g;x=1/./y\"));\n        assertEquals(\"http://example.com/b/c/y\", resolve(\"http://example.com/b/c/d;p?q\", \"g;x=1/../y\"));\n        assertEquals(\"http://example.com/b/c/g?y/./x\", resolve(\"http://example.com/b/c/d;p?q\", \"g?y/./x\"));\n        assertEquals(\"http://example.com/b/c/g?y/../x\", resolve(\"http://example.com/b/c/d;p?q\", \"g?y/../x\"));\n        assertEquals(\"http://example.com/b/c/g#s/./x\", resolve(\"http://example.com/b/c/d;p?q\", \"g#s/./x\"));\n        assertEquals(\"http://example.com/b/c/g#s/../x\", resolve(\"http://example.com/b/c/d;p?q\", \"g#s/../x\"));\n    }\n\n    @Test void stripsControlCharsFromUrls() {\n        // should resovle to an absolute url:\n        assertEquals(\"foo:bar\", resolve(\"\\nhttps://\\texample.com/\", \"\\r\\nfo\\to:ba\\br\"));\n    }\n\n    @Test void allowsSpaceInUrl() {\n        assertEquals(\"https://example.com/foo bar/\", resolve(\"HTTPS://example.com/example/\", \"../foo bar/\"));\n    }\n\n    @Test\n    void isAscii() {\n        assertTrue(StringUtil.isAscii(\"\"));\n        assertTrue(StringUtil.isAscii(\"example.com\"));\n        assertTrue(StringUtil.isAscii(\"One Two\"));\n        assertFalse(StringUtil.isAscii(\"🧔\"));\n        assertFalse(StringUtil.isAscii(\"测试\"));\n        assertFalse(StringUtil.isAscii(\"测试.com\"));\n    }\n\n    @Test void isAsciiLetter() {\n        assertTrue(StringUtil.isAsciiLetter('a'));\n        assertTrue(StringUtil.isAsciiLetter('n'));\n        assertTrue(StringUtil.isAsciiLetter('z'));\n        assertTrue(StringUtil.isAsciiLetter('A'));\n        assertTrue(StringUtil.isAsciiLetter('N'));\n        assertTrue(StringUtil.isAsciiLetter('Z'));\n\n        assertFalse(StringUtil.isAsciiLetter(' '));\n        assertFalse(StringUtil.isAsciiLetter('-'));\n        assertFalse(StringUtil.isAsciiLetter('0'));\n        assertFalse(StringUtil.isAsciiLetter('ß'));\n        assertFalse(StringUtil.isAsciiLetter('Ě'));\n    }\n\n    @Test void isDigit() {\n        assertTrue(StringUtil.isDigit('0'));\n        assertTrue(StringUtil.isDigit('1'));\n        assertTrue(StringUtil.isDigit('2'));\n        assertTrue(StringUtil.isDigit('3'));\n        assertTrue(StringUtil.isDigit('4'));\n        assertTrue(StringUtil.isDigit('5'));\n        assertTrue(StringUtil.isDigit('6'));\n        assertTrue(StringUtil.isDigit('7'));\n        assertTrue(StringUtil.isDigit('8'));\n        assertTrue(StringUtil.isDigit('9'));\n\n        assertFalse(StringUtil.isDigit('a'));\n        assertFalse(StringUtil.isDigit('A'));\n        assertFalse(StringUtil.isDigit('ä'));\n        assertFalse(StringUtil.isDigit('Ä'));\n        assertFalse(StringUtil.isDigit('١'));\n        assertFalse(StringUtil.isDigit('୳'));\n    }\n\n    @Test void isHexDigit() {\n        assertTrue(StringUtil.isHexDigit('0'));\n        assertTrue(StringUtil.isHexDigit('1'));\n        assertTrue(StringUtil.isHexDigit('2'));\n        assertTrue(StringUtil.isHexDigit('3'));\n        assertTrue(StringUtil.isHexDigit('4'));\n        assertTrue(StringUtil.isHexDigit('5'));\n        assertTrue(StringUtil.isHexDigit('6'));\n        assertTrue(StringUtil.isHexDigit('7'));\n        assertTrue(StringUtil.isHexDigit('8'));\n        assertTrue(StringUtil.isHexDigit('9'));\n        assertTrue(StringUtil.isHexDigit('a'));\n        assertTrue(StringUtil.isHexDigit('b'));\n        assertTrue(StringUtil.isHexDigit('c'));\n        assertTrue(StringUtil.isHexDigit('d'));\n        assertTrue(StringUtil.isHexDigit('e'));\n        assertTrue(StringUtil.isHexDigit('f'));\n        assertTrue(StringUtil.isHexDigit('A'));\n        assertTrue(StringUtil.isHexDigit('B'));\n        assertTrue(StringUtil.isHexDigit('C'));\n        assertTrue(StringUtil.isHexDigit('D'));\n        assertTrue(StringUtil.isHexDigit('E'));\n        assertTrue(StringUtil.isHexDigit('F'));\n\n        assertFalse(StringUtil.isHexDigit('g'));\n        assertFalse(StringUtil.isHexDigit('G'));\n        assertFalse(StringUtil.isHexDigit('ä'));\n        assertFalse(StringUtil.isHexDigit('Ä'));\n        assertFalse(StringUtil.isHexDigit('١'));\n        assertFalse(StringUtil.isHexDigit('୳'));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/AttributeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class AttributeTest {\n    @Test\n    public void html() {\n        Attribute attr = new Attribute(\"key\", \"value &\");\n        assertEquals(\"key=\\\"value &amp;\\\"\", attr.html());\n        assertEquals(attr.html(), attr.toString());\n    }\n\n    @Test\n    public void htmlWithLtAndGtInValue() {\n        Attribute attr = new Attribute(\"key\", \"<value>\");\n        assertEquals(\"key=\\\"&lt;value&gt;\\\"\", attr.html());\n    }\n\n    @Test public void testWithSupplementaryCharacterInAttributeKeyAndValue() {\n        String s = new String(Character.toChars(135361));\n        Attribute attr = new Attribute(s, \"A\" + s + \"B\");\n        assertEquals(s + \"=\\\"A\" + s + \"B\\\"\", attr.html());\n        assertEquals(attr.html(), attr.toString());\n    }\n\n    @Test public void validatesKeysNotEmpty() {\n        assertThrows(IllegalArgumentException.class, () -> new Attribute(\" \", \"Check\"));\n    }\n\n    @Test public void validatesKeysNotEmptyViaSet() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Attribute attr = new Attribute(\"One\", \"Check\");\n            attr.setKey(\" \");\n        });\n    }\n\n    @Test public void booleanAttributesAreEmptyStringValues() {\n        Document doc = Jsoup.parse(\"<div hidden>\");\n        Attributes attributes = doc.body().child(0).attributes();\n        assertEquals(\"\", attributes.get(\"hidden\"));\n\n        Attribute first = attributes.iterator().next();\n        assertEquals(\"hidden\", first.getKey());\n        assertEquals(\"\", first.getValue());\n        assertFalse(first.hasDeclaredValue());\n        assertTrue(Attribute.isBooleanAttribute(first.getKey()));\n    }\n\n    @Test public void settersOnOrphanAttribute() {\n        Attribute attr = new Attribute(\"one\", \"two\");\n        attr.setKey(\"three\");\n        String oldVal = attr.setValue(\"four\");\n        assertEquals(\"two\", oldVal);\n        assertEquals(\"three\", attr.getKey());\n        assertEquals(\"four\", attr.getValue());\n        assertNull(attr.parent);\n    }\n\n    @Test void settersAfterParentRemoval() {\n        // tests key and value set on a retained attribute after disconnected from parent\n        Attributes attrs = new Attributes();\n        attrs.put(\"foo\", \"bar\");\n        Attribute attr = attrs.attribute(\"foo\");\n        assertNotNull(attr);\n        attrs.remove(\"foo\");\n        assertEquals(\"foo\", attr.getKey());\n        assertEquals(\"bar\", attr.getValue());\n        attr.setKey(\"new\");\n        attr.setValue(\"newer\");\n        assertEquals(\"new\", attr.getKey());\n        assertEquals(\"newer\", attr.getValue());\n    }\n\n    @Test public void hasValue() {\n        Attribute a1 = new Attribute(\"one\", \"\");\n        Attribute a2 = new Attribute(\"two\", null);\n        Attribute a3 = new Attribute(\"thr\", \"thr\");\n\n        assertTrue(a1.hasDeclaredValue());\n        assertFalse(a2.hasDeclaredValue());\n        assertTrue(a3.hasDeclaredValue());\n    }\n\n    @Test public void canSetValueToNull() {\n        Attribute attr = new Attribute(\"one\", \"val\");\n        String oldVal = attr.setValue(null);\n        assertEquals(\"one\", attr.html());\n        assertEquals(\"val\", oldVal);\n\n        oldVal = attr.setValue(\"foo\");\n        assertEquals(\"\", oldVal); // string, not null\n    }\n\n    @Test void booleanAttributesAreNotCaseSensitive() {\n        // https://github.com/jhy/jsoup/issues/1656\n        assertTrue(Attribute.isBooleanAttribute(\"required\"));\n        assertTrue(Attribute.isBooleanAttribute(\"REQUIRED\"));\n        assertTrue(Attribute.isBooleanAttribute(\"rEQUIREd\"));\n        assertFalse(Attribute.isBooleanAttribute(\"random string\"));\n\n        String html = \"<a href=autofocus REQUIRED>One</a>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<a href=\\\"autofocus\\\" required>One</a>\", doc.selectFirst(\"a\").outerHtml());\n\n        Document doc2 = Jsoup.parse(html, Parser.htmlParser().settings(ParseSettings.preserveCase));\n        assertEquals(\"<a href=\\\"autofocus\\\" REQUIRED>One</a>\", doc2.selectFirst(\"a\").outerHtml());\n    }\n\n    @Test void orphanNamespace() {\n        Attribute attr = new Attribute(\"one\", \"two\");\n        assertEquals(\"\", attr.namespace());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/AttributesTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ConcurrentModificationException;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for Attributes.\n *\n * @author Jonathan Hedley\n */\npublic class AttributesTest {\n\n    @Test\n    public void html() {\n        Attributes a = new Attributes();\n        a.put(\"Tot\", \"a&p\");\n        a.put(\"Hello\", \"There\");\n        a.put(\"data-name\", \"Jsoup\");\n\n        assertEquals(3, a.size());\n        assertTrue(a.hasKey(\"Tot\"));\n        assertTrue(a.hasKey(\"Hello\"));\n        assertTrue(a.hasKey(\"data-name\"));\n        assertFalse(a.hasKey(\"tot\"));\n        assertTrue(a.hasKeyIgnoreCase(\"tot\"));\n        assertEquals(\"There\", a.getIgnoreCase(\"hEllo\"));\n\n        Map<String, String> dataset = a.dataset();\n        assertEquals(1, dataset.size());\n        assertEquals(\"Jsoup\", dataset.get(\"name\"));\n        assertEquals(\"\", a.get(\"tot\"));\n        assertEquals(\"a&p\", a.get(\"Tot\"));\n        assertEquals(\"a&p\", a.getIgnoreCase(\"tot\"));\n\n        assertEquals(\" Tot=\\\"a&amp;p\\\" Hello=\\\"There\\\" data-name=\\\"Jsoup\\\"\", a.html());\n        assertEquals(a.html(), a.toString());\n    }\n\n    @Test\n    public void testIteratorRemovable() {\n        Attributes a = new Attributes();\n        a.put(\"Tot\", \"a&p\");\n        a.put(\"Hello\", \"There\");\n        a.put(\"data-name\", \"Jsoup\");\n        assertTrue(a.hasKey(\"Tot\"));\n\n        Iterator<Attribute> iterator = a.iterator();\n        Attribute attr = iterator.next();\n        assertEquals(\"Tot\", attr.getKey());\n        iterator.remove();\n        assertEquals(2, a.size());\n        attr = iterator.next();\n        assertEquals(\"Hello\", attr.getKey());\n        assertEquals(\"There\", attr.getValue());\n\n        // make sure that's flowing to the underlying attributes object\n        assertEquals(2, a.size());\n        assertEquals(\"There\", a.get(\"Hello\"));\n        assertFalse(a.hasKey(\"Tot\"));\n    }\n\n    @Test\n    public void testIteratorUpdateable() {\n        Attributes a = new Attributes();\n        a.put(\"Tot\", \"a&p\");\n        a.put(\"Hello\", \"There\");\n\n        assertFalse(a.hasKey(\"Foo\"));\n        Iterator<Attribute> iterator = a.iterator();\n        Attribute attr = iterator.next();\n        attr.setKey(\"Foo\");\n        attr = iterator.next();\n        attr.setKey(\"Bar\");\n        attr.setValue(\"Qux\");\n\n        assertEquals(\"a&p\", a.get(\"Foo\"));\n        assertEquals(\"Qux\", a.get(\"Bar\"));\n        assertFalse(a.hasKey(\"Tot\"));\n        assertFalse(a.hasKey(\"Hello\"));\n    }\n\n    @Test public void testIteratorHasNext() {\n        Attributes a = new Attributes();\n        a.put(\"Tot\", \"1\");\n        a.put(\"Hello\", \"2\");\n        a.put(\"data-name\", \"3\");\n\n        int seen = 0;\n        for (Attribute attribute : a) {\n            seen++;\n            assertEquals(String.valueOf(seen), attribute.getValue());\n        }\n        assertEquals(3, seen);\n    }\n\n    @Test\n    public void testIterator() {\n        Attributes a = new Attributes();\n        String[][] datas = {{\"Tot\", \"raul\"},\n            {\"Hello\", \"pismuth\"},\n            {\"data-name\", \"Jsoup\"}};\n        for (String[] atts : datas) {\n            a.put(atts[0], atts[1]);\n        }\n\n        Iterator<Attribute> iterator = a.iterator();\n        assertTrue(iterator.hasNext());\n        int i = 0;\n        for (Attribute attribute : a) {\n            assertEquals(datas[i][0], attribute.getKey());\n            assertEquals(datas[i][1], attribute.getValue());\n            i++;\n        }\n        assertEquals(datas.length, i);\n    }\n\n    @Test\n    public void testIteratorSkipsInternal() {\n        Attributes a = new Attributes();\n        a.put(\"One\", \"One\");\n        a.put(Attributes.internalKey(\"baseUri\"), \"example.com\");\n        a.put(\"Two\", \"Two\");\n        a.put(Attributes.internalKey(\"another\"), \"example.com\");\n\n        Iterator<Attribute> it = a.iterator();\n        assertTrue(it.hasNext());\n        assertEquals(\"One\", it.next().getKey());\n        assertTrue(it.hasNext());\n        assertEquals(\"Two\", it.next().getKey());\n        assertFalse(it.hasNext());\n\n        int seen = 0;\n        for (Attribute attribute : a) {\n            seen++;\n        }\n        assertEquals(2, seen);\n    }\n\n    @Test void iteratorThrows() {\n        Attributes attrs = new Attributes();\n        attrs.put(\"One\", \"one\").put(\"Two\", \"two\");\n\n        Iterator<Attribute> it = attrs.iterator();\n        int seen = 0;\n        while (it.hasNext()) {\n            it.next();\n            seen++;\n        }\n        assertFalse(it.hasNext());\n        assertEquals(2, seen);\n\n        boolean threw = false;\n        try {\n            Attribute next = it.next();\n            assertNotNull(next); // not hit\n        } catch (NoSuchElementException e) {\n            threw = true;\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void testListSkipsInternal() {\n        Attributes a = new Attributes();\n        a.put(\"One\", \"One\");\n        a.put(Attributes.internalKey(\"baseUri\"), \"example.com\");\n        a.put(\"Two\", \"Two\");\n        a.put(Attributes.internalKey(\"another\"), \"example.com\");\n\n        List<Attribute> attributes = a.asList();\n        assertEquals(2, attributes.size());\n        assertEquals(\"One\", attributes.get(0).getKey());\n        assertEquals(\"Two\", attributes.get(1). getKey());\n    }\n\n    @Test public void htmlSkipsInternals() {\n        Attributes a = new Attributes();\n        a.put(\"One\", \"One\");\n        a.put(Attributes.internalKey(\"baseUri\"), \"example.com\");\n        a.put(\"Two\", \"Two\");\n        a.put(Attributes.internalKey(\"another\"), \"example.com\");\n\n        assertEquals(\" One=\\\"One\\\" Two=\\\"Two\\\"\", a.html());\n    }\n\n    @Test\n    public void testIteratorEmpty() {\n        Attributes a = new Attributes();\n\n        Iterator<Attribute> iterator = a.iterator();\n        assertFalse(iterator.hasNext());\n    }\n\n    @Test\n    public void testIteratorRemove() {\n        String html = \"<div 1=1 2=2 3=3 4=4>\";\n        Document doc = Jsoup.parse(html);\n        Element el = doc.expectFirst(\"div\");\n        Attributes attrs = el.attributes();\n\n        Iterator<Attribute> iter = attrs.iterator();\n        int seen = 0;\n        while (iter.hasNext()) {\n            seen++;\n            Attribute attr = iter.next();\n            iter.remove();\n        }\n        assertEquals(4, seen);\n        assertEquals(0, attrs.size());\n        assertEquals(0, el.attributesSize());\n    }\n\n    @Test\n    public void testIteratorRemoveConcurrentException() {\n        String html = \"<div 1=1 2=2 3=3 4=4>\";\n        Document doc = Jsoup.parse(html);\n        Element el = doc.expectFirst(\"div\");\n        Attributes attrs = el.attributes();\n\n        Iterator<Attribute> iter = attrs.iterator();\n        int seen = 0;\n        boolean threw = false;\n        try {\n            while (iter.hasNext()) {\n                seen++;\n                Attribute next = iter.next();\n                el.removeAttr(next.getKey());\n            }\n        } catch (ConcurrentModificationException e) {\n            threw = true;\n        }\n\n        assertEquals(1, seen);\n        assertTrue(threw);\n    }\n\n    @Test\n    public void removeCaseSensitive() {\n        Attributes a = new Attributes();\n        a.put(\"Tot\", \"a&p\");\n        a.put(\"tot\", \"one\");\n        a.put(\"Hello\", \"There\");\n        a.put(\"hello\", \"There\");\n        a.put(\"data-name\", \"Jsoup\");\n\n        assertEquals(5, a.size());\n        a.remove(\"Tot\");\n        a.remove(\"Hello\");\n        assertEquals(3, a.size());\n        assertTrue(a.hasKey(\"tot\"));\n        assertFalse(a.hasKey(\"Tot\"));\n    }\n\n    @Test\n    public void testSetKeyConsistency() {\n        Attributes a = new Attributes();\n        a.put(\"a\", \"a\");\n        for(Attribute at : a) {\n            at.setKey(\"b\");\n        }\n        assertFalse(a.hasKey(\"a\"), \"Attribute 'a' not correctly removed\");\n        assertTrue(a.hasKey(\"b\"), \"Attribute 'b' not present after renaming\");\n    }\n\n    @Test\n    public void testBoolean() {\n        Attributes ats = new Attributes();\n        ats.put(\"a\", \"a\");\n        ats.put(\"B\", \"b\");\n        ats.put(\"c\", null);\n\n        assertTrue(ats.hasDeclaredValueForKey(\"a\"));\n        assertFalse(ats.hasDeclaredValueForKey(\"A\"));\n        assertTrue(ats.hasDeclaredValueForKeyIgnoreCase(\"A\"));\n\n        assertFalse(ats.hasDeclaredValueForKey(\"c\"));\n        assertFalse(ats.hasDeclaredValueForKey(\"C\"));\n        assertFalse(ats.hasDeclaredValueForKeyIgnoreCase(\"C\"));\n    }\n\n    @Test public void testSizeWhenHasInternal() {\n        Attributes a = new Attributes();\n        a.put(\"One\", \"One\");\n        a.put(\"Two\", \"Two\");\n        assertEquals(2, a.size());\n\n        a.put(Attributes.internalKey(\"baseUri\"), \"example.com\");\n        a.put(Attributes.internalKey(\"another\"), \"example.com\");\n        a.put(Attributes.internalKey(\"last\"), \"example.com\");\n        a.remove(Attributes.internalKey(\"last\"));\n\n        assertEquals(2, a.size());\n        assertEquals(2, a.asList().size()); // excluded from lists\n    }\n\n    @Test public void testBooleans() {\n        // want unknown=null, and known like async=null, async=\"\", and async=async to collapse\n        String html = \"<a foo bar=\\\"\\\" async=async qux=qux defer=deferring ismap inert=\\\"\\\">\";\n        Element el = Jsoup.parse(html).selectFirst(\"a\");\n        assertEquals(\" foo bar=\\\"\\\" async qux=\\\"qux\\\" defer=\\\"deferring\\\" ismap inert\", el.attributes().html());\n\n    }\n\n    @Test public void booleanNullAttributesConsistent() {\n        Attributes attributes = new Attributes();\n        attributes.put(\"key\", null);\n        Attribute attribute = attributes.iterator().next();\n\n        assertEquals(\"key\", attribute.html());\n        assertEquals(\" key\", attributes.html());\n    }\n\n    @Test public void booleanEmptyString() {\n        Attributes attributes = new Attributes();\n        attributes.put(\"checked\", \"\");\n        Attribute attribute = attributes.iterator().next();\n\n        assertEquals(\"checked\", attribute.html());\n        assertEquals(\" checked\", attributes.html());\n    }\n\n    @Test public void booleanCaseInsensitive() {\n        Attributes attributes = new Attributes();\n        attributes.put(\"checked\", \"CHECKED\");\n        Attribute attribute = attributes.iterator().next();\n\n        assertEquals(\"checked\", attribute.html());\n        assertEquals(\" checked\", attributes.html());\n    }\n\n    @Test public void equalsIsOrderInsensitive() {\n        Attributes one = new Attributes()\n            .add(\"Key1\", \"Val1\")\n            .add(\"Key2\", \"Val2\")\n            .add(\"Key3\", null);\n\n        Attributes two = new Attributes()\n            .add(\"Key1\", \"Val1\")\n            .add(\"Key2\", \"Val2\")\n            .add(\"Key3\", null);\n\n        Attributes three = new Attributes()\n            .add(\"Key2\", \"Val2\")\n            .add(\"Key3\", null)\n            .add(\"Key1\", \"Val1\");\n\n        Attributes four = new Attributes()\n            .add(\"Key1\", \"Val1\")\n            .add(\"Key2\", \"Val2\")\n            .add(\"Key3\", null)\n            .add(\"Key4\", \"Val4\");\n\n        assertEquals(one, one.clone());\n        assertEquals(one, two);\n        assertEquals(two, two);\n        assertEquals(one, three);\n        assertEquals(two, three);\n        assertEquals(three, three);\n        assertEquals(three, three.clone());\n        assertEquals(four, four);\n        assertEquals(four, four.clone());\n        assertNotEquals(one, four);\n    }\n\n    @Test void cloneAttributes() {\n        Attributes one = new Attributes()\n            .add(\"Key1\", \"Val1\")\n            .add(\"Key2\", \"Val2\")\n            .add(\"Key3\", null);\n        Attributes two = one.clone();\n        assertEquals(3, two.size());\n        assertEquals(\"Val2\", two.get(\"Key2\"));\n        assertEquals(one, two);\n\n        two.add(\"Key4\", \"Val4\");\n        assertEquals(4, two.size());\n        assertEquals(3, one.size());\n        assertNotEquals(one, two);\n    }\n\n    @Test void cloneGetsUniqueUserDataMap() {\n        Attributes one = new Attributes();\n        String data = \"Hello\";\n        one.userData(\"data\", data);\n\n        Attributes two = one.clone();\n        assertSame(two.userData(\"data\"), one.userData(\"data\"));\n        assertNotSame(two.userData(), one.userData());\n    }\n\n    @Test void dontCloneNullUserData() {\n        // https://github.com/jhy/jsoup/issues/2356\n        Element span1 = Jsoup.parse(\"<span id=1></span>\").expectFirst(\"span\");\n        Attributes attrs1 = span1.attributes();\n        assertFalse(attrs1.isEmpty());\n        span1.removeAttr(\"id\");\n        assertTrue(attrs1.isEmpty());\n\n        Element span2 = span1.clone();\n        Attributes attrs2 = span2.attributes();\n        assertTrue(attrs2.isEmpty());\n    }\n\n    @Test void sizeDoesNotIncludeInternal() {\n        Element el = new Element(\"el\");\n        Attributes attrs = el.attributes();\n        assertEquals(0, attrs.size());\n        assertTrue(attrs.isEmpty());\n\n        attrs.userData(\"foo\", \"bar\");\n        attrs.put(Attributes.internalKey(\"qux\"), \"bar\");\n        assertEquals(0, attrs.size());\n        assertEquals(2, attrs.size);\n        assertTrue(attrs.isEmpty());\n\n        attrs.put(\"foo\", \"bar\");\n        attrs.put(\"qux\", \"bar\");\n        assertEquals(2, attrs.size());\n        assertEquals(4, attrs.size);\n\n        el.clearAttributes();\n        assertEquals(0, attrs.size());\n        assertEquals(2, attrs.size); // we keep the internals\n        assertTrue(attrs.isEmpty());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/BuildEntities.java",
    "content": "package org.jsoup.nodes;\n\nimport com.google.gson.Gson;\nimport com.google.gson.reflect.TypeToken;\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\n\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.Map;\n\n/**\n * Fetches HTML entity names from w3.org json, and outputs data files for optimized used in Entities.\n * I refuse to believe that entity names like \"NotNestedLessLess\" are valuable or useful for HTML authors. Implemented\n * only to be complete.\n */\nclass BuildEntities {\n    public static void main(String[] args) throws IOException {\n        String url = \"https://www.w3.org/TR/2012/WD-html5-20121025/entities.json\";\n        Connection.Response res = Jsoup.connect(url)\n            .ignoreContentType(true)\n            .execute();\n\n        Gson gson = new Gson();\n        Map<String, CharacterRef> input = gson.fromJson(res.body(),\n            new TypeToken<Map<String, CharacterRef>>() {\n            }.getType());\n\n\n        // build name sorted base and full character lists:\n        ArrayList<CharacterRef> base = new ArrayList<>();\n        ArrayList<CharacterRef> full = new ArrayList<>();\n\n        for (Map.Entry<String, CharacterRef> entry : input.entrySet()) {\n            String name = entry.getKey().substring(1); // name is like &acute or &acute; , trim &\n            CharacterRef ref = entry.getValue();\n            if (name.endsWith(\";\")) {\n                name = name.substring(0, name.length() - 1);\n                full.add(ref);\n            } else {\n                base.add(ref);\n            }\n            ref.name = name;\n        }\n        base.sort(byName);\n        full.sort(byName);\n\n        // now determine code point order\n        ArrayList<CharacterRef> baseByCode = new ArrayList<>(base);\n        ArrayList<CharacterRef> fullByCode = new ArrayList<>(full);\n        baseByCode.sort(byCode);\n        fullByCode.sort(byCode);\n\n        // and update their codepoint index.\n        @SuppressWarnings(\"unchecked\") ArrayList<CharacterRef>[] codelists = new ArrayList[]{baseByCode, fullByCode};\n        for (ArrayList<CharacterRef> codelist : codelists) {\n            for (int i = 0; i < codelist.size(); i++) {\n                codelist.get(i).codeIndex = i;\n            }\n        }\n\n        // now write them\n        persist(\"entities-full\", full);\n        persist(\"entities-base\", base);\n\n        System.out.println(\"Full size: \" + full.size() + \", base size: \" + base.size());\n    }\n\n    private static void persist(String name, ArrayList<CharacterRef> refs) throws IOException {\n        File file = Files.createTempFile(name, \".txt\").toFile();\n        FileWriter writer = new FileWriter(file, false);\n        writer.append(\"static final String points = \\\"\");\n        for (CharacterRef ref : refs) {\n            writer.append(ref.toString()).append('&');\n        }\n        writer.append(\"\\\";\\n\");\n        writer.close();\n\n        System.out.println(\"Wrote \" + name + \" to \" + file.getAbsolutePath());\n    }\n\n\n    private static class CharacterRef {\n        int[] codepoints;\n        String name;\n        int codeIndex;\n\n        @Override\n        public String toString() {\n            return name\n                + \"=\"\n                + d(codepoints[0])\n                + (codepoints.length > 1 ? \",\" + d(codepoints[1]) : \"\")\n                + \";\" + d(codeIndex);\n        }\n    }\n\n    private static String d(int d) {\n        return Integer.toString(d, Entities.codepointRadix);\n    }\n\n    private static class ByCode implements Comparator<CharacterRef> {\n        public int compare(CharacterRef o1, CharacterRef o2) {\n            int[] c1 = o1.codepoints;\n            int[] c2 = o2.codepoints;\n            int first = c1[0] - c2[0];\n            if (first != 0)\n                return first;\n            if (c1.length == 1 && c2.length == 1) { // for the same code, use the shorter name\n                int len = o2.name.length() - o1.name.length();\n                if (len != 0)\n                    return len;\n                return o1.name.compareTo(o2.name);\n            }\n            if (c1.length == 2 && c2.length == 2)\n                return c1[1] - c2[1];\n            else\n                return c2.length - c1.length; // pushes multi down the list so hits on singles first (don't support multi lookup by codepoint yet)\n        }\n    }\n\n    private static final Comparator<CharacterRef> byName = Comparator.comparing(ref -> ref.name);\n    private static final ByCode byCode = new ByCode();\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/CommentTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CommentTest {\n    private Comment comment = new Comment(\" This is one heck of a comment! \");\n    private Comment decl = new Comment(\"?xml encoding='ISO-8859-1'?\");\n\n    @Test\n    public void nodeName() {\n        assertEquals(\"#comment\", comment.nodeName());\n    }\n\n    @Test\n    public void getData() {\n        assertEquals(\" This is one heck of a comment! \", comment.getData());\n    }\n\n    @Test\n    public void testToString() {\n        assertEquals(\"<!-- This is one heck of a comment! -->\", comment.toString());\n\n        Document doc = Jsoup.parse(\"<div><!-- comment--></div>\");\n        assertEquals(\"<div>\\n <!-- comment-->\\n</div>\", doc.body().html());\n\n        doc = Jsoup.parse(\"<p>One<!-- comment -->Two</p>\");\n        assertEquals(\"<p>One<!-- comment -->Two</p>\", doc.body().html());\n        assertEquals(\"OneTwo\", doc.text());\n    }\n\n    @Test\n    public void testHtmlNoPretty() {\n        Document doc = Jsoup.parse(\"<!-- a simple comment -->\");\n        doc.outputSettings().prettyPrint(false);\n        assertEquals(\"<!-- a simple comment --><html><head></head><body></body></html>\", doc.html());\n        Node node = doc.childNode(0);\n        Comment c1 = (Comment) node;\n        assertEquals(\"<!-- a simple comment -->\", c1.outerHtml());\n    }\n\n    @Test void stableIndentInBlock() {\n        String html = \"<div><!-- comment --> Text</div><p><!-- comment --> Text</p>\";\n        Document doc = Jsoup.parse(html);\n        String out = doc.body().html();\n        assertEquals(\"<div>\\n\" +\n            \" <!-- comment -->\\n Text\\n\" +\n            \"</div>\\n\" +\n            \"<p><!-- comment --> Text</p>\", out);\n\n        Document doc2 = Jsoup.parse(out);\n        String out2 = doc2.body().html();\n        assertEquals(out, out2);\n    }\n\n    @Test\n    public void testClone() {\n        Comment c1 = comment.clone();\n        assertNotSame(comment, c1);\n        assertEquals(comment.getData(), comment.getData());\n        c1.setData(\"New\");\n        assertEquals(\"New\", c1.getData());\n        assertNotEquals(c1.getData(), comment.getData());\n    }\n\n    @Test\n    public void isXmlDeclaration() {\n        assertFalse(comment.isXmlDeclaration());\n        assertTrue(decl.isXmlDeclaration());\n    }\n\n    @Test\n    public void asXmlDeclaration() {\n        XmlDeclaration xmlDeclaration = decl.asXmlDeclaration();\n        assertNotNull(xmlDeclaration);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/DataNodeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.internal.QuietAppendable;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class DataNodeTest {\n    \n    static QuietAppendable appendable() {\n        return QuietAppendable.wrap(new StringBuilder());\n    }\n\n    @Test\n    public void xmlOutputScriptWithCData() {\n        DataNode node = new DataNode(\"//<![CDATA[\\nscript && <> data]]>\");\n        node.parentNode = new Element(\"script\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"//<![CDATA[\\nscript && <> data]]>\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputScriptWithoutCData() {\n        DataNode node = new DataNode(\"script && <> data\");\n        node.parentNode = new Element(\"script\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"//<![CDATA[\\nscript && <> data\\n//]]>\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputStyleWithCData() {\n        DataNode node = new DataNode(\"/*<![CDATA[*/\\nstyle && <> data]]>\");\n        node.parentNode = new Element(\"style\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"/*<![CDATA[*/\\nstyle && <> data]]>\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputStyleWithoutCData() {\n        DataNode node = new DataNode(\"style && <> data\");\n        node.parentNode = new Element(\"style\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"/*<![CDATA[*/\\nstyle && <> data\\n/*]]>*/\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputOtherWithCData() {\n        DataNode node = new DataNode(\"<![CDATA[other && <> data]]>\");\n        node.parentNode = new Element(\"other\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"<![CDATA[other && <> data]]>\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputOtherWithoutCData() {\n        DataNode node = new DataNode(\"other && <> data\");\n        node.parentNode = new Element(\"other\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"<![CDATA[other && <> data]]>\", accum.toString());\n    }\n\n    @Test\n    public void xmlOutputOrphanWithoutCData() {\n        DataNode node = new DataNode(\"other && <> data\");\n        QuietAppendable accum = appendable();\n        node.outerHtmlHead(accum, new Document.OutputSettings().syntax(Document.OutputSettings.Syntax.xml));\n        assertEquals(\"<![CDATA[other && <> data]]>\", accum.toString());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/DocumentTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.nodes.Document.OutputSettings;\nimport org.jsoup.nodes.Document.OutputSettings.Syntax;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests for Document.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class DocumentTest {\n    private static final String charsetUtf8 = \"UTF-8\";\n    private static final String charsetIso8859 = \"ISO-8859-1\";\n\n\n    @Test public void setTextPreservesDocumentStructure() {\n        Document doc = Jsoup.parse(\"<p>Hello</p>\");\n        doc.text(\"Replaced\");\n        assertEquals(\"Replaced\", doc.text());\n        assertEquals(\"Replaced\", doc.body().text());\n        assertEquals(1, doc.select(\"head\").size());\n    }\n\n    @Test public void testTitles() {\n        Document noTitle = Jsoup.parse(\"<p>Hello</p>\");\n        Document withTitle = Jsoup.parse(\"<title>First</title><title>Ignore</title><p>Hello</p>\");\n\n        assertEquals(\"\", noTitle.title());\n        noTitle.title(\"Hello\");\n        assertEquals(\"Hello\", noTitle.title());\n        assertEquals(\"Hello\", noTitle.select(\"title\").first().text());\n\n        assertEquals(\"First\", withTitle.title());\n        withTitle.title(\"Hello\");\n        assertEquals(\"Hello\", withTitle.title());\n        assertEquals(\"Hello\", withTitle.select(\"title\").first().text());\n\n        Document normaliseTitle = Jsoup.parse(\"<title>   Hello\\nthere   \\n   now   \\n\");\n        assertEquals(\"Hello there now\", normaliseTitle.title());\n    }\n\n    @Test public void testOutputEncoding() {\n        Document doc = Jsoup.parse(\"<p title=π>π & < > </p>\");\n        // default is utf-8\n        assertEquals(\"<p title=\\\"π\\\">π &amp; &lt; &gt;</p>\", doc.body().html());\n        assertEquals(\"UTF-8\", doc.outputSettings().charset().name());\n\n        doc.outputSettings().charset(\"ascii\");\n        assertEquals(Entities.EscapeMode.base, doc.outputSettings().escapeMode());\n        assertEquals(\"<p title=\\\"&#x3c0;\\\">&#x3c0; &amp; &lt; &gt;</p>\", doc.body().html());\n\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended);\n        assertEquals(\"<p title=\\\"&pi;\\\">&pi; &amp; &lt; &gt;</p>\", doc.body().html());\n    }\n\n    @Test public void testXhtmlReferences() {\n        Document doc = Jsoup.parse(\"&lt; &gt; &amp; &quot; &apos; &times;\");\n        doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);\n        assertEquals(\"&lt; &gt; &amp; \\\" ' ×\", doc.body().html());\n    }\n\n    @Test public void testNormalisesStructure() {\n        Document doc = Jsoup.parse(\"<html><head><script>one</script><noscript><p>two</p></noscript></head><body><p>three</p></body><p>four</p></html>\");\n        assertEquals(\"<html><head><script>one</script><noscript>&lt;p&gt;two</noscript></head><body><p>three</p><p>four</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void accessorsWillNormalizeStructure() {\n        Document doc = new Document(\"\");\n        assertEquals(\"\", doc.html());\n\n        Element body = doc.body();\n        assertEquals(\"body\", body.tagName());\n        Element head = doc.head();\n        assertEquals(\"head\", head.tagName());\n        assertEquals(\"<html><head></head><body></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void accessorsAreCaseInsensitive() {\n        Parser parser = Parser.htmlParser().settings(ParseSettings.preserveCase);\n        Document doc = parser.parseInput(\"<!DOCTYPE html><HTML><HEAD><TITLE>SHOUTY</TITLE></HEAD><BODY>HELLO</BODY></HTML>\", \"\");\n\n        Element body = doc.body();\n        assertEquals(\"BODY\", body.tagName());\n        assertEquals(\"body\", body.normalName());\n        Element head = doc.head();\n        assertEquals(\"HEAD\", head.tagName());\n        assertEquals(\"body\", body.normalName());\n\n        Element root = doc.selectFirst(\"html\");\n        assertEquals(\"HTML\", root.tagName());\n        assertEquals(\"html\", root.normalName());\n        assertEquals(\"SHOUTY\", doc.title());\n    }\n\n    @Test public void testClone() {\n        Document doc = Jsoup.parse(\"<title>Hello</title> <p>One<p>Two\");\n        Document clone = doc.clone();\n        assertNotSame(doc, clone);\n        assertTrue(doc.hasSameValue(clone));\n        assertSame(doc.parser(), clone.parser());\n        assertNotSame(doc.outputSettings(), clone.outputSettings());\n\n        assertEquals(\"<html><head><title>Hello</title></head><body><p>One</p><p>Two</p></body></html>\", TextUtil.stripNewlines(clone.html()));\n        clone.title(\"Hello there\");\n        assertFalse(doc.hasSameValue(clone));\n        clone.expectFirst(\"p\").text(\"One more\").attr(\"id\", \"1\");\n        assertEquals(\"<html><head><title>Hello there</title></head><body><p id=\\\"1\\\">One more</p><p>Two</p></body></html>\", TextUtil.stripNewlines(clone.html()));\n        assertEquals(\"<html><head><title>Hello</title></head><body><p>One</p><p>Two</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void testBasicIndent() {\n        Document doc = Jsoup.parse(\"<title>Hello</title> <p>One\\n<p>Two\\n\");\n        String expect = \"<html>\\n <head>\\n  <title>Hello</title>\\n </head>\\n <body>\\n  <p>One</p>\\n  <p>Two</p>\\n </body>\\n</html>\";\n        String html = doc.html();\n        assertEquals(expect, html);\n    }\n\n    @Test public void testClonesDeclarations() {\n        Document doc = Jsoup.parse(\"<!DOCTYPE html><html><head><title>Doctype test\");\n        Document clone = doc.clone();\n\n        assertEquals(doc.html(), clone.html());\n        assertEquals(\"<!doctype html><html><head><title>Doctype test</title></head><body></body></html>\",\n                TextUtil.stripNewlines(clone.html()));\n    }\n\n    @Test public void testLocation() throws IOException {\n        // tests location vs base href\n        File in = ParseTest.getFile(\"/htmltests/basehref.html\");\n        Document doc = Jsoup.parse(in, \"UTF-8\", \"http://example.com/\");\n        String location = doc.location();\n        String baseUri = doc.baseUri();\n        assertEquals(\"http://example.com/\", location);\n        assertEquals(\"https://example.com/path/file.html?query\", baseUri);\n        assertEquals(\"./anotherfile.html\", doc.expectFirst(\"a\").attr(\"href\"));\n        assertEquals(\"https://example.com/path/anotherfile.html\", doc.expectFirst(\"a\").attr(\"abs:href\"));\n    }\n\n    @Test public void testLocationFromString() {\n        Document doc = Jsoup.parse(\"<p>Hello\");\n        assertEquals(\"\", doc.location());\n    }\n\n    @Test public void testHtmlAndXmlSyntax() {\n        String h = \"<!DOCTYPE html><body><img async checked='checked' src='&<>\\\"'>&lt;&gt;&amp;&quot;<foo />bar\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"foo\", Parser.NamespaceHtml).set(Tag.SelfClose); // customize foo to allow self close\n        Document doc = Jsoup.parse(h, parser);\n\n        doc.outputSettings().syntax(Syntax.html);\n        assertEquals(\"<!doctype html>\\n\" +\n                \"<html>\\n\" +\n                \" <head></head>\\n\" +\n                \" <body>\\n\" +\n                \"  <img async checked src=\\\"&amp;&lt;&gt;&quot;\\\">&lt;&gt;&amp;\\\"<foo></foo>bar\\n\" + // html won't include self-closing\n                \" </body>\\n\" +\n                \"</html>\", doc.html());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        assertEquals(\"<!DOCTYPE html>\\n\" +\n                \"<html>\\n\" +\n                \" <head></head>\\n\" +\n                \" <body>\\n\" +\n                \"  <img async=\\\"\\\" checked=\\\"checked\\\" src=\\\"&amp;&lt;&gt;&quot;\\\" />&lt;&gt;&amp;\\\"<foo />bar\\n\" + // xml will\n                \" </body>\\n\" +\n                \"</html>\", doc.html());\n    }\n\n    @Test public void htmlParseDefaultsToHtmlOutputSyntax() {\n        Document doc = Jsoup.parse(\"x\");\n        assertEquals(Syntax.html, doc.outputSettings().syntax());\n    }\n\n    @Test public void testHtmlAppendable() {\n    \tString htmlContent = \"<html><head><title>Hello</title></head><body><p>One</p><p>Two</p></body></html>\";\n    \tDocument document = Jsoup.parse(htmlContent);\n    \tOutputSettings outputSettings = new OutputSettings();\n\n    \toutputSettings.prettyPrint(false);\n    \tdocument.outputSettings(outputSettings);\n    \tassertEquals(htmlContent, document.html(new StringWriter()).toString());\n    }\n\n    @Test public void testOverflowClone() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"<head><base href='https://jsoup.org/'>\");\n        for (int i = 0; i < 100000; i++) {\n            sb.append(\"<div>\");\n        }\n        sb.append(\"<p>Hello <a href='/example.html'>there</a>\");\n\n        Document doc = Jsoup.parse(sb.toString());\n\n        String expectedLink = \"https://jsoup.org/example.html\";\n        assertEquals(expectedLink, doc.selectFirst(\"a\").attr(\"abs:href\"));\n        Document clone = doc.clone();\n        doc.hasSameValue(clone);\n        assertEquals(expectedLink, clone.selectFirst(\"a\").attr(\"abs:href\"));\n    }\n\n    @Test public void DocumentsWithSameContentAreEqual() {\n        Document docA = Jsoup.parse(\"<div/>One\");\n        Document docB = Jsoup.parse(\"<div/>One\");\n        Document docC = Jsoup.parse(\"<div/>Two\");\n\n        assertNotEquals(docA, docB);\n        assertEquals(docA, docA);\n        assertEquals(docA.hashCode(), docA.hashCode());\n        assertNotEquals(docA.hashCode(), docC.hashCode());\n    }\n\n    @Test public void DocumentsWithSameContentAreVerifiable() {\n        Document docA = Jsoup.parse(\"<div/>One\");\n        Document docB = Jsoup.parse(\"<div/>One\");\n        Document docC = Jsoup.parse(\"<div/>Two\");\n\n        assertTrue(docA.hasSameValue(docB));\n        assertFalse(docA.hasSameValue(docC));\n    }\n\n    @Test\n    public void testMetaCharsetUpdateUtf8() {\n        final Document doc = createHtmlDocument(\"changeThis\");\n        doc.charset(Charset.forName(charsetUtf8));\n\n        final String htmlCharsetUTF8 = \"<html>\\n\" +\n                                        \" <head>\\n\" +\n                                        \"  <meta charset=\\\"\" + charsetUtf8 + \"\\\">\\n\" +\n                                        \" </head>\\n\" +\n                                        \" <body></body>\\n\" +\n                                        \"</html>\";\n        assertEquals(htmlCharsetUTF8, doc.toString());\n\n        Element selectedElement = doc.select(\"meta[charset]\").first();\n        assertEquals(charsetUtf8, doc.charset().name());\n        assertEquals(charsetUtf8, selectedElement.attr(\"charset\"));\n        assertEquals(doc.charset(), doc.outputSettings().charset());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateIso8859() {\n        final Document doc = createHtmlDocument(\"changeThis\");\n        doc.charset(Charset.forName(charsetIso8859));\n\n        final String htmlCharsetISO = \"<html>\\n\" +\n                                        \" <head>\\n\" +\n                                        \"  <meta charset=\\\"\" + charsetIso8859 + \"\\\">\\n\" +\n                                        \" </head>\\n\" +\n                                        \" <body></body>\\n\" +\n                                        \"</html>\";\n        assertEquals(htmlCharsetISO, doc.toString());\n\n        Element selectedElement = doc.select(\"meta[charset]\").first();\n        assertEquals(charsetIso8859, doc.charset().name());\n        assertEquals(charsetIso8859, selectedElement.attr(\"charset\"));\n        assertEquals(doc.charset(), doc.outputSettings().charset());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateNoCharset() {\n        final Document docNoCharset = Document.createShell(\"\");\n        docNoCharset.charset(Charset.forName(charsetUtf8));\n\n        assertEquals(charsetUtf8, docNoCharset.select(\"meta[charset]\").first().attr(\"charset\"));\n\n        final String htmlCharsetUTF8 = \"<html>\\n\" +\n                                        \" <head>\\n\" +\n                                        \"  <meta charset=\\\"\" + charsetUtf8 + \"\\\">\\n\" +\n                                        \" </head>\\n\" +\n                                        \" <body></body>\\n\" +\n                                        \"</html>\";\n        assertEquals(htmlCharsetUTF8, docNoCharset.toString());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateDisabled() {\n        final Document docDisabled = Document.createShell(\"\");\n\n        final String htmlNoCharset = \"<html>\\n\" +\n                                        \" <head></head>\\n\" +\n                                        \" <body></body>\\n\" +\n                                        \"</html>\";\n        assertEquals(htmlNoCharset, docDisabled.toString());\n        assertNull(docDisabled.select(\"meta[charset]\").first());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateDisabledNoChanges() {\n        final Document doc = createHtmlDocument(\"dontTouch\");\n\n        final String htmlCharset = \"<html>\\n\" +\n                                    \" <head>\\n\" +\n                                    \"  <meta charset=\\\"dontTouch\\\">\\n\" +\n                                    \"  <meta name=\\\"charset\\\" content=\\\"dontTouch\\\">\\n\" +\n                                    \" </head>\\n\" +\n                                    \" <body></body>\\n\" +\n                                    \"</html>\";\n        assertEquals(htmlCharset, doc.toString());\n\n        Element selectedElement = doc.select(\"meta[charset]\").first();\n        assertNotNull(selectedElement);\n        assertEquals(\"dontTouch\", selectedElement.attr(\"charset\"));\n\n        selectedElement = doc.select(\"meta[name=charset]\").first();\n        assertNotNull(selectedElement);\n        assertEquals(\"dontTouch\", selectedElement.attr(\"content\"));\n    }\n\n    @Test\n    public void testMetaCharsetUpdateEnabledAfterCharsetChange() {\n        final Document doc = createHtmlDocument(\"dontTouch\");\n        doc.charset(Charset.forName(charsetUtf8));\n\n        Element selectedElement = doc.select(\"meta[charset]\").first();\n        assertEquals(charsetUtf8, selectedElement.attr(\"charset\"));\n        assertTrue(doc.select(\"meta[name=charset]\").isEmpty());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateCleanup() {\n        final Document doc = createHtmlDocument(\"dontTouch\");\n        doc.charset(Charset.forName(charsetUtf8));\n\n        final String htmlCharsetUTF8 = \"<html>\\n\" +\n                                        \" <head>\\n\" +\n                                        \"  <meta charset=\\\"\" + charsetUtf8 + \"\\\">\\n\" +\n                                        \" </head>\\n\" +\n                                        \" <body></body>\\n\" +\n                                        \"</html>\";\n\n        assertEquals(htmlCharsetUTF8, doc.toString());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateXmlUtf8() {\n        final Document doc = createXmlDocument(\"1.0\", \"changeThis\", true);\n        doc.charset(Charset.forName(charsetUtf8));\n\n        final String xmlCharsetUTF8 = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\" +\n            \"<root>node</root>\";\n        assertEquals(xmlCharsetUTF8, doc.toString());\n\n        XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0);\n        assertEquals(charsetUtf8, doc.charset().name());\n        assertEquals(charsetUtf8, selectedNode.attr(\"encoding\"));\n        assertEquals(doc.charset(), doc.outputSettings().charset());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateXmlIso8859() {\n        final Document doc = createXmlDocument(\"1.0\", \"changeThis\", true);\n        doc.charset(Charset.forName(charsetIso8859));\n\n        final String xmlCharsetISO = \"<?xml version=\\\"1.0\\\" encoding=\\\"ISO-8859-1\\\"?>\\n\" +\n            \"<root>node</root>\";\n        assertEquals(xmlCharsetISO, doc.toString());\n\n        XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0);\n        assertEquals(charsetIso8859, doc.charset().name());\n        assertEquals(charsetIso8859, selectedNode.attr(\"encoding\"));\n        assertEquals(doc.charset(), doc.outputSettings().charset());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateXmlNoCharset() {\n        final Document doc = createXmlDocument(\"1.0\", \"none\", false);\n        doc.charset(Charset.forName(charsetUtf8));\n\n        final String xmlCharsetUTF8 = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\" +\n            \"<root>node</root>\";\n        assertEquals(xmlCharsetUTF8, doc.toString());\n\n        XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0);\n        assertEquals(charsetUtf8, selectedNode.attr(\"encoding\"));\n    }\n\n    @Test\n    public void testMetaCharsetUpdateXmlDisabled() {\n        final Document doc = createXmlDocument(\"none\", \"none\", false);\n\n        final String xmlNoCharset = \"<root>node</root>\";\n        assertEquals(xmlNoCharset, doc.toString());\n    }\n\n    @Test\n    public void testMetaCharsetUpdateXmlDisabledNoChanges() {\n        final Document doc = createXmlDocument(\"dontTouch\", \"dontTouch\", true);\n\n        final String xmlCharset = \"<?xml version=\\\"dontTouch\\\" encoding=\\\"dontTouch\\\"?>\\n\" +\n            \"<root>node</root>\";\n        assertEquals(xmlCharset, doc.toString());\n\n        XmlDeclaration selectedNode = (XmlDeclaration) doc.childNode(0);\n        assertEquals(\"dontTouch\", selectedNode.attr(\"encoding\"));\n        assertEquals(\"dontTouch\", selectedNode.attr(\"version\"));\n    }\n\n    private Document createHtmlDocument(String charset) {\n        final Document doc = Document.createShell(\"\");\n        doc.head().appendElement(\"meta\").attr(\"charset\", charset);\n        doc.head().appendElement(\"meta\").attr(\"name\", \"charset\").attr(\"content\", charset);\n\n        return doc;\n    }\n\n    private Document createXmlDocument(String version, String charset, boolean addDecl) {\n        final Document doc = new Document(\"\");\n        doc.appendElement(\"root\").text(\"node\");\n        doc.outputSettings().syntax(Syntax.xml);\n\n        if(addDecl) {\n            XmlDeclaration decl = new XmlDeclaration(\"xml\", false);\n            decl.attr(\"version\", version);\n            decl.attr(\"encoding\", charset);\n            doc.prependChild(decl);\n        }\n\n        return doc;\n    }\n\n    @Test void charsetOnEmptyDoc() {\n        Document xml = new Document(Parser.NamespaceXml, \"https://example.com\"); // no nodes\n        xml.outputSettings().syntax(Syntax.xml);\n        xml.charset(StandardCharsets.UTF_8);\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\", xml.html());\n\n        Document html = new Document(\"https://example.com\");\n        html.charset(StandardCharsets.UTF_8);\n        assertEquals(\"<html><head><meta charset=\\\"UTF-8\\\"></head></html>\", TextUtil.stripNewlines(html.html()));\n    }\n\n    @Test\n    public void testShiftJisRoundtrip() throws Exception {\n        String input =\n                \"<html>\"\n                        +   \"<head>\"\n                        +     \"<meta http-equiv=\\\"content-type\\\" content=\\\"text/html; charset=Shift_JIS\\\" />\"\n                        +   \"</head>\"\n                        +   \"<body>\"\n                        +     \"before&nbsp;after\"\n                        +   \"</body>\"\n                        + \"</html>\";\n        InputStream is = new ByteArrayInputStream(input.getBytes(StandardCharsets.US_ASCII));\n\n        Document doc = Jsoup.parse(is, null, \"http://example.com\");\n        doc.outputSettings().escapeMode(Entities.EscapeMode.xhtml);\n\n        String output = new String(doc.html().getBytes(doc.outputSettings().charset()), doc.outputSettings().charset());\n\n        assertFalse(output.contains(\"?\"), \"Should not have contained a '?'.\");\n        assertTrue(output.contains(\"&#xa0;\") || output.contains(\"&nbsp;\"),\n                \"Should have contained a '&#xa0;' or a '&nbsp;'.\");\n    }\n\n    @Test public void parseAndHtmlOnDifferentThreads() throws InterruptedException {\n        String html = \"<p>Alrighty then it's not \\uD83D\\uDCA9. <span>Next</span></p>\"; // 💩\n        String asci = \"<p>Alrighty then it's not &#x1f4a9;. <span>Next</span></p>\";\n\n        final Document doc = Jsoup.parse(html);\n        final String[] out = new String[1];\n        final Elements p = doc.select(\"p\");\n        assertEquals(html, p.outerHtml());\n\n        Thread thread = new Thread(() -> {\n            out[0] = p.outerHtml();\n            doc.outputSettings().charset(StandardCharsets.US_ASCII);\n        });\n        thread.start();\n        thread.join();\n\n        assertEquals(html, out[0]);\n        assertEquals(StandardCharsets.US_ASCII, doc.outputSettings().charset());\n        assertEquals(asci, p.outerHtml());\n    }\n\n    @Test public void testDocumentTypeGet() {\n        String html = \"\\n\\n<!-- comment -->  <!doctype html><p>One</p>\";\n        Document doc = Jsoup.parse(html);\n        DocumentType documentType = doc.documentType();\n        assertNotNull(documentType);\n        assertEquals(\"html\", documentType.name());\n    }\n\n    @Test public void framesetSupportsBodyMethod() {\n        String html = \"<html><head><title>Frame Test</title></head><frameset id=id><frame src=foo.html></frameset>\";\n        Document doc = Jsoup.parse(html);\n        Element head = doc.head();\n        assertNotNull(head);\n        assertEquals(\"Frame Test\", doc.title());\n\n        // Frameset docs per html5 spec have no body element - but instead a frameset elelemt\n        assertNull(doc.selectFirst(\"body\"));\n        Element frameset = doc.selectFirst(\"frameset\");\n        assertNotNull(frameset);\n\n        // the body() method returns body or frameset and does not otherwise modify the document\n        // doing it in body() vs parse keeps the html close to original for round-trip option\n        Element body = doc.body();\n        assertNotNull(body);\n        assertSame(frameset, body);\n        assertEquals(\"frame\", body.child(0).tagName());\n\n        assertNull(doc.selectFirst(\"body\")); // did not vivify a body element\n\n        String expected = \"<html>\\n\" +\n            \" <head>\\n\" +\n            \"  <title>Frame Test</title>\\n\" +\n            \" </head>\\n\" +\n            \" <frameset id=\\\"id\\\">\\n\" +\n            \"  <frame src=\\\"foo.html\\\">\\n\" +\n            \" </frameset>\\n\" +\n            \"</html>\";\n        assertEquals(expected, doc.html());\n    }\n\n    @Test void forms() {\n        String html = \"<body><form id=1><input name=foo></form><form id=2><input name=bar>\";\n        Document doc = Jsoup.parse(html);\n\n        List<FormElement> forms = doc.forms();\n        assertEquals(2, forms.size());\n        FormElement form = forms.get(1);\n        assertEquals(1, form.elements().size());\n        assertEquals(\"bar\", form.elements().first().attr(\"name\"));\n\n        String emptyHtml = \"<body>\";\n        Document emptyDoc = Jsoup.parse(emptyHtml);\n        assertEquals(0, emptyDoc.forms().size());\n    }\n\n    @Test void expectForm() {\n        String html = \"<body><div name=form></div><form id=1 name=form><input name=foo></form><form id=2><input name=bar>\";\n        Document doc = Jsoup.parse(html);\n\n        // test finds first <form>\n        FormElement formEl1 = doc.expectForm(\"[name=form]\");\n        assertEquals(\"1\", formEl1.id()); // and not the div\n\n        FormElement formEl2 = doc.expectForm(\"form\");\n        assertEquals(\"1\", formEl2.id());\n\n        FormElement formEl3 = doc.expectForm(\"form:has([name=bar])\");\n        assertEquals(\"2\", formEl3.id());\n\n        boolean threw = false;\n        try {\n            FormElement nix = doc.expectForm(\"div\");\n        } catch (IllegalArgumentException e) {\n            threw = true;\n        }\n        assertTrue(threw);\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/DocumentTypeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * Tests for the DocumentType node\n *\n * @author Jonathan Hedley, http://jonathanhedley.com/\n */\npublic class DocumentTypeTest {\n    @Test\n    public void constructorValidationOkWithBlankName() {\n        new DocumentType(\"\",\"\", \"\");\n    }\n\n    @Test\n    public void constructorValidationThrowsExceptionOnNulls() {\n        assertThrows(IllegalArgumentException.class, () -> new DocumentType(\"html\", null, null));\n    }\n\n    @Test\n    public void constructorValidationOkWithBlankPublicAndSystemIds() {\n        new DocumentType(\"html\",\"\", \"\");\n    }\n\n    @Test public void outerHtmlGeneration() {\n        DocumentType html5 = new DocumentType(\"html\", \"\", \"\");\n        assertEquals(\"<!doctype html>\", html5.outerHtml());\n\n        DocumentType publicDocType = new DocumentType(\"html\", \"-//IETF//DTD HTML//\", \"\");\n        assertEquals(\"<!DOCTYPE html PUBLIC \\\"-//IETF//DTD HTML//\\\">\", publicDocType.outerHtml());\n\n        DocumentType systemDocType = new DocumentType(\"html\", \"\", \"http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd\");\n        assertEquals(\"<!DOCTYPE html SYSTEM \\\"http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd\\\">\", systemDocType.outerHtml());\n\n        DocumentType combo = new DocumentType(\"notHtml\", \"--public\", \"--system\");\n        assertEquals(\"<!DOCTYPE notHtml PUBLIC \\\"--public\\\" \\\"--system\\\">\", combo.outerHtml());\n        assertEquals(\"notHtml\", combo.name());\n        assertEquals(\"--public\", combo.publicId());\n        assertEquals(\"--system\", combo.systemId());\n    }\n\n    @Test public void testRoundTrip() {\n        String base = \"<!DOCTYPE html>\";\n        assertEquals(\"<!doctype html>\", htmlOutput(base));\n        assertEquals(base, xmlOutput(base));\n\n        String publicDoc = \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\";\n        assertEquals(publicDoc, htmlOutput(publicDoc));\n        assertEquals(publicDoc, xmlOutput(publicDoc));\n\n        String systemDoc = \"<!DOCTYPE html SYSTEM \\\"exampledtdfile.dtd\\\">\";\n        assertEquals(systemDoc, htmlOutput(systemDoc));\n        assertEquals(systemDoc, xmlOutput(systemDoc));\n\n        String legacyDoc = \"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\">\";\n        assertEquals(legacyDoc, htmlOutput(legacyDoc));\n        assertEquals(legacyDoc, xmlOutput(legacyDoc));\n    }\n\n    private String htmlOutput(String in) {\n        DocumentType type = (DocumentType) Jsoup.parse(in).childNode(0);\n        return type.outerHtml();\n    }\n\n    private String xmlOutput(String in) {\n        return Jsoup.parse(in, \"\", Parser.xmlParser()).childNode(0).outerHtml();\n    }\n\n    @Test void attributes() {\n        Document doc = Jsoup.parse(\"<!DOCTYPE html>\");\n        DocumentType doctype = doc.documentType();\n        assertEquals(\"#doctype\", doctype.nodeName());\n        assertEquals(\"html\", doctype.name());\n        assertEquals(\"html\", doctype.attr(\"name\"));\n        assertEquals(\"\", doctype.publicId());\n        assertEquals(\"\", doctype.systemId());\n\n        doc = Jsoup.parse(\"<!DOCTYPE notHtml PUBLIC \\\"--public\\\" \\\"--system\\\">\");\n        doctype = doc.documentType();\n\n        assertEquals(\"#doctype\", doctype.nodeName());\n        assertEquals(\"nothtml\", doctype.name());\n        assertEquals(\"nothtml\", doctype.attr(\"name\"));\n        assertEquals(\"--public\", doctype.publicId());\n        assertEquals(\"--system\", doctype.systemId());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/ElementIT.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class ElementIT {\n    @Test\n    public void testFastReparent() {\n        StringBuilder htmlBuf = new StringBuilder();\n        int rows = 300000;\n        for (int i = 1; i <= rows; i++) {\n            htmlBuf\n                .append(\"<p>El-\")\n                .append(i)\n                .append(\"</p>\");\n        }\n        String html = htmlBuf.toString();\n        Document doc = Jsoup.parse(html);\n        long start = System.currentTimeMillis();\n\n        Element wrapper = new Element(\"div\");\n        List<Node> childNodes = doc.body().childNodes();\n        wrapper.insertChildren(0, childNodes);\n\n        long runtime = System.currentTimeMillis() - start;\n        assertEquals(rows, wrapper.childNodes.size());\n        assertEquals(rows, childNodes.size()); // child nodes is a wrapper, so still there\n        assertEquals(0, doc.body().childNodes().size()); // but on a fresh look, all gone\n\n        doc.body().empty().appendChild(wrapper);\n        Element wrapperAcutal = doc.body().children().get(0);\n        assertEquals(wrapper, wrapperAcutal);\n        assertEquals(\"El-1\", wrapperAcutal.children().get(0).text());\n        assertEquals(\"El-\" + rows, wrapperAcutal.children().get(rows - 1).text());\n        assertTrue(runtime <= 10000);\n    }\n\n    @Test\n    public void testFastReparentExistingContent() {\n        StringBuilder htmlBuf = new StringBuilder();\n        int rows = 300000;\n        for (int i = 1; i <= rows; i++) {\n            htmlBuf\n                .append(\"<p>El-\")\n                .append(i)\n                .append(\"</p>\");\n        }\n        String html = htmlBuf.toString();\n        Document doc = Jsoup.parse(html);\n        long start = System.currentTimeMillis();\n\n        Element wrapper = new Element(\"div\");\n        wrapper.append(\"<p>Prior Content</p>\");\n        wrapper.append(\"<p>End Content</p>\");\n        assertEquals(2, wrapper.childNodes.size());\n\n        List<Node> childNodes = doc.body().childNodes();\n        wrapper.insertChildren(1, childNodes);\n\n        long runtime = System.currentTimeMillis() - start;\n        assertEquals(rows + 2, wrapper.childNodes.size());\n        assertEquals(rows, childNodes.size()); // child nodes is a wrapper, so still there\n        assertEquals(0, doc.body().childNodes().size()); // but on a fresh look, all gone\n\n        doc.body().empty().appendChild(wrapper);\n        Element wrapperAcutal = doc.body().children().get(0);\n        assertEquals(wrapper, wrapperAcutal);\n        assertEquals(\"Prior Content\", wrapperAcutal.children().get(0).text());\n        assertEquals(\"El-1\", wrapperAcutal.children().get(1).text());\n\n        assertEquals(\"El-\" + rows, wrapperAcutal.children().get(rows).text());\n        assertEquals(\"End Content\", wrapperAcutal.children().get(rows + 1).text());\n\n        assertTrue(runtime <= 10000);\n    }\n\n    // These overflow tests take a couple seconds to run, so are in the slow tests\n    @Test void hasTextNoOverflow() {\n        // hasText() was recursive, so could overflow\n        Document doc = new Document(\"https://example.com/\");\n        Element el = doc.body();\n        for (int i = 0; i <= 50000; i++) {\n            el = el.appendChild(doc.createElement(\"p\")).firstElementChild();\n        }\n        assertFalse(doc.hasText());\n        el.text(\"Hello\");\n        assertTrue(doc.hasText());\n        assertEquals(el.text(), doc.text());\n    }\n\n    @Test void dataNoOverflow() {\n        // data() was recursive, so could overflow\n        Document doc = new Document(\"https://example.com/\");\n        Element el = doc.body();\n        for (int i = 0; i <= 50000; i++) {\n            el = el.appendChild(doc.createElement(\"p\")).firstElementChild();\n        }\n        Element script = el.appendElement(\"script\");\n        script.text(\"script\"); // holds data nodes, so inserts as data, not text\n        assertFalse(script.hasText());\n        assertEquals(\"script\", script.data());\n        assertEquals(el.data(), doc.data());\n    }\n\n    @Test void parentsNoOverflow() {\n        // parents() was recursive, so could overflow\n        Document doc = new Document(\"https://example.com/\");\n        Element el = doc.body();\n        int num = 50000;\n        for (int i = 0; i <= num; i++) {\n            el = el.appendChild(doc.createElement(\"p\")).firstElementChild();\n        }\n        Elements parents = el.parents();\n        assertEquals(num+2, parents.size()); // +2 for html and body\n        assertEquals(doc, el.ownerDocument());\n    }\n\n    @Test void wrapNoOverflow() {\n        // deepChild was recursive, so could overflow if presented with a fairly insane wrap\n        Document doc = new Document(\"https://example.com/\");\n        doc.parser().setMaxDepth(Integer.MAX_VALUE); // don't limit to 512\n        Element el = doc.body().appendElement(\"p\");\n        int num = 50000;\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i <= num; i++) {\n            sb.append(\"<div>\");\n        }\n        el.wrap(sb.toString());\n        String html = doc.body().html();\n        assertTrue(html.startsWith(\"<div>\"));\n        assertEquals(num + 3, el.parents().size()); // + 3 is for body, html, document\n    }\n\n    @Test\n    public void testConcurrentMerge() throws Exception {\n        // https://github.com/jhy/jsoup/discussions/2280 / https://github.com/jhy/jsoup/issues/2281\n        // This was failing because the template.clone().append(html) was reusing the same underlying parser object.\n        // Document#clone now correctly clones its parser.\n\n        class TemplateMerger {\n            private final Document templateDoc;\n\n            public TemplateMerger(Document template) {\n                this.templateDoc = template;\n            }\n\n            public Document mergeWith(Document inputDoc) {\n                Document merged = templateDoc.clone();\n                Element content = merged.getElementById(\"content\");\n                content.append(inputDoc.html());\n                return merged;\n            }\n        }\n\n        String templateHtml = \"<html><body><div id='content'></div></body></html>\";\n        Document templateDoc = Jsoup.parse(templateHtml);\n        TemplateMerger merger = new TemplateMerger(templateDoc);\n\n        Runnable mergeTask = () -> {\n            try {\n                for (int i = 0; i < 1000; i++) {\n                    String inputHtml = \"<html><body><p>Some content</p></body></html>\";\n                    Document inputDoc = Jsoup.parse(inputHtml);\n                    Document merged = merger.mergeWith(inputDoc);\n                    assertNotNull(merged);\n                }\n            } catch (Exception e) {\n                fail(e);\n            }\n        };\n\n        int threadCount = 10;\n        Thread[] threads = new Thread[threadCount];\n        for (int i = 0; i < threadCount; i++) {\n            threads[i] = new Thread(mergeTask);\n            threads[i].start();\n        }\n        for (Thread t : threads) {\n            t.join();\n        }\n    }\n\n    @Test\n    void concurrentChildren() throws InterruptedException {\n        int childCount = 200;\n\n        Document doc = Jsoup.parse(\"<div></div>\");\n        Element div = doc.expectFirst(\"div\");\n        for (int i = 0; i < childCount; i++) {\n            div.appendElement(\"p\").after(\"Some text\");\n        }\n\n        int threadCount = 100;\n        int iterCount = 10000;\n        CountDownLatch startLatch = new CountDownLatch(1);\n        CountDownLatch endLatch = new CountDownLatch(threadCount);\n        AtomicReference<Throwable> failure = new AtomicReference<>();\n\n        for (int i = 0; i < threadCount; i++) {\n            Thread t = new Thread(() -> {\n                try {\n                    startLatch.await();\n                    for (int j = 0; j < iterCount; j++) {\n                        Elements children = div.children();\n                        assertEquals(childCount, children.size());\n                    }\n                } catch (Throwable e) {\n                    System.err.println(e);\n                    failure.set(e);\n                } finally {\n                    endLatch.countDown();\n                }\n            });\n            t.start();\n        }\n\n        startLatch.countDown();\n        endLatch.await();\n        if (failure.get() != null) {\n            throw new AssertionError(failure.get());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/ElementTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.helper.ValidationException;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.Evaluator;\nimport org.jsoup.select.NodeFilter;\nimport org.jsoup.select.NodeVisitor;\nimport org.jsoup.select.QueryParser;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.nodes.NodeIteratorTest.assertIterates;\nimport static org.jsoup.nodes.NodeIteratorTest.trackSeen;\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.jsoup.select.SelectorTest.assertSelectedIds;\nimport static org.jsoup.select.SelectorTest.assertSelectedOwnText;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests for Element (DOM stuff mostly).\n\n @author Jonathan Hedley */\npublic class ElementTest {\n    private final String reference = \"<div id=div1><p>Hello</p><p>Another <b>element</b></p><div id=div2><img src=foo.png></div></div>\";\n\n    private static void validateScriptContents(String src, Element el) {\n        assertEquals(\"\", el.text()); // it's not text\n        assertEquals(\"\", el.ownText());\n        assertEquals(\"\", el.wholeText());\n        assertEquals(src, el.html());\n        assertEquals(src, el.data());\n    }\n\n    private static void validateXmlScriptContents(Element el) {\n        assertEquals(\"var foo = 5 < 2; var bar = 1 && 2;\", el.text());\n        assertEquals(\"var foo = 5 < 2; var bar = 1 && 2;\", el.ownText());\n        assertEquals(\"var foo = 5 < 2;\\nvar bar = 1 && 2;\", el.wholeText());\n        assertEquals(\"var foo = 5 &lt; 2;\\nvar bar = 1 &amp;&amp; 2;\", el.html());\n        assertEquals(\"\", el.data());\n    }\n\n    @Test\n    public void testId() {\n        Document doc = Jsoup.parse(\"<div id=Foo>\");\n        Element el = doc.selectFirst(\"div\");\n        assertEquals(\"Foo\", el.id());\n    }\n\n    @Test\n    public void testSetId() {\n        Document doc = Jsoup.parse(\"<div id=Boo>\");\n        Element el = doc.selectFirst(\"div\");\n        el.id(\"Foo\");\n        assertEquals(\"Foo\", el.id());\n    }\n\n    @Test\n    public void getElementsByTagName() {\n        Document doc = Jsoup.parse(reference);\n        List<Element> divs = doc.getElementsByTag(\"div\");\n        assertEquals(2, divs.size());\n        assertEquals(\"div1\", divs.get(0).id());\n        assertEquals(\"div2\", divs.get(1).id());\n\n        List<Element> ps = doc.getElementsByTag(\"p\");\n        assertEquals(2, ps.size());\n        assertEquals(\"Hello\", ((TextNode) ps.get(0).childNode(0)).getWholeText());\n        assertEquals(\"Another \", ((TextNode) ps.get(1).childNode(0)).getWholeText());\n        List<Element> ps2 = doc.getElementsByTag(\"P\");\n        assertEquals(ps, ps2);\n\n        List<Element> imgs = doc.getElementsByTag(\"img\");\n        assertEquals(\"foo.png\", imgs.get(0).attr(\"src\"));\n\n        List<Element> empty = doc.getElementsByTag(\"wtf\");\n        assertEquals(0, empty.size());\n    }\n\n    @Test\n    public void getNamespacedElementsByTag() {\n        Document doc = Jsoup.parse(\"<div><abc:def id=1>Hello</abc:def></div>\");\n        Elements els = doc.getElementsByTag(\"abc:def\");\n        assertEquals(1, els.size());\n        assertEquals(\"1\", els.first().id());\n        assertEquals(\"abc:def\", els.first().tagName());\n    }\n\n    @Test\n    public void testGetElementById() {\n        Document doc = Jsoup.parse(reference);\n        Element div = doc.getElementById(\"div1\");\n        assertEquals(\"div1\", div.id());\n        assertNull(doc.getElementById(\"none\"));\n\n        Document doc2 = Jsoup.parse(\"<div id=1><div id=2><p>Hello <span id=2>world!</span></p></div></div>\");\n        Element div2 = doc2.getElementById(\"2\");\n        assertEquals(\"div\", div2.tagName()); // not the span\n        Element span = div2.child(0).getElementById(\"2\"); // called from <p> context should be span\n        assertEquals(\"span\", span.tagName());\n    }\n\n    @Test\n    public void testGetText() {\n        Document doc = Jsoup.parse(reference);\n        assertEquals(\"Hello Another element\", doc.text());\n        assertEquals(\"Another element\", doc.getElementsByTag(\"p\").get(1).text());\n    }\n\n    @Test\n    public void testGetChildText() {\n        Document doc = Jsoup.parse(\"<p>Hello <b>there</b> now\");\n        Element p = doc.select(\"p\").first();\n        assertEquals(\"Hello there now\", p.text());\n        assertEquals(\"Hello now\", p.ownText());\n    }\n\n    @Test\n    public void testNormalisesText() {\n        String h = \"<p>Hello<p>There.</p> \\n <p>Here <b>is</b> \\n s<b>om</b>e text.\";\n        Document doc = Jsoup.parse(h);\n        String text = doc.text();\n        assertEquals(\"Hello There. Here is some text.\", text);\n    }\n\n    @Test\n    public void testKeepsPreText() {\n        String h = \"<p>Hello \\n \\n there.</p> <div><pre>  What's \\n\\n  that?</pre>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"Hello there.   What's \\n\\n  that?\", doc.text());\n    }\n\n    @Test\n    public void testKeepsPreTextInCode() {\n        String h = \"<pre><code>code\\n\\ncode</code></pre>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"code\\n\\ncode\", doc.text());\n        assertEquals(\"<pre><code>code\\n\\ncode</code></pre>\", doc.body().html());\n    }\n\n    @Test\n    public void testKeepsPreTextAtDepth() {\n        String h = \"<pre><code><span><b>code\\n\\ncode</b></span></code></pre>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"code\\n\\ncode\", doc.text());\n        assertEquals(\"<pre><code><span><b>code\\n\\ncode</b></span></code></pre>\", doc.body().html());\n    }\n\n    @Test void doesNotWrapBlocksInPre() {\n        // https://github.com/jhy/jsoup/issues/1891\n        String h = \"<pre><span><foo><div>TEST\\n TEST</div></foo></span></pre>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"TEST\\n TEST\", doc.wholeText());\n        assertEquals(h, doc.body().html());\n    }\n\n    @Test\n    public void testBrHasSpace() {\n        Document doc = Jsoup.parse(\"<p>Hello<br>there</p>\");\n        assertEquals(\"Hello there\", doc.text());\n        assertEquals(\"Hello there\", doc.select(\"p\").first().ownText());\n\n        doc = Jsoup.parse(\"<p>Hello <br> there</p>\");\n        assertEquals(\"Hello there\", doc.text());\n    }\n\n    @Test\n    public void testBrHasSpaceCaseSensitive() {\n        Document doc = Jsoup.parse(\"<p>Hello<br>there<BR>now</p>\", Parser.htmlParser().settings(ParseSettings.preserveCase));\n        assertEquals(\"Hello there now\", doc.text());\n        assertEquals(\"Hello there now\", doc.select(\"p\").first().ownText());\n\n        doc = Jsoup.parse(\"<p>Hello <br> there <BR> now</p>\");\n        assertEquals(\"Hello there now\", doc.text());\n    }\n\n    @Test public void textHasSpacesAfterBlock() {\n        Document doc = Jsoup.parse(\"<div>One</div><div>Two</div><span>Three</span><p>Fou<i>r</i></p>\");\n        String text = doc.text();\n        String wholeText = doc.wholeText();\n\n        assertEquals(\"One Two Three Four\", text);\n        assertEquals(\"OneTwoThreeFour\",wholeText);\n\n        assertEquals(\"OneTwo\",Jsoup.parse(\"<span>One</span><span>Two</span>\").text());\n    }\n\n    @Test\n    public void testWholeText() {\n        Document doc = Jsoup.parse(\"<p> Hello\\nthere &nbsp;  </p>\");\n        assertEquals(\" Hello\\nthere    \", doc.wholeText());\n\n        doc = Jsoup.parse(\"<p>Hello  \\n  there</p>\");\n        assertEquals(\"Hello  \\n  there\", doc.wholeText());\n\n        doc = Jsoup.parse(\"<p>Hello  <div>\\n  there</div></p>\");\n        assertEquals(\"Hello  \\n  there\", doc.wholeText());\n    }\n\n    @Test void wholeTextRuns() {\n        Document doc = Jsoup.parse(\"<div><p id=1></p><p id=2> </p><p id=3>.  </p>\");\n\n        Element p1 = doc.expectFirst(\"#1\");\n        Element p2 = doc.expectFirst(\"#2\");\n        Element p3 = doc.expectFirst(\"#3\");\n\n        assertEquals(\"\", p1.wholeText());\n        assertEquals(\" \", p2.wholeText());\n        assertEquals(\".  \", p3.wholeText());\n    }\n\n    @Test void buttonTextHasSpace() {\n        // https://github.com/jhy/jsoup/issues/2105\n        Document doc = Jsoup.parse(\"<html><button>Reply</button><button>All</button></html>\");\n        String text = doc.body().text();\n        String wholetext = doc.body().wholeText();\n\n        assertEquals(\"Reply All\", text);\n        assertEquals(\"ReplyAll\", wholetext);\n    }\n\n    @Test\n    public void testGetSiblings() {\n        Document doc = Jsoup.parse(\"<div><p>Hello<p id=1>there<p>this<p>is<p>an<p id=last>element</div>\");\n        Element p = doc.getElementById(\"1\");\n        assertEquals(\"there\", p.text());\n        assertEquals(\"Hello\", p.previousElementSibling().text());\n        assertEquals(\"this\", p.nextElementSibling().text());\n        assertEquals(\"Hello\", p.firstElementSibling().text());\n        assertEquals(\"element\", p.lastElementSibling().text());\n        assertNull(p.lastElementSibling().nextElementSibling());\n        assertNull(p.firstElementSibling().previousElementSibling());\n    }\n\n    @Test public void nextElementSibling() {\n        Document doc = Jsoup.parse(\"<p>One</p>Two<p>Three</p>\");\n        Element el = doc.expectFirst(\"p\");\n        assertNull(el.previousElementSibling());\n        Element next = el.nextElementSibling();\n        assertNotNull(next);\n        assertEquals(\"Three\", next.text());\n        assertNull(next.nextElementSibling());\n    }\n\n    @Test public void prevElementSibling() {\n        Document doc = Jsoup.parse(\"<p>One</p>Two<p>Three</p>\");\n        Element el = doc.expectFirst(\"p:contains(Three)\");\n        assertNull(el.nextElementSibling());\n        Element prev = el.previousElementSibling();\n        assertNotNull(prev);\n        assertEquals(\"One\", prev.text());\n        assertNull(prev.previousElementSibling());\n    }\n\n    @Test\n    public void testGetSiblingsWithDuplicateContent() {\n        Document doc = Jsoup.parse(\"<div><p>Hello<p id=1>there<p>this<p>this<p>is<p>an<p id=last>element</div>\");\n        Element p = doc.getElementById(\"1\");\n        assertEquals(\"there\", p.text());\n        assertEquals(\"Hello\", p.previousElementSibling().text());\n        assertEquals(\"this\", p.nextElementSibling().text());\n        assertEquals(\"this\", p.nextElementSibling().nextElementSibling().text());\n        assertEquals(\"is\", p.nextElementSibling().nextElementSibling().nextElementSibling().text());\n        assertEquals(\"Hello\", p.firstElementSibling().text());\n        assertEquals(\"element\", p.lastElementSibling().text());\n    }\n\n    @Test\n    public void testFirstElementSiblingOnOrphan() {\n        Element p = new Element(\"p\");\n        assertSame(p, p.firstElementSibling());\n        assertSame(p, p.lastElementSibling());\n    }\n\n    @Test\n    public void testFirstAndLastSiblings() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three\");\n        Element div = doc.expectFirst(\"div\");\n        Element one = div.child(0);\n        Element two = div.child(1);\n        Element three = div.child(2);\n\n        assertSame(one, one.firstElementSibling());\n        assertSame(one, two.firstElementSibling());\n        assertSame(three, three.lastElementSibling());\n        assertSame(three, two.lastElementSibling());\n        assertNull(one.previousElementSibling());\n        assertNull(three.nextElementSibling());\n    }\n\n    @Test\n    public void testGetParents() {\n        Document doc = Jsoup.parse(\"<div><p>Hello <span>there</span></div>\");\n        Element span = doc.select(\"span\").first();\n        Elements parents = span.parents();\n\n        assertEquals(4, parents.size());\n        assertEquals(\"p\", parents.get(0).tagName());\n        assertEquals(\"div\", parents.get(1).tagName());\n        assertEquals(\"body\", parents.get(2).tagName());\n        assertEquals(\"html\", parents.get(3).tagName());\n\n        Element orphan = new Element(\"p\");\n        Elements none = orphan.parents();\n        assertEquals(0, none.size());\n    }\n\n    @Test\n    public void testElementSiblingIndex() {\n        Document doc = Jsoup.parse(\"<div><p>One</p>...<p>Two</p>...<p>Three</p>\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(0, ps.get(0).elementSiblingIndex());\n        assertEquals(1, ps.get(1).elementSiblingIndex());\n        assertEquals(2, ps.get(2).elementSiblingIndex());\n    }\n\n    @Test\n    public void testElementSiblingIndexSameContent() {\n        Document doc = Jsoup.parse(\"<div><p>One</p>...<p>One</p>...<p>One</p>\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(0, ps.get(0).elementSiblingIndex());\n        assertEquals(1, ps.get(1).elementSiblingIndex());\n        assertEquals(2, ps.get(2).elementSiblingIndex());\n    }\n\n    @Test\n    public void testGetElementsWithClass() {\n        Document doc = Jsoup.parse(\"<div class='mellow yellow'><span class=mellow>Hello <b class='yellow'>Yellow!</b></span><p>Empty</p></div>\");\n\n        List<Element> els = doc.getElementsByClass(\"mellow\");\n        assertEquals(2, els.size());\n        assertEquals(\"div\", els.get(0).tagName());\n        assertEquals(\"span\", els.get(1).tagName());\n\n        List<Element> els2 = doc.getElementsByClass(\"yellow\");\n        assertEquals(2, els2.size());\n        assertEquals(\"div\", els2.get(0).tagName());\n        assertEquals(\"b\", els2.get(1).tagName());\n\n        List<Element> none = doc.getElementsByClass(\"solo\");\n        assertEquals(0, none.size());\n    }\n\n    @Test\n    public void testGetElementsWithAttribute() {\n        Document doc = Jsoup.parse(\"<div style='bold'><p title=qux><p><b style></b></p></div>\");\n        List<Element> els = doc.getElementsByAttribute(\"style\");\n        assertEquals(2, els.size());\n        assertEquals(\"div\", els.get(0).tagName());\n        assertEquals(\"b\", els.get(1).tagName());\n\n        List<Element> none = doc.getElementsByAttribute(\"class\");\n        assertEquals(0, none.size());\n    }\n\n    @Test\n    public void testGetElementsWithAttributeDash() {\n        Document doc = Jsoup.parse(\"<meta http-equiv=content-type value=utf8 id=1> <meta name=foo content=bar id=2> <div http-equiv=content-type value=utf8 id=3>\");\n        Elements meta = doc.select(\"meta[http-equiv=content-type], meta[charset]\");\n        assertEquals(1, meta.size());\n        assertEquals(\"1\", meta.first().id());\n    }\n\n    @Test\n    public void testGetElementsWithAttributeValue() {\n        Document doc = Jsoup.parse(\"<div style='bold'><p><p><b style></b></p></div>\");\n        List<Element> els = doc.getElementsByAttributeValue(\"style\", \"bold\");\n        assertEquals(1, els.size());\n        assertEquals(\"div\", els.get(0).tagName());\n\n        List<Element> none = doc.getElementsByAttributeValue(\"style\", \"none\");\n        assertEquals(0, none.size());\n    }\n\n    @Test\n    public void testClassDomMethods() {\n        Document doc = Jsoup.parse(\"<div><span class=' mellow yellow '>Hello <b>Yellow</b></span></div>\");\n        List<Element> els = doc.getElementsByAttribute(\"class\");\n        Element span = els.get(0);\n        assertEquals(\"mellow yellow\", span.className());\n        assertTrue(span.hasClass(\"mellow\"));\n        assertTrue(span.hasClass(\"yellow\"));\n        Set<String> classes = span.classNames();\n        assertEquals(2, classes.size());\n        assertTrue(classes.contains(\"mellow\"));\n        assertTrue(classes.contains(\"yellow\"));\n\n        assertEquals(\"\", doc.className());\n        classes = doc.classNames();\n        assertEquals(0, classes.size());\n        assertFalse(doc.hasClass(\"mellow\"));\n    }\n\n    @Test\n    public void testHasClassDomMethods() {\n        Tag tag = Tag.valueOf(\"a\");\n        Attributes attribs = new Attributes();\n        Element el = new Element(tag, \"\", attribs);\n\n        attribs.put(\"class\", \"toto\");\n        boolean hasClass = el.hasClass(\"toto\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \" toto\");\n        hasClass = el.hasClass(\"toto\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \"toto \");\n        hasClass = el.hasClass(\"toto\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \"\\ttoto \");\n        hasClass = el.hasClass(\"toto\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \"  toto \");\n        hasClass = el.hasClass(\"toto\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \"ab\");\n        hasClass = el.hasClass(\"toto\");\n        assertFalse(hasClass);\n\n        attribs.put(\"class\", \"     \");\n        hasClass = el.hasClass(\"toto\");\n        assertFalse(hasClass);\n\n        attribs.put(\"class\", \"tototo\");\n        hasClass = el.hasClass(\"toto\");\n        assertFalse(hasClass);\n\n        attribs.put(\"class\", \"raulpismuth  \");\n        hasClass = el.hasClass(\"raulpismuth\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \" abcd  raulpismuth efgh \");\n        hasClass = el.hasClass(\"raulpismuth\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \" abcd efgh raulpismuth\");\n        hasClass = el.hasClass(\"raulpismuth\");\n        assertTrue(hasClass);\n\n        attribs.put(\"class\", \" abcd efgh raulpismuth \");\n        hasClass = el.hasClass(\"raulpismuth\");\n        assertTrue(hasClass);\n    }\n\n    @Test\n    public void testClassUpdates() {\n        Document doc = Jsoup.parse(\"<div class='mellow yellow'></div>\");\n        Element div = doc.select(\"div\").first();\n\n        div.addClass(\"green\");\n        assertEquals(\"mellow yellow green\", div.className());\n        div.removeClass(\"red\"); // noop\n        div.removeClass(\"yellow\");\n        assertEquals(\"mellow green\", div.className());\n        div.toggleClass(\"green\").toggleClass(\"red\");\n        assertEquals(\"mellow red\", div.className());\n    }\n\n    @Test\n    public void testOuterHtml() {\n        Document doc = Jsoup.parse(\"<div title='Tags &amp;c.'><img src=foo.png><p><!-- comment -->Hello<p>there\");\n        assertEquals(\"<html><head></head><body><div title=\\\"Tags &amp;c.\\\"><img src=\\\"foo.png\\\"><p><!-- comment -->Hello</p><p>there</p></div></body></html>\",\n            TextUtil.stripNewlines(doc.outerHtml()));\n    }\n\n    @Test\n    public void testInnerHtml() {\n        Document doc = Jsoup.parse(\"<div>\\n <p>Hello</p> </div>\");\n        assertEquals(\"<p>Hello</p>\", doc.getElementsByTag(\"div\").get(0).html());\n    }\n\n    @Test void formatNoTrailingSpace() {\n        // https://github.com/jhy/jsoup/issues/2215\n        String html = \"<html>\\n <head></head>\\n <body>\\n  a\\n  <div>\\n  </div>\\n </body>\\n</html>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<html>\\n <head></head>\\n <body>\\n  a\\n  <div></div>\\n </body>\\n</html>\", doc.html());\n    }\n\n    @Test\n    public void testFormatHtml() {\n        Document doc = Jsoup.parse(\"<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>\");\n        assertEquals(\"<html>\\n <head>\\n  <title>Format test</title>\\n </head>\\n <body>\\n  <div>\\n   <p>Hello <span>jsoup <span>users</span></span></p>\\n   <p>Good.</p>\\n  </div>\\n </body>\\n</html>\", doc.html());\n    }\n\n    @Test\n    public void testFormatOutline() {\n        Document doc = Jsoup.parse(\"<title>Format test</title><div><p>Hello <span>jsoup <span>users</span></span></p><p>Good.</p></div>\");\n        doc.outputSettings().outline(true);\n        assertEquals(\"<html>\\n <head>\\n  <title>Format test</title>\\n </head>\\n <body>\\n  <div>\\n   <p>\\n    Hello\\n    <span>\\n     jsoup\\n     <span>users</span>\\n    </span>\\n   </p>\\n   <p>Good.</p>\\n  </div>\\n </body>\\n</html>\", doc.html());\n    }\n\n    @Test\n    public void testSetIndent() {\n        Document doc = Jsoup.parse(\"<div><p>Hello\\nthere</p></div>\");\n        doc.outputSettings().indentAmount(0);\n        assertEquals(\"<html>\\n<head></head>\\n<body>\\n<div>\\n<p>Hello there</p>\\n</div>\\n</body>\\n</html>\", doc.html());\n    }\n\n    @Test void testIndentLevel() {\n        // deep to test default and extended max\n        StringBuilder divs = new StringBuilder();\n        for (int i = 0; i < 40; i++) {\n            divs.append(\"<div>\");\n        }\n        divs.append(\"Foo\");\n        Document doc = Jsoup.parse(divs.toString());\n        Document.OutputSettings settings = doc.outputSettings();\n\n        int defaultMax = 30;\n        assertEquals(defaultMax, settings.maxPaddingWidth());\n        String html = doc.html();\n        assertTrue(html.contains(\"\\n\" + StringUtil.padding(defaultMax, -1) + \"<div>Foo</div>\\n\"));\n\n        settings.maxPaddingWidth(32);\n        assertEquals(32, settings.maxPaddingWidth());\n        html = doc.html();\n        assertTrue(html.contains(\"\\n\" + StringUtil.padding(32, -1) + \"<div>Foo</div>\\n\"));\n\n        settings.maxPaddingWidth(-1);\n        assertEquals(-1, settings.maxPaddingWidth());\n        html = doc.html();\n        assertTrue(html.contains(\"\\n\" + StringUtil.padding(41, -1) + \"<div>Foo</div>\\n\"));\n    }\n\n    @Test\n    public void testNotPretty() {\n        Document doc = Jsoup.parse(\"<div>   \\n<p>Hello\\n there\\n</p></div>\");\n        doc.outputSettings().prettyPrint(false);\n        assertEquals(\"<html><head></head><body><div>   \\n<p>Hello\\n there\\n</p></div></body></html>\", doc.html());\n\n        Element div = doc.select(\"div\").first();\n        assertEquals(\"   \\n<p>Hello\\n there\\n</p>\", div.html());\n    }\n\n    @Test\n    public void testNotPrettyWithEnDashBody() {\n        String html = \"<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>\";\n        Document document = Jsoup.parse(html);\n        document.outputSettings().prettyPrint(false);\n\n        assertEquals(\"<div><span>1:15</span>–<span>2:15</span>&nbsp;p.m.</div>\", document.body().html());\n    }\n\n    @Test\n    public void testPrettyWithEnDashBody() {\n        String html = \"<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>\";\n        Document document = Jsoup.parse(html);\n\n        assertEquals(\"<div>\\n <span>1:15</span>–<span>2:15</span>&nbsp;p.m.\\n</div>\", document.body().html());\n    }\n\n    @Test\n    public void testPrettyAndOutlineWithEnDashBody() {\n        String html = \"<div><span>1:15</span>&ndash;<span>2:15</span>&nbsp;p.m.</div>\";\n        Document document = Jsoup.parse(html);\n        document.outputSettings().outline(true);\n\n        assertEquals(\"<div>\\n <span>1:15</span>\\n –\\n <span>2:15</span>\\n &nbsp;p.m.\\n</div>\", document.body().html());\n    }\n\n    @Test\n    public void testBasicFormats() {\n        String html = \"<span>0</span>.<div><span>1</span>-<span>2</span><p> <span>3</span>-<span>4</span><div> 5 </div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\n            \"<span>0</span>.\\n\" +\n                \"<div>\\n\" +\n                \" <span>1</span>-<span>2</span>\\n\" +\n                \" <p><span>3</span>-<span>4</span></p>\\n\" +\n                \" <div>5</div>\\n\" +\n                \"</div>\", doc.body().html());\n    }\n\n    @Test\n    public void testEmptyElementFormatHtml() {\n        // don't put newlines into empty blocks\n        Document doc = Jsoup.parse(\"<section><div></div></section>\");\n        assertEquals(\"<section>\\n <div></div>\\n</section>\", doc.select(\"section\").first().outerHtml());\n    }\n\n    @Test\n    public void testNoIndentOnScriptAndStyle() {\n        // don't newline+indent closing </script> and </style> tags\n        Document doc = Jsoup.parse(\"<script>one\\ntwo</script>\\n<style>three\\nfour</style>\");\n        assertEquals(\"<script>one\\ntwo</script>\\n<style>three\\nfour</style>\", doc.head().html());\n    }\n\n    @Test\n    public void testContainerOutput() {\n        Document doc = Jsoup.parse(\"<title>Hello there</title> <div><p>Hello</p><p>there</p></div> <div>Another</div>\");\n        assertEquals(\"<title>Hello there</title>\", doc.select(\"title\").first().outerHtml());\n        assertEquals(\"<div>\\n <p>Hello</p>\\n <p>there</p>\\n</div>\", doc.select(\"div\").first().outerHtml());\n        assertEquals(\"<div>\\n <p>Hello</p>\\n <p>there</p>\\n</div>\\n<div>Another</div>\", doc.select(\"body\").first().html());\n    }\n\n    @Test\n    public void testSetText() {\n        String h = \"<div id=1>Hello <p>there <b>now</b></p></div>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"Hello there now\", doc.text()); // need to sort out node whitespace\n        assertEquals(\"there now\", doc.select(\"p\").get(0).text());\n\n        Element div = doc.getElementById(\"1\").text(\"Gone\");\n        assertEquals(\"Gone\", div.text());\n        assertEquals(0, doc.select(\"p\").size());\n    }\n\n    @Test\n    public void testAddNewElement() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.appendElement(\"p\").text(\"there\");\n        div.appendElement(\"P\").attr(\"CLASS\", \"second\").text(\"now\");\n        // manually specifying tag and attributes should maintain case based on parser settings\n        assertEquals(\"<html><head></head><body><div id=\\\"1\\\"><p>Hello</p><p>there</p><p class=\\\"second\\\">now</p></div></body></html>\",\n            TextUtil.stripNewlines(doc.html()));\n\n        // check sibling index (with short circuit on reindexChildren):\n        Elements ps = doc.select(\"p\");\n        for (int i = 0; i < ps.size(); i++) {\n            assertEquals(i, ps.get(i).siblingIndex);\n        }\n    }\n\n    @Test\n    public void testAddBooleanAttribute() {\n        Element div = new Element(Tag.valueOf(\"div\"), \"\");\n\n        div.attr(\"true\", true);\n\n        div.attr(\"false\", \"value\");\n        div.attr(\"false\", false);\n\n        assertTrue(div.hasAttr(\"true\"));\n        assertEquals(\"\", div.attr(\"true\"));\n\n        List<Attribute> attributes = div.attributes().asList();\n        assertEquals(1, attributes.size(), \"There should be one attribute\");\n        assertFalse(div.hasAttr(\"false\"));\n\n        assertEquals(\"<div true></div>\", div.outerHtml());\n    }\n\n    @Test\n    public void testAppendRowToTable() {\n        Document doc = Jsoup.parse(\"<table><tr><td>1</td></tr></table>\");\n        Element table = doc.select(\"tbody\").first();\n        table.append(\"<tr><td>2</td></tr>\");\n\n        assertEquals(\"<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testPrependRowToTable() {\n        Document doc = Jsoup.parse(\"<table><tr><td>1</td></tr></table>\");\n        Element table = doc.select(\"tbody\").first();\n        table.prepend(\"<tr><td>2</td></tr>\");\n\n        assertEquals(\"<table><tbody><tr><td>2</td></tr><tr><td>1</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // check sibling index (reindexChildren):\n        Elements ps = doc.select(\"tr\");\n        for (int i = 0; i < ps.size(); i++) {\n            assertEquals(i, ps.get(i).siblingIndex);\n        }\n    }\n\n    @Test\n    public void testPrependElement() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.prependElement(\"p\").text(\"Before\");\n        assertEquals(\"Before\", div.child(0).text());\n        assertEquals(\"Hello\", div.child(1).text());\n    }\n\n    @Test\n    public void testAddNewText() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.appendText(\" there & now >\");\n        assertEquals (\"Hello there & now >\", div.text());\n        assertEquals(\"<p>Hello</p>there &amp; now &gt;\", TextUtil.stripNewlines(div.html()));\n    }\n\n    @Test\n    public void testPrependText() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.prependText(\"there & now > \");\n        assertEquals(\"there & now > Hello\", div.text());\n        assertEquals(\"there &amp; now &gt;\\n<p>Hello</p>\", div.html());\n    }\n\n    @Test\n    public void testThrowsOnAddNullText() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n            Element div = doc.getElementById(\"1\");\n            div.appendText(null);\n        });\n    }\n\n    @Test\n    public void testThrowsOnPrependNullText() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n            Element div = doc.getElementById(\"1\");\n            div.prependText(null);\n        });\n    }\n\n    @Test\n    public void testAddNewHtml() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.append(\"<p>there</p><p>now</p>\");\n        assertEquals(\"<p>Hello</p><p>there</p><p>now</p>\", TextUtil.stripNewlines(div.html()));\n\n        // check sibling index (no reindexChildren):\n        Elements ps = doc.select(\"p\");\n        for (int i = 0; i < ps.size(); i++) {\n            assertEquals(i, ps.get(i).siblingIndex);\n        }\n    }\n\n    @Test\n    public void testPrependNewHtml() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.prepend(\"<p>there</p><p>now</p>\");\n        assertEquals(\"<p>there</p><p>now</p><p>Hello</p>\", TextUtil.stripNewlines(div.html()));\n\n        // check sibling index (reindexChildren):\n        Elements ps = doc.select(\"p\");\n        for (int i = 0; i < ps.size(); i++) {\n            assertEquals(i, ps.get(i).siblingIndex);\n        }\n    }\n\n    @Test void prependNodes() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element p = doc.expectFirst(\"p\");\n        p.prepend(\"Text <!-- comment --> \");\n        assertEquals(\"Text <!-- comment --> Hello\", TextUtil.stripNewlines(p.html()));\n    }\n\n    @Test void appendNodes() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element p = doc.expectFirst(\"p\");\n        p.append(\" Text <!-- comment -->\");\n        assertEquals(\"Hello Text <!-- comment -->\", TextUtil.stripNewlines(p.html()));\n    }\n\n    @Test\n    public void testSetHtml() {\n        Document doc = Jsoup.parse(\"<div id=1><p>Hello</p></div>\");\n        Element div = doc.getElementById(\"1\");\n        div.html(\"<p>there</p><p>now</p>\");\n        assertEquals(\"<p>there</p><p>now</p>\", TextUtil.stripNewlines(div.html()));\n    }\n\n    @Test\n    public void testSetHtmlTitle() {\n        Document doc = Jsoup.parse(\"<html><head id=2><title id=1></title></head></html>\");\n\n        Element title = doc.getElementById(\"1\");\n        title.html(\"good\");\n        assertEquals(\"good\", title.html());\n        title.html(\"<i>bad</i>\");\n        assertEquals(\"&lt;i&gt;bad&lt;/i&gt;\", title.html());\n\n        Element head = doc.getElementById(\"2\");\n        head.html(\"<title><i>bad</i></title>\");\n        assertEquals(\"<title>&lt;i&gt;bad&lt;/i&gt;</title>\", head.html());\n    }\n\n    @Test\n    public void testWrap() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p><p>There</p></div>\");\n        Element p = doc.select(\"p\").first();\n        p.wrap(\"<div class='head'></div>\");\n        assertEquals(\"<div><div class=\\\"head\\\"><p>Hello</p></div><p>There</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        Element ret = p.wrap(\"<div><div class=foo></div><p>What?</p></div>\");\n        assertEquals(\"<div><div class=\\\"head\\\"><div><div class=\\\"foo\\\"><p>Hello</p></div><p>What?</p></div></div><p>There</p></div>\",\n            TextUtil.stripNewlines(doc.body().html()));\n\n        assertEquals(ret, p);\n    }\n\n    @Test\n    public void testWrapNoop() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div>\");\n        Node p = doc.select(\"p\").first();\n        Node wrapped = p.wrap(\"Some junk\");\n        assertSame(p, wrapped);\n        assertEquals(\"<div><p>Hello</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n        // should be a NOOP\n    }\n\n    @Test\n    public void testWrapOnOrphan() {\n        Element orphan = new Element(\"span\").text(\"Hello!\");\n        assertFalse(orphan.hasParent());\n        Element wrapped = orphan.wrap(\"<div></div> There!\");\n        assertSame(orphan, wrapped);\n        assertTrue(orphan.hasParent()); // should now be in the DIV\n        assertNotNull(orphan.parent());\n        assertEquals(\"div\", orphan.parent().tagName());\n        assertEquals(\"<div>\\n <span>Hello!</span>\\n</div>\", orphan.parent().outerHtml());\n    }\n\n    @Test\n    public void testWrapArtificialStructure() {\n        // div normally couldn't get into a p, but explicitly want to wrap\n        Document doc = Jsoup.parse(\"<p>Hello <i>there</i> now.\");\n        Element i = doc.expectFirst(\"i\");\n        i.wrap(\"<div id=id1></div> quite\");\n        assertEquals(\"div\", i.parent().tagName());\n        assertEquals(\"<p>Hello\\n <div id=\\\"id1\\\">\\n  <i>there</i>\\n </div>\\n quite now.</p>\",(doc.body().html()));\n        // gives us a TextNode seq of \"quite\" and \" now\"; make sure not to collapse to \"quitenow\" when pretty print.\n    }\n\n    @Test\n    public void before() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p><p>There</p></div>\");\n        Element p1 = doc.select(\"p\").first();\n        p1.before(\"<div>one</div><div>two</div>\");\n        assertEquals(\"<div><div>one</div><div>two</div><p>Hello</p><p>There</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        doc.select(\"p\").last().before(\"<p>Three</p><!-- four -->\");\n        assertEquals(\"<div><div>one</div><div>two</div><p>Hello</p><p>Three</p><!-- four --><p>There</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void after() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p><p>There</p></div>\");\n        Element p1 = doc.select(\"p\").first();\n        p1.after(\"<div>one</div><div>two</div>\");\n        assertEquals(\"<div><p>Hello</p><div>one</div><div>two</div><p>There</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        doc.select(\"p\").last().after(\"<p>Three</p><!-- four -->\");\n        assertEquals(\"<div><p>Hello</p><div>one</div><div>two</div><p>There</p><p>Three</p><!-- four --></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testWrapWithRemainder() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div>\");\n        Element p = doc.select(\"p\").first();\n        p.wrap(\"<div class='head'></div><p>There!</p>\");\n        assertEquals(\"<div><div class=\\\"head\\\"><p>Hello</p></div><p>There!</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testWrapWithSimpleRemainder() {\n        Document doc = Jsoup.parse(\"<p>Hello\");\n        Element p = doc.selectFirst(\"p\");\n        Element body = p.parent();\n        assertNotNull(body);\n        assertEquals(\"body\", body.tagName());\n\n        p.wrap(\"<div></div> There\");\n        Element div = p.parent();\n        assertNotNull(div);\n        assertEquals(\"div\", div.tagName());\n        assertSame(div, p.parent());\n        assertSame(body, div.parent());\n\n        assertEquals(\"<div><p>Hello</p></div>There\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testHasText() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p><p></p></div>\");\n        Element div = doc.select(\"div\").first();\n        Elements ps = doc.select(\"p\");\n\n        assertTrue(div.hasText());\n        assertTrue(ps.first().hasText());\n        assertFalse(ps.last().hasText());\n    }\n\n    @Test\n    public void dataset() {\n        Document doc = Jsoup.parse(\"<div id=1 data-name=jsoup class=new data-package=jar>Hello</div><p id=2>Hello</p>\");\n        Element div = doc.select(\"div\").first();\n        Map<String, String> dataset = div.dataset();\n        Attributes attributes = div.attributes();\n\n        // size, get, set, add, remove\n        assertEquals(2, dataset.size());\n        assertEquals(\"jsoup\", dataset.get(\"name\"));\n        assertEquals(\"jar\", dataset.get(\"package\"));\n\n        dataset.put(\"name\", \"jsoup updated\");\n        dataset.put(\"language\", \"java\");\n        dataset.remove(\"package\");\n\n        assertEquals(2, dataset.size());\n        assertEquals(4, attributes.size());\n        assertEquals(\"jsoup updated\", attributes.get(\"data-name\"));\n        assertEquals(\"jsoup updated\", dataset.get(\"name\"));\n        assertEquals(\"java\", attributes.get(\"data-language\"));\n        assertEquals(\"java\", dataset.get(\"language\"));\n\n        attributes.put(\"data-food\", \"bacon\");\n        assertEquals(3, dataset.size());\n        assertEquals(\"bacon\", dataset.get(\"food\"));\n\n        attributes.put(\"data-\", \"empty\");\n        assertNull(dataset.get(\"\")); // data- is not a data attribute\n\n        Element p = doc.select(\"p\").first();\n        assertEquals(0, p.dataset().size());\n\n    }\n\n    @Test\n    public void parentlessToString() {\n        Document doc = Jsoup.parse(\"<img src='foo'>\");\n        Element img = doc.select(\"img\").first();\n        assertEquals(\"<img src=\\\"foo\\\">\", img.toString());\n\n        img.remove(); // lost its parent\n        assertEquals(\"<img src=\\\"foo\\\">\", img.toString());\n    }\n\n    @Test\n    public void orphanDivToString() {\n        Element orphan = new Element(\"div\").id(\"foo\").text(\"Hello\");\n        assertEquals(\"<div id=\\\"foo\\\">Hello</div>\", orphan.toString());\n    }\n\n    @Test\n    public void testClone() {\n        Document doc = Jsoup.parse(\"<div><p>One<p><span>Two</div>\");\n\n        Element p = doc.select(\"p\").get(1);\n        Element clone = p.clone();\n\n        assertNotNull(clone.parentNode); // should be a cloned document just containing this clone\n        assertEquals(1, clone.parentNode.childNodeSize());\n        assertSame(clone.ownerDocument(), clone.parentNode);\n\n        assertEquals(0, clone.siblingIndex);\n        assertEquals(1, p.siblingIndex);\n        assertNotNull(p.parent());\n\n        clone.append(\"<span>Three\");\n        assertEquals(\"<p><span>Two</span><span>Three</span></p>\", TextUtil.stripNewlines(clone.outerHtml()));\n        assertEquals(\"<div><p>One</p><p><span>Two</span></p></div>\", TextUtil.stripNewlines(doc.body().html())); // not modified\n\n        doc.body().appendChild(clone); // adopt\n        assertNotNull(clone.parent());\n        assertEquals(\"<div><p>One</p><p><span>Two</span></p></div><p><span>Two</span><span>Three</span></p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testClonesClassnames() {\n        Document doc = Jsoup.parse(\"<div class='one two'></div>\");\n        Element div = doc.select(\"div\").first();\n        Set<String> classes = div.classNames();\n        assertEquals(2, classes.size());\n        assertTrue(classes.contains(\"one\"));\n        assertTrue(classes.contains(\"two\"));\n\n        Element copy = div.clone();\n        Set<String> copyClasses = copy.classNames();\n        assertEquals(2, copyClasses.size());\n        assertTrue(copyClasses.contains(\"one\"));\n        assertTrue(copyClasses.contains(\"two\"));\n        copyClasses.add(\"three\");\n        copyClasses.remove(\"one\");\n\n        assertTrue(classes.contains(\"one\"));\n        assertFalse(classes.contains(\"three\"));\n        assertFalse(copyClasses.contains(\"one\"));\n        assertTrue(copyClasses.contains(\"three\"));\n\n        assertEquals(\"\", div.html());\n        assertEquals(\"\", copy.html());\n    }\n\n    @Test\n    public void testShallowClone() {\n        String base = \"http://example.com/\";\n        Document doc = Jsoup.parse(\"<div id=1 class=one><p id=2 class=two>One\", base);\n        Element d = doc.selectFirst(\"div\");\n        Element p = doc.selectFirst(\"p\");\n        TextNode t = p.textNodes().get(0);\n\n        Element d2 = d.shallowClone();\n        Element p2 = p.shallowClone();\n        TextNode t2 = (TextNode) t.shallowClone();\n\n        assertEquals(1, d.childNodeSize());\n        assertEquals(0, d2.childNodeSize());\n\n        assertEquals(1, p.childNodeSize());\n        assertEquals(0, p2.childNodeSize());\n\n        assertEquals(\"\", p2.text());\n        assertEquals(\"One\", t2.text());\n\n        assertEquals(\"two\", p2.className());\n        p2.removeClass(\"two\");\n        assertEquals(\"two\", p.className());\n\n        d2.append(\"<p id=3>Three\");\n        assertEquals(1, d2.childNodeSize());\n        assertEquals(\"Three\", d2.text());\n        assertEquals(\"One\", d.text());\n        assertEquals(base, d2.baseUri());\n    }\n\n    @Test void cloneRetainsParser() {\n        Document htmlDoc = Jsoup.parse(\"<div><script></script></div>\", Parser.htmlParser());\n        Document xmlDoc = Jsoup.parse(\"<div><script></script></div>\", Parser.xmlParser());\n\n        Element hEl = htmlDoc.expectFirst(\"script\");\n        Element hEl2 = hEl.clone();\n        assertNotSame(hEl, hEl2);\n        assertNotSame(hEl.ownerDocument(), hEl2.ownerDocument());\n        assertSame(hEl.ownerDocument().parser(), hEl2.ownerDocument().parser());\n\n        Document doc2 = htmlDoc.clone();\n        assertNotSame(htmlDoc, doc2);\n        assertSame(htmlDoc.parser(), doc2.parser());\n\n        hEl2.append(\"<foo></foo>\"); // we are inside a script, should be parsed as data\n        assertEquals(\"<foo></foo>\", hEl2.data());\n\n        Element xEl = xmlDoc.expectFirst(\"script\");\n        Element xEl2 = xEl.clone();\n        xEl2.append(\"<foo></foo>\"); // in XML, script doesn't mean anything, and so will be parsed as xml\n        assertEquals(\"<script><foo></foo></script>\", xEl2.outerHtml());\n    }\n\n    @Test\n    public void testTagNameSet() {\n        Document doc = Jsoup.parse(\"<div><i>Hello</i>\");\n        doc.select(\"i\").first().tagName(\"em\");\n        assertEquals(0, doc.select(\"i\").size());\n        assertEquals(1, doc.select(\"em\").size());\n        assertEquals(\"<em>Hello</em>\", doc.select(\"div\").first().html());\n    }\n\n    @Test void testSetTag() {\n        Document doc = Jsoup.parse(\"<div><em>Hello</em></div>\");\n        Element el = doc.expectFirst(\"em\");\n        el.tag(new Tag(\"I\", NamespaceHtml));\n        assertEquals(\"<I>Hello</I>\", el.outerHtml()); // case-sensitive path\n    }\n\n    @Test\n    public void testHtmlContainsOuter() {\n        Document doc = Jsoup.parse(\"<title>Check</title> <div>Hello there</div>\");\n        doc.outputSettings().indentAmount(0);\n        assertTrue(doc.html().contains(doc.select(\"title\").outerHtml()));\n        assertTrue(doc.html().contains(doc.select(\"div\").outerHtml()));\n    }\n\n    @Test\n    public void testGetTextNodes() {\n        Document doc = Jsoup.parse(\"<p>One <span>Two</span> Three <br> Four</p>\");\n        List<TextNode> textNodes = doc.select(\"p\").first().textNodes();\n\n        assertEquals(3, textNodes.size());\n        assertEquals(\"One \", textNodes.get(0).text());\n        assertEquals(\" Three \", textNodes.get(1).text());\n        assertEquals(\" Four\", textNodes.get(2).text());\n\n        assertEquals(0, doc.select(\"br\").first().textNodes().size());\n    }\n\n    @Test\n    public void testManipulateTextNodes() {\n        Document doc = Jsoup.parse(\"<p>One <span>Two</span> Three <br> Four</p>\");\n        Element p = doc.select(\"p\").first();\n        List<TextNode> textNodes = p.textNodes();\n\n        textNodes.get(1).text(\" three-more \");\n        textNodes.get(2).splitText(3).text(\"-ur\");\n\n        assertEquals(\"One Two three-more Fo-ur\", p.text());\n        assertEquals(\"One three-more Fo-ur\", p.ownText());\n        assertEquals(4, p.textNodes().size()); // grew because of split\n    }\n\n    @Test\n    public void testGetDataNodes() {\n        Document doc = Jsoup.parse(\"<script>One Two</script> <style>Three Four</style> <p>Fix Six</p>\");\n        Element script = doc.select(\"script\").first();\n        Element style = doc.select(\"style\").first();\n        Element p = doc.select(\"p\").first();\n\n        List<DataNode> scriptData = script.dataNodes();\n        assertEquals(1, scriptData.size());\n        assertEquals(\"One Two\", scriptData.get(0).getWholeData());\n\n        List<DataNode> styleData = style.dataNodes();\n        assertEquals(1, styleData.size());\n        assertEquals(\"Three Four\", styleData.get(0).getWholeData());\n\n        List<DataNode> pData = p.dataNodes();\n        assertEquals(0, pData.size());\n    }\n\n    @Test\n    public void elementIsNotASiblingOfItself() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div>\");\n        Element p2 = doc.select(\"p\").get(1);\n\n        assertEquals(\"Two\", p2.text());\n        Elements els = p2.siblingElements();\n        assertEquals(2, els.size());\n        assertEquals(\"<p>One</p>\", els.get(0).outerHtml());\n        assertEquals(\"<p>Three</p>\", els.get(1).outerHtml());\n    }\n\n    @Test\n    public void testChildThrowsIndexOutOfBoundsOnMissing() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p></div>\");\n        Element div = doc.select(\"div\").first();\n\n        assertEquals(2, div.children().size());\n        assertEquals(\"One\", div.child(0).text());\n\n        try {\n            div.child(3);\n            fail(\"Should throw index out of bounds\");\n        } catch (IndexOutOfBoundsException e) {\n        }\n    }\n\n    @Test\n    public void moveByAppend() {\n        // test for https://github.com/jhy/jsoup/issues/239\n        // can empty an element and append its children to another element\n        Document doc = Jsoup.parse(\"<div id=1>Text <p>One</p> Text <p>Two</p></div><div id=2></div>\");\n        Element div1 = doc.select(\"div\").get(0);\n        Element div2 = doc.select(\"div\").get(1);\n\n        assertEquals(4, div1.childNodeSize());\n        List<Node> children = div1.childNodes();\n        assertEquals(4, children.size());\n\n        div2.insertChildren(0, children);\n\n        assertEquals(4, children.size()); // children is NOT backed by div1.childNodes but a wrapper, so should still be 4 (but re-parented)\n        assertEquals(0, div1.childNodeSize());\n        assertEquals(4, div2.childNodeSize());\n        assertEquals(\"<div id=\\\"1\\\"></div>\\n<div id=\\\"2\\\">\\n Text\\n <p>One</p>\\n Text\\n <p>Two</p>\\n</div>\", doc.body().html());\n    }\n\n    @Test\n    public void insertChildrenArgumentValidation() {\n        Document doc = Jsoup.parse(\"<div id=1>Text <p>One</p> Text <p>Two</p></div><div id=2></div>\");\n        Element div1 = doc.select(\"div\").get(0);\n        Element div2 = doc.select(\"div\").get(1);\n        List<Node> children = div1.childNodes();\n\n        try {\n            div2.insertChildren(6, children);\n            fail();\n        } catch (IllegalArgumentException e) {\n        }\n\n        try {\n            div2.insertChildren(-5, children);\n            fail();\n        } catch (IllegalArgumentException e) {\n        }\n\n        try {\n            div2.insertChildren(0, (Collection<? extends Node>) null);\n            fail();\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void insertChildrenAtPosition() {\n        Document doc = Jsoup.parse(\"<div id=1>Text1 <p>One</p> Text2 <p>Two</p></div><div id=2>Text3 <p>Three</p></div>\");\n        Element div1 = doc.select(\"div\").get(0);\n        Elements p1s = div1.select(\"p\");\n        Element div2 = doc.select(\"div\").get(1);\n\n        assertEquals(2, div2.childNodeSize());\n        div2.insertChildren(-1, p1s);\n        assertEquals(2, div1.childNodeSize()); // moved two out\n        assertEquals(4, div2.childNodeSize());\n        assertEquals(3, p1s.get(1).siblingIndex()); // should be last\n\n        List<Node> els = new ArrayList<>();\n        Element el1 = new Element(Tag.valueOf(\"span\"), \"\").text(\"Span1\");\n        Element el2 = new Element(Tag.valueOf(\"span\"), \"\").text(\"Span2\");\n        TextNode tn1 = new TextNode(\"Text4\");\n        els.add(el1);\n        els.add(el2);\n        els.add(tn1);\n\n        assertNull(el1.parent());\n        div2.insertChildren(-2, els);\n        assertEquals(div2, el1.parent());\n        assertEquals(7, div2.childNodeSize());\n        assertEquals(3, el1.siblingIndex());\n        assertEquals(4, el2.siblingIndex());\n        assertEquals(5, tn1.siblingIndex());\n    }\n\n    @Test\n    public void insertChildrenAsCopy() {\n        Document doc = Jsoup.parse(\"<div id=1>Text <p>One</p> Text <p>Two</p></div><div id=2></div>\");\n        Element div1 = doc.select(\"div\").get(0);\n        Element div2 = doc.select(\"div\").get(1);\n        Elements ps = doc.select(\"p\").clone();\n        ps.first().text(\"One cloned\");\n        div2.insertChildren(-1, ps);\n\n        assertEquals(4, div1.childNodeSize()); // not moved -- cloned\n        assertEquals(2, div2.childNodeSize());\n        assertEquals(\n            \"<div id=\\\"1\\\">\\n Text\\n <p>One</p>\\n Text\\n <p>Two</p>\\n</div>\\n<div id=\\\"2\\\">\\n <p>One cloned</p>\\n <p>Two</p>\\n</div>\",\n            doc.body().html()\n        );\n    }\n\n    @Test\n    public void testCssPath() {\n        Document doc = Jsoup.parse(\"<div id=\\\"id1\\\">A</div><div>B</div><div class=\\\"c1 c2\\\">C</div>\");\n        Element divA = doc.select(\"div\").get(0);\n        Element divB = doc.select(\"div\").get(1);\n        Element divC = doc.select(\"div\").get(2);\n        assertEquals(divA.cssSelector(), \"#id1\");\n        assertEquals(divB.cssSelector(), \"html > body > div:nth-child(2)\");\n        assertEquals(divC.cssSelector(), \"html > body > div.c1.c2\");\n\n        assertSame(divA, doc.select(divA.cssSelector()).first());\n        assertSame(divB, doc.select(divB.cssSelector()).first());\n        assertSame(divC, doc.select(divC.cssSelector()).first());\n    }\n\n    @Test\n    public void testCssPathDuplicateIds() {\n        // https://github.com/jhy/jsoup/issues/1147 - multiple elements with same ID, use the non-ID form\n        Document doc = Jsoup.parse(\"<article><div id=dupe>A</div><div id=dupe>B</div><div id=dupe class=c1>\");\n        Element divA = doc.select(\"div\").get(0);\n        Element divB = doc.select(\"div\").get(1);\n        Element divC = doc.select(\"div\").get(2);\n\n        assertEquals(divA.cssSelector(), \"html > body > article > div:nth-child(1)\");\n        assertEquals(divB.cssSelector(), \"html > body > article > div:nth-child(2)\");\n        assertEquals(divC.cssSelector(), \"html > body > article > div.c1\");\n\n        assertSame(divA, doc.select(divA.cssSelector()).first());\n        assertSame(divB, doc.select(divB.cssSelector()).first());\n        assertSame(divC, doc.select(divC.cssSelector()).first());\n    }\n\n    @Test public void cssSelectorEscaped() {\n        // https://github.com/jhy/jsoup/issues/1742\n        Document doc = Jsoup.parse(\"<p\\\\p>One</p\\\\p> <p id='one.two'>Two</p> <p class='one.two:three/four'>Three</p>\");\n        Element one = doc.expectFirst(\"p\\\\\\\\p\");\n        Elements ps = doc.select(\"p\");\n        Element two = ps.get(0);\n        Element three = ps.get(1);\n\n        String oneSelect = one.cssSelector();\n        assertEquals(\"html > body > p\\\\\\\\p\", oneSelect);\n        assertEquals(one, doc.expectFirst(oneSelect));\n\n        String twoSelect = two.cssSelector();\n        assertEquals(\"#one\\\\.two\", twoSelect);\n        assertEquals(two, doc.expectFirst(twoSelect));\n\n        String threeSelect = three.cssSelector();\n        assertEquals(\"html > body > p.one\\\\.two\\\\:three\\\\/four\", threeSelect);\n        assertEquals(three, doc.expectFirst(threeSelect));\n    }\n\n    @Test public void cssEscapedAmp() {\n        Document doc = Jsoup.parse(\"<p class='\\\\&'>One</p>\");\n        Element one = doc.expectFirst(\".\\\\\\\\\\\\&\"); // tested matches js querySelector\n        assertEquals(\"One\", one.text());\n\n        String q = one.cssSelector();\n        assertEquals(\"html > body > p.\\\\\\\\\\\\&\", q);\n        assertEquals(one, doc.expectFirst(q));\n    }\n\n    @Test public void cssSelectorEscapedClass() {\n        // example in https://github.com/jhy/jsoup/issues/838\n        String html = \"<div class='B\\\\&W\\\\?'><div class=test>Text</div></div>\";\n        Document parse = Jsoup.parse(html);\n        Element el = parse.expectFirst(\".test\");\n        assertEquals(\"Text\", el.text());\n\n        String q = el.cssSelector();\n        assertEquals(\"html > body > div.B\\\\\\\\\\\\&W\\\\\\\\\\\\? > div.test\", q);\n        Element found = parse.expectFirst(q);\n        assertEquals(found, el);\n    }\n\n    @Test\n    public void testClassNames() {\n        Document doc = Jsoup.parse(\"<div class=\\\"c1 c2\\\">C</div>\");\n        Element div = doc.select(\"div\").get(0);\n\n        assertEquals(\"c1 c2\", div.className());\n\n        final Set<String> set1 = div.classNames();\n        final Object[] arr1 = set1.toArray();\n        assertEquals(2, arr1.length);\n        assertEquals(\"c1\", arr1[0]);\n        assertEquals(\"c2\", arr1[1]);\n\n        // Changes to the set should not be reflected in the Elements getters\n        set1.add(\"c3\");\n        assertEquals(2, div.classNames().size());\n        assertEquals(\"c1 c2\", div.className());\n\n        // Update the class names to a fresh set\n        final Set<String> newSet = new LinkedHashSet<>(3);\n        newSet.addAll(set1);\n        newSet.add(\"c3\");\n\n        div.classNames(newSet);\n\n        assertEquals(\"c1 c2 c3\", div.className());\n\n        final Set<String> set2 = div.classNames();\n        final Object[] arr2 = set2.toArray();\n        assertEquals(3, arr2.length);\n        assertEquals(\"c1\", arr2[0]);\n        assertEquals(\"c2\", arr2[1]);\n        assertEquals(\"c3\", arr2[2]);\n    }\n\n    @Test\n    public void testHashAndEqualsAndValue() {\n        // .equals and hashcode are identity. value is content.\n\n        String doc1 = \"<div id=1><p class=one>One</p><p class=one>One</p><p class=one>Two</p><p class=two>One</p></div>\" +\n            \"<div id=2><p class=one>One</p><p class=one>One</p><p class=one>Two</p><p class=two>One</p></div>\";\n\n        Document doc = Jsoup.parse(doc1);\n        Elements els = doc.select(\"p\");\n\n        /*\n        for (Element el : els) {\n            System.out.println(el.hashCode() + \" - \" + el.outerHtml());\n        }\n\n        0 1534787905 - <p class=\"one\">One</p>\n        1 1534787905 - <p class=\"one\">One</p>\n        2 1539683239 - <p class=\"one\">Two</p>\n        3 1535455211 - <p class=\"two\">One</p>\n        4 1534787905 - <p class=\"one\">One</p>\n        5 1534787905 - <p class=\"one\">One</p>\n        6 1539683239 - <p class=\"one\">Two</p>\n        7 1535455211 - <p class=\"two\">One</p>\n        */\n        assertEquals(8, els.size());\n        Element e0 = els.get(0);\n        Element e1 = els.get(1);\n        Element e2 = els.get(2);\n        Element e3 = els.get(3);\n        Element e4 = els.get(4);\n        Element e5 = els.get(5);\n        Element e6 = els.get(6);\n        Element e7 = els.get(7);\n\n        assertEquals(e0, e0);\n        assertTrue(e0.hasSameValue(e1));\n        assertTrue(e0.hasSameValue(e4));\n        assertTrue(e0.hasSameValue(e5));\n        assertNotEquals(e0, e2);\n        assertFalse(e0.hasSameValue(e2));\n        assertFalse(e0.hasSameValue(e3));\n        assertFalse(e0.hasSameValue(e6));\n        assertFalse(e0.hasSameValue(e7));\n\n        assertEquals(e0.hashCode(), e0.hashCode());\n        assertNotEquals(e0.hashCode(), (e2.hashCode()));\n        assertNotEquals(e0.hashCode(), (e3).hashCode());\n        assertNotEquals(e0.hashCode(), (e6).hashCode());\n        assertNotEquals(e0.hashCode(), (e7).hashCode());\n    }\n\n    @Test\n    public void testRelativeUrls() {\n        String html = \"<body><a href='./one.html'>One</a> <a href='two.html'>two</a> <a href='../three.html'>Three</a> <a href='//example2.com/four/'>Four</a> <a href='https://example2.com/five/'>Five</a> <a>Six</a> <a href=''>Seven</a>\";\n        Document doc = Jsoup.parse(html, \"http://example.com/bar/\");\n        Elements els = doc.select(\"a\");\n\n        assertEquals(\"http://example.com/bar/one.html\", els.get(0).absUrl(\"href\"));\n        assertEquals(\"http://example.com/bar/two.html\", els.get(1).absUrl(\"href\"));\n        assertEquals(\"http://example.com/three.html\", els.get(2).absUrl(\"href\"));\n        assertEquals(\"http://example2.com/four/\", els.get(3).absUrl(\"href\"));\n        assertEquals(\"https://example2.com/five/\", els.get(4).absUrl(\"href\"));\n        assertEquals(\"\", els.get(5).absUrl(\"href\"));\n        assertEquals(\"http://example.com/bar/\", els.get(6).absUrl(\"href\"));\n    }\n\n    @Test\n    public void testRelativeIdnUrls() {\n        String idn = \"https://www.测试.测试/\";\n        String idnFoo = idn + \"foo.html?bar\";\n\n        Document doc = Jsoup.parse(\"<a href=''>One</a><a href='/bar.html?qux'>Two</a>\", idnFoo);\n        Elements els = doc.select(\"a\");\n        Element one = els.get(0);\n        Element two = els.get(1);\n        String hrefOne = one.absUrl(\"href\");\n        String hrefTwo = two.absUrl(\"href\");\n        assertEquals(idnFoo, hrefOne);\n        assertEquals(\"https://www.测试.测试/bar.html?qux\", hrefTwo);\n    }\n\n    @Test\n    public void appendMustCorrectlyMoveChildrenInsideOneParentElement() {\n        Document doc = new Document(\"\");\n        Element body = doc.appendElement(\"body\");\n        body.appendElement(\"div1\");\n        body.appendElement(\"div2\");\n        final Element div3 = body.appendElement(\"div3\");\n        div3.text(\"Check\");\n        final Element div4 = body.appendElement(\"div4\");\n\n        ArrayList<Element> toMove = new ArrayList<>();\n        toMove.add(div3);\n        toMove.add(div4);\n\n        body.insertChildren(0, toMove);\n\n        String result = doc.toString().replaceAll(\"\\\\s+\", \"\");\n        assertEquals(\"<body><div3>Check</div3><div4></div4><div1></div1><div2></div2></body>\", result);\n    }\n\n    @Test\n    public void testHashcodeIsStableWithContentChanges() {\n        Element root = new Element(Tag.valueOf(\"root\"), \"\");\n\n        HashSet<Element> set = new HashSet<>();\n        // Add root node:\n        set.add(root);\n\n        root.appendChild(new Element(Tag.valueOf(\"a\"), \"\"));\n        assertTrue(set.contains(root));\n    }\n\n    @Test\n    public void testNamespacedElements() {\n        // Namespaces with ns:tag in HTML must be translated to ns|tag in CSS.\n        String html = \"<html><body><fb:comments /></body></html>\";\n        Document doc = Jsoup.parse(html, \"http://example.com/bar/\");\n        Elements els = doc.select(\"fb|comments\");\n        assertEquals(1, els.size());\n        assertEquals(\"html > body > fb|comments\", els.get(0).cssSelector());\n    }\n\n    @Test\n    public void testChainedRemoveAttributes() {\n        String html = \"<a one two three four>Text</a>\";\n        Document doc = Jsoup.parse(html);\n        Element a = doc.select(\"a\").first();\n        a\n            .removeAttr(\"zero\")\n            .removeAttr(\"one\")\n            .removeAttr(\"two\")\n            .removeAttr(\"three\")\n            .removeAttr(\"four\")\n            .removeAttr(\"five\");\n        assertEquals(\"<a>Text</a>\", a.outerHtml());\n    }\n\n    @Test\n    public void testLoopedRemoveAttributes() {\n        String html = \"<a one two three four>Text</a><p foo>Two</p>\";\n        Document doc = Jsoup.parse(html);\n        for (Element el : doc.getAllElements()) {\n            el.clearAttributes();\n        }\n\n        assertEquals(\"<a>Text</a>\\n<p>Two</p>\", doc.body().html());\n    }\n\n    @Test\n    public void testIs() {\n        String html = \"<div><p>One <a class=big>Two</a> Three</p><p>Another</p>\";\n        Document doc = Jsoup.parse(html);\n        Element p = doc.select(\"p\").first();\n\n        assertTrue(p.is(\"p\"));\n        assertFalse(p.is(\"div\"));\n        assertTrue(p.is(\"p:has(a)\"));\n        assertFalse(p.is(\"a\")); // does not descend\n        assertTrue(p.is(\"p:first-child\"));\n        assertFalse(p.is(\"p:last-child\"));\n        assertTrue(p.is(\"*\"));\n        assertTrue(p.is(\"div p\"));\n\n        Element q = doc.select(\"p\").last();\n        assertTrue(q.is(\"p\"));\n        assertTrue(q.is(\"p ~ p\"));\n        assertTrue(q.is(\"p + p\"));\n        assertTrue(q.is(\"p:last-child\"));\n        assertFalse(q.is(\"p a\"));\n        assertFalse(q.is(\"a\"));\n    }\n\n    @Test\n    public void testEvalMethods() {\n        Document doc = Jsoup.parse(\"<div><p>One <a class=big>Two</a> Three</p><p>Another</p>\");\n        Element p = doc.selectFirst(QueryParser.parse((\"p\")));\n        assertEquals(\"One Three\", p.ownText());\n\n        assertTrue(p.is(QueryParser.parse(\"p\")));\n        Evaluator aEval = QueryParser.parse(\"a\");\n        assertFalse(p.is(aEval));\n\n        Element a = p.selectFirst(aEval);\n        assertEquals(\"div\", a.closest(QueryParser.parse(\"div:has( > p)\")).tagName());\n        Element body = p.closest(QueryParser.parse(\"body\"));\n        assertEquals(\"body\", body.nodeName());\n    }\n\n    @Test\n    public void testClosest() {\n        String html = \"<article>\\n\" +\n            \"  <div id=div-01>Here is div-01\\n\" +\n            \"    <div id=div-02>Here is div-02\\n\" +\n            \"      <div id=div-03>Here is div-03</div>\\n\" +\n            \"    </div>\\n\" +\n            \"  </div>\\n\" +\n            \"</article>\";\n\n        Document doc = Jsoup.parse(html);\n        Element el = doc.selectFirst(\"#div-03\");\n        assertEquals(\"Here is div-03\", el.text());\n        assertEquals(\"div-03\", el.id());\n\n        assertEquals(\"div-02\", el.closest(\"#div-02\").id());\n        assertEquals(el, el.closest(\"div div\")); // closest div in a div is itself\n        assertEquals(\"div-01\", el.closest(\"article > div\").id());\n        assertEquals(\"article\", el.closest(\":not(div)\").tagName());\n        assertNull(el.closest(\"p\"));\n    }\n\n    @Test\n    public void elementByTagName() {\n        Element a = new Element(\"P\");\n        assertEquals(\"P\", a.tagName());\n    }\n\n    @Test\n    public void testChildrenElements() {\n        String html = \"<div><p><a>One</a></p><p><a>Two</a></p>Three</div><span>Four</span><foo></foo><img>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.select(\"div\").first();\n        Element p = doc.select(\"p\").first();\n        Element span = doc.select(\"span\").first();\n        Element foo = doc.select(\"foo\").first();\n        Element img = doc.select(\"img\").first();\n\n        Elements docChildren = div.children();\n        assertEquals(2, docChildren.size());\n        assertEquals(\"<p><a>One</a></p>\", docChildren.get(0).outerHtml());\n        assertEquals(\"<p><a>Two</a></p>\", docChildren.get(1).outerHtml());\n        assertEquals(3, div.childNodes().size());\n        assertEquals(\"Three\", div.childNodes().get(2).outerHtml());\n\n        assertEquals(1, p.children().size());\n        assertEquals(\"One\", p.children().text());\n\n        assertEquals(0, span.children().size());\n        assertEquals(1, span.childNodes().size());\n        assertEquals(\"Four\", span.childNodes().get(0).outerHtml());\n\n        assertEquals(0, foo.children().size());\n        assertEquals(0, foo.childNodes().size());\n        assertEquals(0, img.children().size());\n        assertEquals(0, img.childNodes().size());\n    }\n\n    @Test\n    public void testShadowElementsAreUpdated() {\n        String html = \"<div><p><a>One</a></p><p><a>Two</a></p>Three</div><span>Four</span><foo></foo><img>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.select(\"div\").first();\n        Elements els = div.children();\n        List<Node> nodes = div.childNodes();\n\n        assertEquals(2, els.size()); // the two Ps\n        assertEquals(3, nodes.size()); // the \"Three\" textnode\n\n        Element p3 = new Element(\"p\").text(\"P3\");\n        Element p4 = new Element(\"p\").text(\"P4\");\n        div.insertChildren(1, p3);\n        div.insertChildren(3, p4);\n        Elements els2 = div.children();\n\n        // first els should not have changed\n        assertEquals(2, els.size());\n        assertEquals(4, els2.size());\n\n        assertEquals(\"<p><a>One</a></p>\\n\" +\n            \"<p>P3</p>\\n\" +\n            \"<p><a>Two</a></p>\\n\" +\n            \"<p>P4</p>\\nThree\", div.html());\n        assertEquals(\"P3\", els2.get(1).text());\n        assertEquals(\"P4\", els2.get(3).text());\n\n        p3.after(\"<span>Another</span\");\n\n        Elements els3 = div.children();\n        assertEquals(5, els3.size());\n        assertEquals(\"span\", els3.get(2).tagName());\n        assertEquals(\"Another\", els3.get(2).text());\n\n        assertEquals(\"<p><a>One</a></p>\\n\" +\n            \"<p>P3</p>\\n\" +\n            \"<span>Another</span>\\n\" +\n            \"<p><a>Two</a></p>\\n\" +\n            \"<p>P4</p>\\n\" +\n            \"Three\", div.html());\n    }\n\n    @Test\n    void shadowChildrenOnClone() {\n        // https://github.com/jhy/jsoup/issues/2334\n        String listHtml = \"<ul>\" +\n            \"<li><h2>initial1</h2></li>\" +\n            \"</ul>\";\n        Document origDoc = Jsoup.parseBodyFragment(listHtml);\n        origDoc.body().children().first();\n        Document cloneDoc = origDoc.clone();\n\n        Element ulEl = cloneDoc.body().expectFirst(\"ul\");\n\n        for (int i = 0; i < ulEl.children().size(); i++)\n            ulEl.child(i).children();\n\n        int growthSize = 3;\n        Element liEl = ulEl.firstElementChild();\n        assertNotNull(liEl);\n        while (ulEl.children().size() < growthSize)\n            ulEl.appendChild(liEl.clone());\n\n        Elements listItems = ulEl.children();\n        for (int i = 0; i < listItems.size(); i++) {\n            Element item = listItems.get(i);\n            Element h2 = item.child(0);\n            h2.text(\"other text \" + i);\n        }\n\n        assertFalse(ulEl.text().contains(\"initial\"));\n        assertEquals(\"other text 0 other text 1 other text 2\", ulEl.text());\n    }\n\n    @Test\n    public void classNamesAndAttributeNameIsCaseInsensitive() {\n        String html = \"<p Class='SomeText AnotherText'>One</p>\";\n        Document doc = Jsoup.parse(html);\n        Element p = doc.select(\"p\").first();\n        assertEquals(\"SomeText AnotherText\", p.className());\n        assertTrue(p.classNames().contains(\"SomeText\"));\n        assertTrue(p.classNames().contains(\"AnotherText\"));\n        assertTrue(p.hasClass(\"SomeText\"));\n        assertTrue(p.hasClass(\"sometext\"));\n        assertTrue(p.hasClass(\"AnotherText\"));\n        assertTrue(p.hasClass(\"anothertext\"));\n\n        Element p1 = doc.select(\".SomeText\").first();\n        Element p2 = doc.select(\".sometext\").first();\n        Element p3 = doc.select(\"[class=SomeText AnotherText]\").first();\n        Element p4 = doc.select(\"[Class=SomeText AnotherText]\").first();\n        Element p5 = doc.select(\"[class=sometext anothertext]\").first();\n        Element p6 = doc.select(\"[class=SomeText AnotherText]\").first();\n        Element p7 = doc.select(\"[class^=sometext]\").first();\n        Element p8 = doc.select(\"[class$=nothertext]\").first();\n        Element p9 = doc.select(\"[class^=sometext]\").first();\n        Element p10 = doc.select(\"[class$=AnotherText]\").first();\n\n        assertEquals(\"One\", p1.text());\n        assertEquals(p1, p2);\n        assertEquals(p1, p3);\n        assertEquals(p1, p4);\n        assertEquals(p1, p5);\n        assertEquals(p1, p6);\n        assertEquals(p1, p7);\n        assertEquals(p1, p8);\n        assertEquals(p1, p9);\n        assertEquals(p1, p10);\n    }\n\n    @Test\n    public void testAppendTo() {\n        String parentHtml = \"<div class='a'></div>\";\n        String childHtml = \"<div class='b'></div><p>Two</p>\";\n\n        Document parentDoc = Jsoup.parse(parentHtml);\n        Element parent = parentDoc.body();\n        Document childDoc = Jsoup.parse(childHtml);\n\n        Element div = childDoc.select(\"div\").first();\n        Element p = childDoc.select(\"p\").first();\n        Element appendTo1 = div.appendTo(parent);\n        assertEquals(div, appendTo1);\n\n        Element appendTo2 = p.appendTo(div);\n        assertEquals(p, appendTo2);\n\n        assertEquals(\"<div class=\\\"a\\\"></div>\\n<div class=\\\"b\\\">\\n <p>Two</p>\\n</div>\", parentDoc.body().html());\n        assertEquals(\"\", childDoc.body().html()); // got moved out\n    }\n\n    @Test\n    public void testNormalizesNbspInText() {\n        String escaped = \"You can't always get what you&nbsp;want.\";\n        String withNbsp = \"You can't always get what you want.\"; // there is an nbsp char in there\n        Document doc = Jsoup.parse(\"<p>\" + escaped);\n        Element p = doc.select(\"p\").first();\n        assertEquals(\"You can't always get what you want.\", p.text()); // text is normalized\n\n        assertEquals(\"<p>\" + escaped + \"</p>\", p.outerHtml()); // html / whole text keeps &nbsp;\n        assertEquals(withNbsp, p.textNodes().get(0).getWholeText());\n        assertEquals(160, withNbsp.charAt(29));\n\n        Element matched = doc.select(\"p:contains(get what you want)\").first();\n        assertEquals(\"p\", matched.nodeName());\n        assertTrue(matched.is(\":containsOwn(get what you want)\"));\n    }\n\n    @Test\n    public void testNormalizesInvisiblesInText() {\n        String escaped = \"This&shy;is&#x200b;one&shy;long&shy;word\";\n        String decoded = \"This\\u00ADis\\u200Bone\\u00ADlong\\u00ADword\"; // browser would not display those soft hyphens / other chars, so we don't want them in the text\n\n        Document doc = Jsoup.parse(\"<p>\" + escaped);\n        Element p = doc.select(\"p\").first();\n        doc.outputSettings().charset(\"ascii\"); // so that the outer html is easier to see with escaped invisibles\n        assertEquals(\"Thisisonelongword\", p.text()); // text is normalized\n        assertEquals(\"<p>\" + escaped + \"</p>\", p.outerHtml()); // html / whole text keeps &shy etc;\n        assertEquals(decoded, p.textNodes().get(0).getWholeText());\n\n        Element matched = doc.select(\"p:contains(Thisisonelongword)\").first(); // really just oneloneword, no invisibles\n        assertEquals(\"p\", matched.nodeName());\n        assertTrue(matched.is(\":containsOwn(Thisisonelongword)\"));\n\n    }\n\n    @Test\n    public void testRemoveBeforeIndex() {\n        Document doc = Jsoup.parse(\n            \"<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>\",\n            \"\");\n        Element body = doc.select(\"body\").first();\n        Elements elems = body.select(\"p:matchesOwn(XXX)\");\n        Element xElem = elems.first();\n        Elements beforeX = xElem.parent().getElementsByIndexLessThan(xElem.elementSiblingIndex());\n\n        for (Element p : beforeX) {\n            p.remove();\n        }\n\n        assertEquals(\"<body><div><p>XXX</p><p>after1</p><p>after2</p></div></body>\", TextUtil.stripNewlines(body.outerHtml()));\n    }\n\n    @Test\n    public void testRemoveAfterIndex() {\n        Document doc2 = Jsoup.parse(\n            \"<html><body><div><p>before1</p><p>before2</p><p>XXX</p><p>after1</p><p>after2</p></div></body></html>\",\n            \"\");\n        Element body = doc2.select(\"body\").first();\n        Elements elems = body.select(\"p:matchesOwn(XXX)\");\n        Element xElem = elems.first();\n        Elements afterX = xElem.parent().getElementsByIndexGreaterThan(xElem.elementSiblingIndex());\n\n        for (Element p : afterX) {\n            p.remove();\n        }\n\n        assertEquals(\"<body><div><p>before1</p><p>before2</p><p>XXX</p></div></body>\", TextUtil.stripNewlines(body.outerHtml()));\n    }\n\n    @Test\n    public void whiteSpaceClassElement() {\n        Tag tag = Tag.valueOf(\"a\");\n        Attributes attribs = new Attributes();\n        Element el = new Element(tag, \"\", attribs);\n\n        attribs.put(\"class\", \"abc \");\n        boolean hasClass = el.hasClass(\"ab\");\n        assertFalse(hasClass);\n    }\n\n    @Test\n    public void testNextElementSiblingAfterClone() {\n        // via https://github.com/jhy/jsoup/issues/951\n        String html = \"<!DOCTYPE html><html lang=\\\"en\\\"><head></head><body><div>Initial element</div></body></html>\";\n        String expectedText = \"New element\";\n        String cloneExpect = \"New element in clone\";\n\n        Document original = Jsoup.parse(html);\n        Document clone = original.clone();\n\n        Element originalElement = original.body().child(0);\n        originalElement.after(\"<div>\" + expectedText + \"</div>\");\n        Element originalNextElementSibling = originalElement.nextElementSibling();\n        Element originalNextSibling = (Element) originalElement.nextSibling();\n        assertEquals(expectedText, originalNextElementSibling.text());\n        assertEquals(expectedText, originalNextSibling.text());\n\n        Element cloneElement = clone.body().child(0);\n        cloneElement.after(\"<div>\" + cloneExpect + \"</div>\");\n        Element cloneNextElementSibling = cloneElement.nextElementSibling();\n        Element cloneNextSibling = (Element) cloneElement.nextSibling();\n        assertEquals(cloneExpect, cloneNextElementSibling.text());\n        assertEquals(cloneExpect, cloneNextSibling.text());\n    }\n\n    @Test\n    public void testRemovingEmptyClassAttributeWhenLastClassRemoved() {\n        // https://github.com/jhy/jsoup/issues/947\n        Document doc = Jsoup.parse(\"<img class=\\\"one two\\\" />\");\n        Element img = doc.select(\"img\").first();\n        img.removeClass(\"one\");\n        img.removeClass(\"two\");\n        assertFalse(doc.body().html().contains(\"class=\\\"\\\"\"));\n    }\n\n    @Test\n    public void booleanAttributeOutput() {\n        Document doc = Jsoup.parse(\"<img src=foo noshade='' nohref async=async autofocus=false>\");\n        Element img = doc.selectFirst(\"img\");\n\n        assertEquals(\"<img src=\\\"foo\\\" noshade nohref async autofocus=\\\"false\\\">\", img.outerHtml());\n    }\n\n    @Test\n    public void textHasSpaceAfterBlockTags() {\n        Document doc = Jsoup.parse(\"<div>One</div>Two\");\n        assertEquals(\"One Two\", doc.text());\n    }\n\n    @Test\n    public void textHasSpaceBetweenDivAndCenterTags() {\n        Document doc = Jsoup.parse(\"<div>One</div><div>Two</div><center>Three</center><center>Four</center>\");\n        assertEquals(\"One Two Three Four\", doc.text());\n    }\n\n    @Test\n    public void testNextElementSiblings() {\n        Document doc = Jsoup.parse(\"<ul id='ul'>\" +\n            \"<li id='a'>a</li>\" +\n            \"<li id='b'>b</li>\" +\n            \"<li id='c'>c</li>\" +\n            \"</ul> Not An Element but a node\" +\n            \"<div id='div'>\" +\n            \"<li id='d'>d</li>\" +\n            \"</div>\");\n\n        Element element = doc.getElementById(\"a\");\n        Elements elementSiblings = element.nextElementSiblings();\n        assertNotNull(elementSiblings);\n        assertEquals(2, elementSiblings.size());\n        assertEquals(\"b\", elementSiblings.get(0).id());\n        assertEquals(\"c\", elementSiblings.get(1).id());\n\n        Element element1 = doc.getElementById(\"b\");\n        List<Element> elementSiblings1 = element1.nextElementSiblings();\n        assertNotNull(elementSiblings1);\n        assertEquals(1, elementSiblings1.size());\n        assertEquals(\"c\", elementSiblings1.get(0).id());\n\n        Element element2 = doc.getElementById(\"c\");\n        List<Element> elementSiblings2 = element2.nextElementSiblings();\n        assertEquals(0, elementSiblings2.size());\n\n        Element ul = doc.getElementById(\"ul\");\n        List<Element> elementSiblings3 = ul.nextElementSiblings();\n        assertNotNull(elementSiblings3);\n        assertEquals(1, elementSiblings3.size());\n        assertEquals(\"div\", elementSiblings3.get(0).id());\n\n        Element div = doc.getElementById(\"div\");\n        List<Element> elementSiblings4 = div.nextElementSiblings();\n        assertEquals(0, elementSiblings4.size());\n    }\n\n    @Test\n    public void testPreviousElementSiblings() {\n        Document doc = Jsoup.parse(\"<ul id='ul'>\" +\n            \"<li id='a'>a</li>\" +\n            \"<li id='b'>b</li>\" +\n            \"<li id='c'>c</li>\" +\n            \"</ul>\" +\n            \"<div id='div'>\" +\n            \"<li id='d'>d</li>\" +\n            \"</div>\");\n\n        Element element = doc.getElementById(\"b\");\n        Elements elementSiblings = element.previousElementSiblings();\n        assertNotNull(elementSiblings);\n        assertEquals(1, elementSiblings.size());\n        assertEquals(\"a\", elementSiblings.get(0).id());\n\n        Element element1 = doc.getElementById(\"a\");\n        List<Element> elementSiblings1 = element1.previousElementSiblings();\n        assertEquals(0, elementSiblings1.size());\n\n        Element element2 = doc.getElementById(\"c\");\n        List<Element> elementSiblings2 = element2.previousElementSiblings();\n        assertNotNull(elementSiblings2);\n        assertEquals(2, elementSiblings2.size());\n        assertEquals(\"b\", elementSiblings2.get(0).id());\n        assertEquals(\"a\", elementSiblings2.get(1).id());\n\n        Element ul = doc.getElementById(\"ul\");\n        List<Element> elementSiblings3 = ul.previousElementSiblings();\n        assertEquals(0, elementSiblings3.size());\n    }\n\n    @Test\n    public void testClearAttributes() {\n        Element el = new Element(\"a\").attr(\"href\", \"http://example.com\").text(\"Hello\");\n        assertEquals(\"<a href=\\\"http://example.com\\\">Hello</a>\", el.outerHtml());\n        Element el2 = el.clearAttributes(); // really just force testing the return type is Element\n        assertSame(el, el2);\n        assertEquals(\"<a>Hello</a>\", el2.outerHtml());\n    }\n\n    @Test\n    public void testRemoveAttr() {\n        Element el = new Element(\"a\")\n            .attr(\"href\", \"http://example.com\")\n            .attr(\"id\", \"1\")\n            .text(\"Hello\");\n        assertEquals(\"<a href=\\\"http://example.com\\\" id=\\\"1\\\">Hello</a>\", el.outerHtml());\n        Element el2 = el.removeAttr(\"href\"); // really just force testing the return type is Element\n        assertSame(el, el2);\n        assertEquals(\"<a id=\\\"1\\\">Hello</a>\", el2.outerHtml());\n    }\n\n    @Test\n    public void testRoot() {\n        Element el = new Element(\"a\");\n        el.append(\"<span>Hello</span>\");\n        assertEquals(\"<a><span>Hello</span></a>\", el.outerHtml());\n        Element span = el.selectFirst(\"span\");\n        assertNotNull(span);\n        Element el2 = span.root();\n        assertSame(el, el2);\n\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three\");\n        Element div = doc.selectFirst(\"div\");\n        assertSame(doc, div.root());\n        assertSame(doc, div.ownerDocument());\n    }\n\n    @Test\n    public void testTraverse() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three\");\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        final AtomicInteger counter = new AtomicInteger(0);\n\n        Element div2 = div.traverse(new NodeVisitor() {\n\n            @Override\n            public void head(Node node, int depth) {\n                counter.incrementAndGet();\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n\n            }\n        });\n\n        assertEquals(7, counter.get());\n        assertEquals(div2, div);\n    }\n\n    @Test void testTraverseLambda() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three\");\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        final AtomicInteger counter = new AtomicInteger(0);\n\n        Element div2 = div.traverse((node, depth) -> counter.incrementAndGet());\n\n        assertEquals(7, counter.get());\n        assertEquals(div2, div);\n    }\n\n    @Test\n    public void testFilterCallReturnsElement() {\n        // doesn't actually test the filter so much as the return type for Element. See node.nodeFilter for an actual test\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three\");\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        Element div2 = div.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                return FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                return FilterResult.CONTINUE;\n            }\n        });\n\n        assertSame(div, div2);\n    }\n\n    @Test void testFilterAsLambda() {\n        Document doc = Jsoup.parse(\"<div><p>One<p id=2>Two<p>Three\");\n        doc.filter((node, depth) -> node.attr(\"id\").equals(\"2\")\n            ? NodeFilter.FilterResult.REMOVE\n            : NodeFilter.FilterResult.CONTINUE);\n\n        assertEquals(\"<div><p>One</p><p>Three</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void testForEach() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div><div id=1>Gone<p></div>\");\n        doc.forEach(el -> {\n            if (el.id().equals(\"1\"))\n                el.remove();\n            else if (el.text().equals(\"There\")) {\n                el.text(\"There Now\");\n                el.append(\"<p>Another</p>\");\n            }\n        });\n        assertEquals(\"<div><p>Hello</p></div><div>There Now<p>Another</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void doesntDeleteZWJWhenNormalizingText() {\n        String text = \"\\uD83D\\uDC69\\u200D\\uD83D\\uDCBB\\uD83E\\uDD26\\uD83C\\uDFFB\\u200D\\u2642\\uFE0F\";\n\n        Document doc = Jsoup.parse(\"<p>\" + text + \"</p><div>One&zwj;Two</div>\");\n        Element p = doc.selectFirst(\"p\");\n        Element d = doc.selectFirst(\"div\");\n\n        assertEquals(12, p.text().length());\n        assertEquals(text, p.text());\n        assertEquals(7, d.text().length());\n        assertEquals(\"One\\u200DTwo\", d.text());\n        Element found = doc.selectFirst(\"div:contains(One\\u200DTwo)\");\n        assertTrue(found.hasSameValue(d));\n    }\n\n    @Test\n    public void testReparentSeperateNodes() {\n        String html = \"<div><p>One<p>Two\";\n        Document doc = Jsoup.parse(html);\n        Element new1 = new Element(\"p\").text(\"Three\");\n        Element new2 = new Element(\"p\").text(\"Four\");\n\n        doc.body().insertChildren(-1, new1, new2);\n        assertEquals(\"<div><p>One</p><p>Two</p></div><p>Three</p><p>Four</p>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // note that these get moved from the above - as not copied\n        doc.body().insertChildren(0, new1, new2);\n        assertEquals(\"<p>Three</p><p>Four</p><div><p>One</p><p>Two</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        doc.body().insertChildren(0, new2.clone(), new1.clone());\n        assertEquals(\"<p>Four</p><p>Three</p><p>Three</p><p>Four</p><div><p>One</p><p>Two</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // shifted to end\n        doc.body().appendChild(new1);\n        assertEquals(\"<p>Four</p><p>Three</p><p>Four</p><div><p>One</p><p>Two</p></div><p>Three</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testNotActuallyAReparent() {\n        // prep\n        String html = \"<div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.selectFirst(\"div\");\n        Element new1 = new Element(\"p\").text(\"One\");\n        Element new2 = new Element(\"p\").text(\"Two\");\n        div.addChildren(new1, new2);\n\n        assertEquals(\"<div><p>One</p><p>Two</p></div>\", TextUtil.stripNewlines(div.outerHtml()));\n\n        // and the issue setup:\n        Element new3 = new Element(\"p\").text(\"Three\");\n        Element wrap = new Element(\"nav\");\n        wrap.addChildren(0, new1, new3);\n\n        assertEquals(\"<nav><p>One</p><p>Three</p></nav>\", TextUtil.stripNewlines(wrap.outerHtml()));\n        div.addChildren(wrap);\n        // now should be that One moved into wrap, leaving Two in div.\n\n        assertEquals(\"<div><p>Two</p><nav><p>One</p><p>Three</p></nav></div>\", TextUtil.stripNewlines(div.outerHtml()));\n        assertEquals(\"<div><p>Two</p><nav><p>One</p><p>Three</p></nav></div>\", TextUtil.stripNewlines(div.outerHtml()));\n    }\n\n    @Test\n    public void testChildSizeWithMixedContent() {\n        Document doc = Jsoup.parse(\"<table><tbody>\\n<tr>\\n<td>15:00</td>\\n<td>sport</td>\\n</tr>\\n</tbody></table>\");\n        Element row = doc.selectFirst(\"table tbody tr\");\n        assertEquals(2, row.childrenSize());\n        assertEquals(5, row.childNodeSize());\n    }\n\n    @Test\n    public void isBlock() {\n        String html = \"<div><p><span>Hello</span>\";\n        Document doc = Jsoup.parse(html);\n        assertTrue(doc.selectFirst(\"div\").isBlock());\n        assertTrue(doc.selectFirst(\"p\").isBlock());\n        assertFalse(doc.selectFirst(\"span\").isBlock());\n    }\n\n    @Test\n    public void testScriptTextHtmlSetAsData() {\n        String src = \"var foo = 5 < 2;\\nvar bar = 1 && 2;\";\n        String html = \"<script>\" + src + \"</script>\";\n        Document doc = Jsoup.parse(html);\n        Element el = doc.selectFirst(\"script\");\n        assertNotNull(el);\n\n        validateScriptContents(src, el);\n\n        src = \"var foo = 4 < 2;\\nvar bar > 1 && 2;\";\n        el.html(src);\n        validateScriptContents(src, el);\n\n        // special case for .text (in HTML; in XML will just be regular text)\n        el.text(src);\n        validateScriptContents(src, el);\n\n        // XML, no special treatment, get escaped correctly\n        Document xml = Parser.xmlParser().parseInput(html, \"\");\n        Element xEl = xml.selectFirst(\"script\");\n        assertNotNull(xEl);\n        src = \"var foo = 5 < 2;\\nvar bar = 1 && 2;\";\n        String escaped = \"var foo = 5 &lt; 2;\\nvar bar = 1 &amp;&amp; 2;\";\n        validateXmlScriptContents(xEl);\n        xEl.text(src);\n        validateXmlScriptContents(xEl);\n        xEl.html(src);\n        validateXmlScriptContents(xEl);\n\n        assertEquals(\"<script>var foo = 4 < 2;\\nvar bar > 1 && 2;</script>\", el.outerHtml());\n        assertEquals(\"<script>\" + escaped + \"</script>\", xEl.outerHtml()); // escaped in xml as no special treatment\n\n    }\n\n    @Test\n    public void testShallowCloneToString() {\n        // https://github.com/jhy/jsoup/issues/1410\n        Document doc = Jsoup.parse(\"<p><i>Hello</i></p>\");\n        Element p = doc.selectFirst(\"p\");\n        Element i = doc.selectFirst(\"i\");\n        String pH = p.shallowClone().toString();\n        String iH = i.shallowClone().toString();\n\n        assertEquals(\"<p></p>\", pH); // shallow, so no I\n        assertEquals(\"<i></i>\", iH);\n\n        assertEquals(p.outerHtml(), p.toString());\n        assertEquals(i.outerHtml(), i.toString());\n    }\n\n    @Test\n    public void styleHtmlRoundTrips() {\n        String styleContents = \"foo < bar > qux {color:white;}\";\n        String html = \"<head><style>\" + styleContents + \"</style></head>\";\n        Document doc = Jsoup.parse(html);\n\n        Element head = doc.head();\n        Element style = head.selectFirst(\"style\");\n        assertNotNull(style);\n        assertEquals(styleContents, style.html());\n        style.html(styleContents);\n        assertEquals(styleContents, style.html());\n        assertEquals(\"\", style.text());\n        style.text(styleContents); // pushes the HTML, not the Text\n        assertEquals(\"\", style.text());\n        assertEquals(styleContents, style.html());\n    }\n\n    @Test\n    public void moveChildren() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div><div></div>\");\n        Elements divs = doc.select(\"div\");\n        Element a = divs.get(0);\n        Element b = divs.get(1);\n\n        b.insertChildren(-1, a.childNodes());\n\n        assertEquals(\"<div></div>\\n<div>\\n <p>One</p>\\n <p>Two</p>\\n <p>Three</p>\\n</div>\",\n            doc.body().html());\n    }\n\n    @Test\n    public void moveChildrenToOuter() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div><div></div>\");\n        Elements divs = doc.select(\"div\");\n        Element a = divs.get(0);\n        Element b = doc.body();\n\n        b.insertChildren(-1, a.childNodes());\n\n        assertEquals(\"<div></div>\\n<div></div>\\n<p>One</p>\\n<p>Two</p>\\n<p>Three</p>\",\n            doc.body().html());\n    }\n\n    @Test\n    public void appendChildren() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div><div><p>Four</div>\");\n        Elements divs = doc.select(\"div\");\n        Element a = divs.get(0);\n        Element b = divs.get(1);\n\n        b.appendChildren(a.childNodes());\n\n        assertEquals(\"<div></div>\\n<div>\\n <p>Four</p>\\n <p>One</p>\\n <p>Two</p>\\n <p>Three</p>\\n</div>\",\n            doc.body().html());\n    }\n\n    @Test\n    public void prependChildren() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div><div><p>Four</div>\");\n        Elements divs = doc.select(\"div\");\n        Element a = divs.get(0);\n        Element b = divs.get(1);\n\n        b.prependChildren(a.childNodes());\n\n        assertEquals(\"<div></div>\\n<div>\\n <p>One</p>\\n <p>Two</p>\\n <p>Three</p>\\n <p>Four</p>\\n</div>\",\n            doc.body().html());\n    }\n\n    @Test\n    public void loopMoveChildren() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div><div><p>Four</div>\");\n        Elements divs = doc.select(\"div\");\n        Element a = divs.get(0);\n        Element b = divs.get(1);\n\n        Element outer = b.parent();\n        assertNotNull(outer);\n        for (Node node : a.childNodes()) {\n            outer.appendChild(node);\n        }\n\n        assertEquals(\"<div></div>\\n<div>\\n <p>Four</p>\\n</div>\\n<p>One</p>\\n<p>Two</p>\\n<p>Three</p>\",\n            doc.body().html());\n    }\n\n    @Test\n    public void accessorsDoNotVivifyAttributes() throws NoSuchFieldException, IllegalAccessException {\n        // internally, we don't want to create empty Attribute objects unless actually used for something\n        Document doc = Jsoup.parse(\"<div><p><a href=foo>One</a>\");\n        Element div = doc.selectFirst(\"div\");\n        Element p = doc.selectFirst(\"p\");\n        Element a = doc.selectFirst(\"a\");\n\n        // should not create attributes\n        assertEquals(\"\", div.attr(\"href\"));\n        p.removeAttr(\"href\");\n\n        Elements hrefs = doc.select(\"[href]\");\n        assertEquals(1, hrefs.size());\n\n        assertFalse(div.hasAttributes());\n        assertFalse(p.hasAttributes());\n        assertTrue(a.hasAttributes());\n    }\n\n    @Test\n    public void childNodesAccessorDoesNotVivify() {\n        Document doc = Jsoup.parse(\"<p></p>\");\n        Element p = doc.selectFirst(\"p\");\n        assertFalse(p.hasChildNodes());\n\n        assertEquals(0, p.childNodeSize());\n        assertEquals(0, p.childrenSize());\n\n        List<Node> childNodes = p.childNodes();\n        assertEquals(0, childNodes.size());\n\n        Elements children = p.children();\n        assertEquals(0, children.size());\n\n        assertFalse(p.hasChildNodes());\n    }\n\n    @Test void emptyChildrenElementsIsModifiable() {\n        // using unmodifiable empty in childElementList as short circuit, but people may be modifying Elements.\n        Element p = new Element(\"p\");\n        Elements els = p.children();\n        assertEquals(0, els.size());\n        els.add(new Element(\"a\"));\n        assertEquals(1, els.size());\n    }\n\n    @Test public void attributeSizeDoesNotAutoVivify() {\n        Document doc = Jsoup.parse(\"<p></p>\");\n        Element p = doc.selectFirst(\"p\");\n        assertNotNull(p);\n        assertFalse(p.hasAttributes());\n        assertEquals(0, p.attributesSize());\n        assertFalse(p.hasAttributes());\n\n        p.attr(\"foo\", \"bar\");\n        assertEquals(1, p.attributesSize());\n        assertTrue(p.hasAttributes());\n\n        p.removeAttr(\"foo\");\n        assertEquals(0, p.attributesSize());\n    }\n\n    @Test void clonedElementsHaveOwnerDocsAndIndependentSettings() {\n        // https://github.com/jhy/jsoup/issues/763\n        Document doc = Jsoup.parse(\"<div>Text</div><div>Two</div>\");\n        doc.outputSettings().prettyPrint(false);\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        Node text = div.childNode(0);\n        assertNotNull(text);\n\n        Element divClone = div.clone();\n        Document docClone = divClone.ownerDocument();\n        assertNotNull(docClone);\n        assertFalse(docClone.outputSettings().prettyPrint());\n        assertNotSame(doc, docClone);\n        assertSame(docClone, divClone.childNode(0).ownerDocument());\n        // the cloned text has same owner doc as the cloned div\n\n        doc.outputSettings().prettyPrint(true);\n        assertTrue(doc.outputSettings().prettyPrint());\n        assertFalse(docClone.outputSettings().prettyPrint());\n        assertEquals(1, docClone.children().size()); // check did not get the second div as the owner's children\n        assertEquals(divClone, docClone.child(0)); // note not the head or the body -- not normalized\n    }\n\n    private static Stream<Document.OutputSettings> testOutputSettings() {\n        return Stream.of(\n            new Document.OutputSettings().prettyPrint(true).indentAmount(4),\n            new Document.OutputSettings().prettyPrint(true).indentAmount(1),\n            new Document.OutputSettings().prettyPrint(true).indentAmount(4).outline(true),\n            new Document.OutputSettings().prettyPrint(false)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"testOutputSettings\")\n    void prettySerializationRoundTrips(Document.OutputSettings settings) {\n        // https://github.com/jhy/jsoup/issues/1688\n        // tests that repeated html() and parse() does not accumulate errant spaces / newlines\n        Document doc = Jsoup.parse(\"<div>\\nFoo\\n<p>\\nBar\\nqux</p></div>\\n<script>\\n alert('Hello!');\\n</script>\");\n        doc.outputSettings(settings);\n        String html = doc.html();\n        Document doc2 = Jsoup.parse(html);\n        doc2.outputSettings(settings);\n        String html2 = doc2.html();\n\n        assertEquals(html, html2);\n    }\n\n    @Test void prettyPrintScriptsDoesNotGrowOnRepeat() {\n        Document doc = Jsoup.parse(\"<div>\\nFoo\\n<p>\\nBar\\nqux</p></div>\\n<script>\\n alert('Hello!');\\n</script>\");\n        Document.OutputSettings settings = doc.outputSettings();\n        settings\n            .prettyPrint(true)\n            .outline(true)\n            .indentAmount(4)\n            ;\n\n        String html = doc.html();\n        Document doc2 = Jsoup.parse(html);\n        doc2.outputSettings(settings);\n        String html2 = doc2.html();\n        assertEquals(html, html2);\n    }\n\n    @Test void elementBrText() {\n        // testcase for https://github.com/jhy/jsoup/issues/1437\n        String html = \"<p>Hello<br>World</p>\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().prettyPrint(false); // otherwise html serializes as Hello<br>\\n World.\n        Element p = doc.select(\"p\").first();\n        assertNotNull(p);\n        assertEquals(html, p.outerHtml());\n        assertEquals(\"Hello World\", p.text());\n        assertEquals(\"Hello\\nWorld\", p.wholeText());\n    }\n\n    @Test void wrapTextAfterBr() {\n        // https://github.com/jhy/jsoup/issues/1858\n        String html = \"<p>Hello<br>there<br>now.</p>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<p>Hello\\n <br>\\n there\\n <br>\\n now.</p>\", doc.body().html());\n    }\n\n    @Test void prettyprintBrInBlock() {\n        String html = \"<div><br> </div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<div>\\n <br>\\n</div>\", doc.body().html()); // not div\\n br\\n \\n/div\n    }\n\n    @Test void prettyprintBrWhenNotFirstChild() {\n        // https://github.com/jhy/jsoup/issues/1911\n        String h = \"<div><p><br>Foo</p><br></div>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<div>\\n <p>\\n  <br>\\n  Foo\\n </p>\\n <br>\\n</div>\", doc.body().html());\n        // br gets wrapped\n    }\n\n    @Test void preformatFlowsToChildTextNodes() {\n        // https://github.com/jhy/jsoup/issues/1776\n        String html = \"<div><pre>One\\n<span>\\nTwo</span>\\n <span>  \\nThree</span>\\n <span>Four <span>Five</span>\\n  Six\\n</pre>\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().indentAmount(2).prettyPrint(true);\n\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        String actual = div.outerHtml();\n        String expect = \"<div>\\n\" +\n            \"  <pre>One\\n\" +\n            \"<span>\\n\" +\n            \"Two</span>\\n\" +\n            \" <span>  \\n\" +\n            \"Three</span>\\n\" +\n            \" <span>Four <span>Five</span>\\n\" +\n            \"  Six\\n\" +\n            \"</span></pre>\\n\" +\n            \"</div>\";\n        assertEquals(expect, actual);\n\n        String expectText = \"One\\n\" +\n            \"\\n\" +\n            \"Two\\n\" +\n            \"   \\n\" +\n            \"Three\\n\" +\n            \" Four Five\\n\" +\n            \"  Six\\n\";\n        assertEquals(expectText, div.wholeText());\n\n        String expectOwn = \"One\\n\" +\n            \"\\n\" +\n            \" \\n\" +\n            \" \";\n        assertEquals(expectOwn, div.child(0).wholeOwnText());\n    }\n\n    @Test void inlineInBlockShouldIndent() {\n        // was inconsistent between <div>\\n<span> and <div><span> - former would print inline, latter would wrap(!)\n        String html = \"<div>One <span>Hello</span><span>!</span></div><div>\\n<span>There</span></div><div> <span>Now</span></div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\n            \"<div>\\n\" +\n                \" One <span>Hello</span><span>!</span>\\n\" +\n                \"</div>\\n\" +\n                \"<div>\\n\" +\n                \" <span>There</span>\\n\" +\n                \"</div>\\n\" +\n                \"<div>\\n\" +\n                \" <span>Now</span>\\n\" +\n                \"</div>\",\n            doc.body().html());\n    }\n\n    @Test void testExpectFirst() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two <span>Three</span> <span>Four</span>\");\n\n        Element span = doc.expectFirst(\"span\");\n        assertEquals(\"Three\", span.text());\n\n        assertNull(doc.selectFirst(\"div\"));\n        boolean threw = false;\n        try {\n            Element div = doc.expectFirst(\"div\");\n        } catch (IllegalArgumentException e) {\n            threw = true;\n        }\n        assertTrue(threw);\n    }\n\n    @Test void testExpectFirstMessage() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two <span>Three</span> <span>Four</span>\");\n        boolean threw = false;\n        Element p = doc.expectFirst(\"P\");\n        try {\n            Element span = p.expectFirst(\"span.doesNotExist\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"No elements matched the query 'span.doesNotExist' on element 'p'.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void testExpectFirstMessageDoc() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two <span>Three</span> <span>Four</span>\");\n        boolean threw = false;\n        Element p = doc.expectFirst(\"P\");\n        try {\n            Element span = doc.expectFirst(\"span.doesNotExist\");\n        } catch (ValidationException e) {\n            threw = true;\n            assertEquals(\"No elements matched the query 'span.doesNotExist' in the document.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void spanRunsMaintainSpace() {\n        // https://github.com/jhy/jsoup/issues/1787\n        Document doc = Jsoup.parse(\"<p><span>One</span>\\n<span>Two</span>\\n<span>Three</span></p>\");\n        String text = \"One Two Three\";\n        Element body = doc.body();\n        assertEquals(text, body.text());\n\n        Element p = doc.expectFirst(\"p\");\n        String html = p.html();\n        p.html(html);\n        assertEquals(text, body.text());\n\n        assertEquals(\"<p><span>One</span> <span>Two</span> <span>Three</span></p>\", body.html());\n    }\n\n    @Test void doctypeIsPrettyPrinted() {\n        // resolves underlying issue raised in https://github.com/jhy/jsoup/pull/1664\n        Document doc1 = Jsoup.parse(\"<!--\\nlicense\\n-->\\n \\n<!doctype html>\\n<html>\");\n        Document doc2 = Jsoup.parse(\"\\n  <!doctype html><html>\");\n        Document doc3 = Jsoup.parse(\"<!doctype html>\\n<html>\");\n        Document doc4 = Jsoup.parse(\"\\n<!doctype html>\\n<html>\");\n        Document doc5 = Jsoup.parse(\"\\n<!--\\n comment \\n -->  <!doctype html>\\n<html>\");\n        Document doc6 = Jsoup.parse(\"<!--\\n comment \\n -->  <!doctype html>\\n<html>\");\n\n        assertEquals(\"<!--\\nlicense\\n-->\\n<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc1.html());\n        doc1.outputSettings().prettyPrint(false);\n        assertEquals(\"<!--\\nlicense\\n--><!doctype html>\\n<html><head></head><body></body></html>\", doc1.html());\n        // note that the whitespace between the comment and the doctype is not retained, in Initial state\n\n        assertEquals(\"<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc2.html());\n        assertEquals(\"<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc3.html());\n        assertEquals(\"<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc4.html());\n        assertEquals(\"<!--\\n comment \\n -->\\n<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc5.html());\n        assertEquals(\"<!--\\n comment \\n -->\\n<!doctype html>\\n<html>\\n <head></head>\\n <body></body>\\n</html>\", doc6.html());\n    }\n\n    @Test void textnodeInBlockIndent() {\n        String html =\"<div>\\nmsg \\n </div>\\n<div>\\nmsg \\n </div><div><div>msg</div></div><div>msg<p>msg</p></div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\n            \"<div>msg</div>\\n<div>msg</div>\\n<div>\\n <div>msg</div>\\n</div>\\n<div>\\n msg\\n <p>msg</p>\\n</div>\",\n            doc.body().html()\n        );\n    }\n\n    @Test void stripTrailing() {\n        String html = \"<p> This <span>is </span>fine. </p>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<p>This <span>is </span>fine.</p>\", doc.body().html());\n    }\n\n    @Test void elementIndentAndSpaceTrims() {\n        String html = \"<body><div> <p> One Two </p> <a>  Hello </a><p>\\nSome text \\n</p>\\n </div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<div>\\n\" +\n            \" <p>One Two</p>\\n\" +\n            \" <a> Hello </a>\\n\" +\n            \" <p>Some text</p>\\n\" +\n            \"</div>\", doc.body().html());\n    }\n\n    @Test void divAInlineable() {\n        String html = \"<body><div> <a>Text</a>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<div>\\n\" +\n            \" <a>Text</a>\\n\" +\n            \"</div>\", doc.body().html());\n    }\n\n    @Test void noDanglingSpaceAfterCustomElement() {\n        // https://github.com/jhy/jsoup/issues/1852\n        String html = \"<bar><p/>\\n</bar>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<bar>\\n <p></p>\\n</bar>\", doc.body().html());\n\n        html = \"<foo>\\n  <bar /></foo>\";\n        doc = Jsoup.parse(html);\n        assertEquals(\"<foo>\\n <bar></bar>\\n</foo>\", doc.body().html());\n    }\n\n    @Test void spanInBlockTrims() {\n        String html = \"<p>Lorem ipsum</p>\\n<span>Thanks</span>\";\n        Document doc = Jsoup.parse(html);\n        String outHtml = doc.body().html();\n        assertEquals(\"<p>Lorem ipsum</p>\\n<span>Thanks</span>\", outHtml);\n    }\n\n    @Test void replaceWithSelf() {\n        // https://github.com/jhy/jsoup/issues/1843\n        Document doc = Jsoup.parse(\"<p>One<p>Two\");\n        Elements ps = doc.select(\"p\");\n        Element first = ps.first();\n\n        assertNotNull(first);\n        first.replaceWith(first);\n        assertEquals(ps.get(1), first.nextSibling());\n        assertEquals(\"<p>One</p>\\n<p>Two</p>\", first.parent().html());\n    }\n\n    @Test void select() {\n        Evaluator eval = QueryParser.parse(\"div\");\n        Document doc = Jsoup.parse(reference);\n        Elements els = doc.select(\"div\");\n        Elements els2 = doc.select(eval);\n        assertEquals(els, els2);\n    }\n\n    @Test void insertChildrenValidation() {\n        Document doc = Jsoup.parse(reference);\n        Element div = doc.expectFirst(\"div\");\n        Throwable ex = assertThrows(ValidationException.class, () -> div.insertChildren(20, new Element(\"div\")));\n        assertEquals(\"Insert position out of bounds.\", ex.getMessage());\n    }\n\n    @Test void cssSelectorNoDoc() {\n        Element el = new Element(\"div\");\n        el.id(\"one\");\n        assertEquals(\"#one\", el.cssSelector());\n    }\n\n    @Test void cssSelectorNoParent() {\n        Element el = new Element(\"div\");\n        assertEquals(\"div\", el.cssSelector());\n    }\n\n    @Test void cssSelectorParentWithId() {\n        // https://github.com/jhy/jsoup/issues/2282\n        Document doc = Jsoup.parse(\"<div><div id=id1><p>A</p></div><div><p>B</p></div><div class='c1 c2'><p>C</p></div></div>\");\n        Elements els = doc.select(\"p\");\n        Element pA = els.get(0);\n        Element pB = els.get(1);\n        Element pC = els.get(2);\n        assertEquals(\"#id1 > p\", pA.cssSelector());\n        assertEquals(\"html > body > div > div:nth-child(2) > p\", pB.cssSelector());\n        assertEquals(\"html > body > div > div.c1.c2 > p\", pC.cssSelector());\n    }\n\n    @Test void cssSelectorWithNonUniqueId() {\n        Document doc = Jsoup.parse(\"<main id=out><div><div id=in>One</div><div id=in>Two</div></div></main>\");\n        Element two = doc.expectFirst(\"div:containsOwn(Two)\");\n        String selector = two.cssSelector();\n        assertEquals(\"#out > div > div:nth-child(2)\", selector);\n        Elements found = doc.select(selector);\n        assertEquals(1, found.size());\n        assertEquals(two, found.first());\n    }\n\n    @Test void cssSelectorDoesntStackOverflow() {\n        // https://github.com/jhy/jsoup/issues/2001\n        Element element = new Element(\"element\");\n        Element root = element;\n\n        // Create a long chain of elements\n        for (int i = 0; i < 5000; i++) {\n            Element elem2 = new Element(\"element\" + i);\n            element.appendChild(elem2);\n            element = elem2;\n        }\n\n        String selector = element.cssSelector(); // would overflow in cssSelector parent() recurse\n        Evaluator eval = QueryParser.parse(selector);\n\n        assertEquals(eval.toString(), selector);\n        assertTrue(selector.startsWith(\"element > element0 >\"));\n        assertTrue(selector.endsWith(\"8 > element4999\"));\n\n        Elements elements = root.select(selector); // would overflow in nested And ImmediateParent chain eval\n        assertEquals(1, elements.size());\n        assertEquals(element, elements.first());\n    }\n\n    @Test void cssSelectorWithBracket() {\n        // https://github.com/jhy/jsoup/issues/2146\n        Document doc = Jsoup.parse(\"<div class='a[foo]'>One</div><div class='b[bar]'>Two</div>\");\n        Element div = doc.expectFirst(\"div\");\n        String selector = div.cssSelector();\n        assertEquals(\"html > body > div.a\\\\[foo\\\\]\", selector); // would fail with \"Did not find balanced marker\", consumeSubquery was not handling escapes\n\n        Elements selected = doc.select(selector);\n        assertEquals(1, selected.size());\n        assertEquals(selected.first(), div);\n    }\n\n    @Test void cssSelectorUnbalanced() {\n        // https://github.com/jhy/jsoup/issues/2146\n        Document doc = Jsoup.parse(\"<div class='a(foo'>One</div><div class='a-bar'>Two</div>\");\n        Element div = doc.expectFirst(\"div\");\n        String selector = div.cssSelector();\n        assertEquals(\"html > body > div.a\\\\(foo\", selector);\n\n        Elements selected = doc.select(selector);\n        assertEquals(1, selected.size());\n        assertEquals(selected.first(), div);\n    }\n\n    @Test void cssSelectorWithAsterisk() {\n        // https://github.com/jhy/jsoup/issues/2169\n        Document doc = Jsoup.parse(\"<div class='vds-items_flex-end [&amp;_>_*:first-child]:vds-pt_0'>One</div><div class='vds-items_flex-end'>Two</div>\");\n        Element div = doc.expectFirst(\"div\");\n        String selector = div.cssSelector();\n        assertEquals(\"html > body > div.vds-items_flex-end.\\\\[\\\\&_\\\\>_\\\\*\\\\:first-child\\\\]\\\\:vds-pt_0\", selector);\n\n        Elements selected = doc.select(selector);\n        assertEquals(1, selected.size());\n        assertEquals(selected.first(), div);\n    }\n\n    @Test void cssSelectorWithPipe() {\n        // https://github.com/jhy/jsoup/issues/1998\n        Document doc = Jsoup.parse(\"<div><span class='|'>One</div>\");\n        Element span = doc.expectFirst(\"div span\");\n        String selector = span.cssSelector();\n        assertEquals(\"html > body > div > span.\\\\|\", selector);\n        Elements selected = doc.select(selector);\n        assertSelectedOwnText(selected, \"One\");\n    }\n\n    @Test void cssSelectorCombined() {\n        // https://github.com/jhy/jsoup/issues/1984\n        Document doc = Jsoup.parse(\"<img class='e\\u0301'><p class=👨‍👨‍👧‍👧></p><a class='\\uD83D\\uDC68\\u200D\\uD83D\\uDC68\\u200D\\uD83D\\uDC67\\u200D\\uD83D\\uDC67'></a>\");\n        Element img = doc.expectFirst(\"img\");\n        Element p = doc.expectFirst(\"p\");\n        Element a = doc.expectFirst(\"a\");\n\n        String imgQ = img.cssSelector();\n        String pQ = p.cssSelector();\n        String aQ = a.cssSelector();\n\n        assertEquals(\"html > body > img.é\", imgQ); // previously was img.e\\́; chrome gives literal body > img.e\\\\u0301\n        assertEquals(\"html > body > p.👨‍👨‍👧‍👧\", pQ); // chrome gives body > p.👨‍👨‍👧‍👧\n        assertEquals(\"html > body > a.👨‍👨‍👧‍👧\", aQ); // body > a.👨‍👨‍👧‍👧\n\n        assertSame(img, doc.expectFirst(imgQ));\n        assertSame(p, doc.expectFirst(pQ));\n        assertSame(a, doc.expectFirst(aQ));\n    }\n\n    @Test void orphanSiblings() {\n        Element el = new Element(\"div\");\n        assertEquals(0, el.siblingElements().size());\n        assertEquals(0, el.nextElementSiblings().size());\n        assertEquals(0, el.previousElementSiblings().size());\n        assertNull(el.nextElementSibling());\n        assertNull(el.previousElementSibling());\n    }\n\n    @Test void getElementsByAttributeStarting() {\n        Document doc = Jsoup.parse(\"<div data-one=1 data-two=2 id=1><p data-one=3 id=2>Text</div><div>\");\n        Elements els = doc.getElementsByAttributeStarting(\" data- \");\n        assertEquals(2, els.size());\n        assertEquals(\"1\", els.get(0).id());\n        assertEquals(\"2\", els.get(1).id());\n        assertEquals(0, doc.getElementsByAttributeStarting(\"not-data\").size());\n    }\n\n    @Test void getElementsByAttributeValueNot() {\n        Document doc = Jsoup.parse(\"<div data-one=1 data-two=2 id=1><p data-one=3 id=2>Text</div><div id=3>\");\n        Elements els = doc.body().getElementsByAttributeValueNot(\"data-one\", \"1\");\n        assertEquals(3, els.size()); // the body, p, and last div\n        assertEquals(\"body\", els.get(0).normalName());\n        assertEquals(\"2\", els.get(1).id());\n        assertEquals(\"3\", els.get(2).id());\n    }\n\n    @Test void getElementsByAttributeValueStarting() {\n        Document doc = Jsoup.parse(\"<a href=one1></a><a href=one2></a><a href=else</a>\");\n        Elements els = doc.getElementsByAttributeValueStarting(\"href\", \"one\");\n        assertEquals(2, els.size());\n        assertEquals(\"one1\", els.get(0).attr(\"href\"));\n        assertEquals(\"one2\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsByAttributeValueEnding() {\n        Document doc = Jsoup.parse(\"<a href=1one></a><a href=2one></a><a href=else</a>\");\n        Elements els = doc.getElementsByAttributeValueEnding(\"href\", \"one\");\n        assertEquals(2, els.size());\n        assertEquals(\"1one\", els.get(0).attr(\"href\"));\n        assertEquals(\"2one\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsByAttributeValueContaining() {\n        Document doc = Jsoup.parse(\"<a href=1one></a><a href=2one></a><a href=else</a>\");\n        Elements els = doc.getElementsByAttributeValueContaining(\"href\", \"on\");\n        assertEquals(2, els.size());\n        assertEquals(\"1one\", els.get(0).attr(\"href\"));\n        assertEquals(\"2one\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsByAttributeValueMatchingPattern() {\n        Document doc = Jsoup.parse(\"<a href=1one></a><a href=2one></a><a href=else</a>\");\n        Elements els = doc.getElementsByAttributeValueMatching(\"href\", Pattern.compile(\"^\\\\d\\\\w+\"));\n        assertEquals(2, els.size());\n        assertEquals(\"1one\", els.get(0).attr(\"href\"));\n        assertEquals(\"2one\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsByAttributeValueMatching() {\n        Document doc = Jsoup.parse(\"<a href=1one></a><a href=2one></a><a href=else</a>\");\n        Elements els = doc.getElementsByAttributeValueMatching(\"href\", \"^\\\\d\\\\w+\");\n        assertEquals(2, els.size());\n        assertEquals(\"1one\", els.get(0).attr(\"href\"));\n        assertEquals(\"2one\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsByAttributeValueMatchingValidation() {\n        Document doc = Jsoup.parse(reference);\n        Throwable ex = assertThrows(IllegalArgumentException.class,\n            () -> doc.getElementsByAttributeValueMatching(\"key\", \"\\\\x\"));\n        assertTrue(ex.getMessage().contains(\"Pattern syntax error\"));\n    }\n\n    @Test void getElementsByIndexEquals() {\n        Document doc = Jsoup.parse(\"<a href=1one></a><a href=2one></a><a href=else</a>\");\n        Elements els = doc.body().getElementsByIndexEquals(1);\n        assertEquals(2, els.size());\n        assertEquals(\"body\", els.get(0).normalName());\n        assertEquals(\"2one\", els.get(1).attr(\"href\"));\n    }\n\n    @Test void getElementsContainingText() {\n        Document doc = Jsoup.parse(\"<div id=1>One</div><div>Two</div>\");\n        Elements els = doc.body().getElementsContainingText(\"one\");\n        assertEquals(2, els.size());\n        assertEquals(\"body\", els.get(0).normalName());\n        assertEquals(\"1\", els.get(1).id());\n    }\n\n    @Test void getElementsContainingOwnText() {\n        Document doc = Jsoup.parse(\"<div id=1>One</div><div>Two</div>\");\n        Elements els = doc.body().getElementsContainingOwnText(\"one\");\n        assertEquals(1, els.size());\n        assertEquals(\"1\", els.get(0).id());\n    }\n\n    @Test void getElementsMatchingTextValidation() {\n        Document doc = Jsoup.parse(reference);\n        Throwable ex = assertThrows(IllegalArgumentException.class,\n            () -> doc.getElementsMatchingText(\"\\\\x\"));\n        assertTrue(ex.getMessage().contains(\"Pattern syntax error:\"));\n    }\n\n    @Test void getElementsMatchingText() {\n        Document doc = Jsoup.parse(\"<div id=1>One</div><div>Two</div>\");\n        Elements els = doc.body().getElementsMatchingText(\"O\\\\w+\");\n        assertEquals(2, els.size());\n        assertEquals(\"body\", els.get(0).normalName());\n        assertEquals(\"1\", els.get(1).id());\n    }\n\n    @Test void getElementsMatchingOwnText() {\n        Document doc = Jsoup.parse(\"<div id=1>One</div><div>Two</div>\");\n        Elements els = doc.body().getElementsMatchingOwnText(\"O\\\\w+\");\n        assertEquals(1, els.size());\n        assertEquals(\"1\", els.get(0).id());\n    }\n\n    @Test void getElementsMatchingOwnTextValidation() {\n        Document doc = Jsoup.parse(reference);\n        Throwable ex = assertThrows(IllegalArgumentException.class,\n            () -> doc.getElementsMatchingOwnText(\"\\\\x\"));\n        assertTrue(ex.getMessage().contains(\"Pattern syntax error:\"));\n    }\n\n    @Test void hasText() {\n        Document doc = Jsoup.parse(\"<div id=1><p><i>One</i></p></div><div id=2>Two</div><div id=3><script>data</script> </div>\");\n        assertTrue(doc.getElementById(\"1\").hasText());\n        assertTrue(doc.getElementById(\"2\").hasText());\n        assertFalse(doc.getElementById(\"3\").hasText());\n    }\n\n    @Test void dataInCdataNode() {\n        Element el = new Element(\"div\");\n        CDataNode cdata = new CDataNode(\"Some CData\");\n        el.appendChild(cdata);\n        assertEquals(\"Some CData\", el.data());\n\n        Document parse = Jsoup.parse(\"One <![CDATA[Hello]]>\");\n        assertEquals(\"Hello\", parse.data());\n    }\n\n    @Test void datanodesOutputCdataInXhtml() {\n        String html = \"<p><script>1 && 2</script><style>3 && 4</style> 5 &amp;&amp; 6</p>\";\n        Document doc = Jsoup.parse(html); // parsed as HTML\n        String out = TextUtil.normalizeSpaces(doc.body().html());\n        assertEquals(html, out);\n        Element scriptEl = doc.expectFirst(\"script\");\n        DataNode scriptDataNode = (DataNode) scriptEl.childNode(0);\n        assertEquals(\"1 && 2\", scriptDataNode.getWholeData());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        Element p = doc.expectFirst(\"p\");\n        String xml = p.html();\n        assertEquals(\n            \"<script>//<![CDATA[\\n\" +\n                \"1 && 2\\n\" +\n                \"//]]></script>\\n\" +\n                \"<style>/*<![CDATA[*/\\n\" +\n                \"3 && 4\\n\" +\n                \"/*]]>*/</style>\\n\" +\n                \"5 &amp;&amp; 6\",\n            xml);\n\n        Document xmlDoc = Jsoup.parse(xml, Parser.xmlParser());\n        assertEquals(xml, xmlDoc.html());\n        Element scriptXmlEl = xmlDoc.expectFirst(\"script\");\n        TextNode scriptText = (TextNode) scriptXmlEl.childNode(0);\n        assertEquals(\"//\", scriptText.getWholeText());\n        CDataNode scriptCdata = (CDataNode) scriptXmlEl.childNode(1);\n        assertEquals(\"\\n1 && 2\\n//\", scriptCdata.text());\n    }\n\n    @Test void datanodesOutputExistingCdataInXhtml() {\n        String html = \"<p><script>//<![CDATA[\\n1 && 2\\n//]]></script><style>\\n/*<![CDATA[*/3 && 4\\n/*]]>*/</style> 5 &amp;&amp; 6</p>\";;\n        Document doc = Jsoup.parse(html); // parsed as HTML\n        String out = TextUtil.normalizeSpaces(doc.body().html());\n        assertEquals(\"<p><script>//<![CDATA[1 && 2//]]></script><style>/*<![CDATA[*/3 && 4/*]]>*/</style> 5 &amp;&amp; 6</p>\", out);\n        Element scriptEl = doc.expectFirst(\"script\");\n        DataNode scriptDataNode = (DataNode) scriptEl.childNode(0);\n        assertEquals(\"//<![CDATA[\\n\" +\n            \"1 && 2\\n\" +\n            \"//]]>\", scriptDataNode.getWholeData());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        Element p = doc.expectFirst(\"p\");\n        String xml = p.html();\n        assertEquals(\n            \"<script>//<![CDATA[\\n\" +\n                \"1 && 2\\n\" +\n                \"//]]></script>\\n\" +\n                \"<style>\\n\" +\n                \"/*<![CDATA[*/3 && 4\\n\" +\n                \"/*]]>*/</style>\\n\" +\n                \"5 &amp;&amp; 6\",\n            xml);\n\n        Document xmlDoc = Jsoup.parse(xml, Parser.xmlParser());\n        assertEquals(xml, xmlDoc.html());\n        Element scriptXmlEl = xmlDoc.expectFirst(\"script\");\n        TextNode scriptText = (TextNode) scriptXmlEl.childNode(0);\n        assertEquals(\"//\", scriptText.getWholeText());\n        CDataNode scriptCdata = (CDataNode) scriptXmlEl.childNode(1);\n        assertEquals(\"\\n1 && 2\\n//\", scriptCdata.text());\n    }\n\n    @Test void outerHtmlAppendable() {\n        // tests not string builder flow\n        Document doc = Jsoup.parse(\"<div>One</div>\");\n        StringBuffer buffer = new StringBuffer();\n        doc.body().outerHtml(buffer);\n        assertEquals(\"<body>\\n <div>One</div>\\n</body>\", buffer.toString());\n        StringBuilder builder = new StringBuilder();\n        doc.body().outerHtml(builder);\n        assertEquals(\"<body>\\n <div>One</div>\\n</body>\", builder.toString());\n    }\n\n    @Test void rubyInline() {\n        String html = \"<ruby>T<rp>(</rp><rtc>!</rtc><rt>)</rt></ruby>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(html, doc.body().html());\n    }\n\n    @Test void nestedFormatAsInlinePrintsAsBlock() {\n        // https://github.com/jhy/jsoup/issues/1926\n        String h = \"        <table>\\n\" +\n            \"            <tr>\\n\" +\n            \"                <td>\\n\" +\n            \"                    <p style=\\\"display:inline;\\\">A</p>\\n\" +\n            \"                    <p style=\\\"display:inline;\\\">B</p>\\n\" +\n            \"                </td>\\n\" +\n            \"            </tr>\\n\" +\n            \"        </table>\";\n        Document doc = Jsoup.parse(h);\n        String out = doc.body().html();\n        assertEquals(\"<table>\\n\" +\n            \" <tbody>\\n\" +\n            \"  <tr>\\n\" +\n            \"   <td>\\n\" +\n            \"    <p style=\\\"display:inline;\\\">A</p>\\n\" +\n            \"    <p style=\\\"display:inline;\\\">B</p>\\n\" +\n            \"   </td>\\n\" +\n            \"  </tr>\\n\" +\n            \" </tbody>\\n\" +\n            \"</table>\", out);\n    }\n\n    @Test void emptyDetachesChildren() {\n        String html = \"<div><p>One<p>Two</p>Three</div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.expectFirst(\"div\");\n        assertEquals(3, div.childNodeSize());\n\n        List<Node> childNodes = div.childNodes();\n\n        div.empty();\n        assertEquals(0, div.childNodeSize());\n        assertEquals(3, childNodes.size()); // copied before removing\n        for (Node childNode : childNodes) {\n            assertNull(childNode.parentNode);\n        }\n\n        Element p = (Element) childNodes.get(0);\n        assertEquals(p, p.childNode(0).parentNode()); // TextNode \"One\" still has parent p, as detachment is only on div element\n    }\n\n    @Test void emptyAndAddPreviousChild() {\n        String html = \"<div><p>One<p>Two<p>Three</div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.expectFirst(\"div\");\n        Element p = div.expectFirst(\"p\");\n        div\n            .empty()\n            .appendChild(p);\n\n        assertEquals(\"<p>One</p>\", div.html());\n    }\n\n    @Test void emptyAndAddPreviousDescendant() {\n        String html = \"<header><div><p>One<p>Two<p>Three</div></header>\";\n        Document doc = Jsoup.parse(html);\n        Element header = doc.expectFirst(\"header\");\n        Element p = header.expectFirst(\"p\");\n        header\n            .empty()\n            .appendChild(p);\n\n        assertEquals(\"<p>One</p>\", header.html());\n    }\n\n    @Test void xmlSyntaxSetsEscapeMode() {\n        String html = \"Foo&nbsp;&Succeeds;\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().charset(\"ascii\"); // so we can see the zws\n        assertEquals(\"Foo&nbsp;&#x227b;\", doc.body().html());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        String out = doc.body().html();\n        assertEquals(\"Foo&#xa0;&#x227b;\", out);\n\n        // can set back if desired\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended);\n        assertEquals(\"Foo&nbsp;&succ;\", doc.body().html()); // succ is alias for Succeeds, and first hit in entities\n    }\n\n    @Test void attribute() {\n        String html = \"<p CLASS='yes'>One</p>\";\n        Document doc = Jsoup.parse(html);\n        Element p = doc.expectFirst(\"p\");\n        Attribute attr = p.attribute(\"class\"); // HTML parse lower-cases names\n        assertNotNull(attr);\n        assertEquals(\"class\", attr.getKey());\n        assertEquals(\"yes\", attr.getValue());\n        assertFalse(attr.sourceRange().nameRange().start().isTracked()); // tracking disabled\n\n        assertNull(p.attribute(\"CLASS\")); // no such key\n\n        attr.setKey(\"CLASS\"); // set preserves input case\n        attr.setValue(\"YES\");\n\n        assertEquals(\"<p CLASS=\\\"YES\\\">One</p>\", p.outerHtml());\n        assertEquals(\"CLASS=\\\"YES\\\"\", attr.html());\n    }\n\n    @Test void testSelectStream() {\n        Document doc = Jsoup.parse(\"<div>Hello world</div>\");\n        Element div = doc.select(\"div\").stream().findFirst().orElse(null);\n\n        assertEquals(\"Hello world\", div.text());\n\n        div = doc.selectStream(\"div\").findFirst().orElse(null);\n\n        assertEquals(\"Hello world\", div.text());\n    }\n\n    @Test void elementIsIterable() {\n        Document doc = Jsoup.parse(\"<div><a id=1>One</a> Two <a id=2>Three<b>Four</a><a id=3>Five</a></div>\");\n        String expect = \"div;a#1;a#2;b;b;a#3;\"; // elements only, in doc order\n        Element div = doc.expectFirst(\"div\");\n\n        // for each pattern\n        StringBuilder seen = new StringBuilder();\n        for (Element el: div) {\n            trackSeen(el, seen);\n        }\n        assertEquals(expect, seen.toString());\n\n        // iterator\n        seen = new StringBuilder();\n        Iterator<Element> iterator = div.iterator();\n        assertIterates(iterator, expect);\n    }\n\n    @Test void htmlToXmlNormalizes() {\n        // https://github.com/jhy/jsoup/issues/1496\n        String in = \"<p\\u226F\\u0322>One</p\\u226F\\u0322>\";\n        Document doc = Jsoup.parse(in);\n        doc.outputSettings().prettyPrint(false);\n        String html = doc.body().html();\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        String xml = doc.body().html();\n        assertEquals(\"<p≯̢>One</p≯̢>\", html);\n        assertEquals(\"<p_>One</p_>\", xml);\n    }\n\n    @Test\n    public void invalidCharactersDiscardedInXml() {\n        // https://github.com/jhy/jsoup/issues/1743\n        String invalid = \"AAA&#xc;BBB\\fCCC\\uFFFE\\uFFFFDDD\";\n        Document doc = Jsoup.parseBodyFragment(invalid);\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml).prettyPrint(false);\n        String cleaned = doc.body().html();\n        assertFalse(cleaned.contains(\"\\f\"));\n        assertFalse(cleaned.contains(\"&#xc;\"));\n        assertFalse(cleaned.contains(\"\\uFFFE\"));\n        assertFalse(cleaned.contains(\"\\uFFFF\"));\n        assertTrue(cleaned.matches(\"AAA *BBB *CCC *DDD\"));\n    }\n\n    @Test\n    public void asList() {\n        // supports https://github.com/jhy/jsoup/issues/2100\n        Document doc = Jsoup.parse(\"<p id=1>One</p><p>Two</p><p>Three</p>\");\n        Elements els = doc.select(\"p\");\n        ArrayList<Element> list = els.asList();\n        assertEquals(els.size(), list.size());\n\n        // does not modify backing DOM\n        list.remove(0);\n        assertEquals(3, els.size());\n        assertEquals(2, list.size());\n\n        Element el = doc.expectFirst(\"#1\");\n        assertSame(doc, el.ownerDocument());\n    }\n\n    @Test\n    public void deselect() {\n        // supports https://github.com/jhy/jsoup/issues/2100\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</p></div>\");\n        Elements els = doc.select(\"p\");\n        Element parent = doc.expectFirst(\"div\");\n\n        Element removedByIndex = els.deselect(1);\n        assertEquals(\"Two\", removedByIndex.text());\n        assertEquals(2, els.size());\n        assertEquals(3, parent.childrenSize());\n\n        Element toRemove = doc.expectFirst(\"p:contains(Three)\");\n        boolean removedByObject = els.deselect(toRemove);\n        assertTrue(removedByObject);\n        assertEquals(1, els.size());\n        assertEquals(3, parent.childrenSize());\n    }\n\n    @Test\n    public void deselectAll() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</p></div>\");\n        Elements els = doc.select(\"p\");\n        Element parent = doc.expectFirst(\"div\");\n\n        els.deselectAll();\n        assertEquals(0, els.size());\n        assertEquals(3, parent.childrenSize());\n    }\n\n    @Test void selectDescendents() {\n        String html = \"<div id=out><div id=1><div id=2></div></div><div id=3></div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.expectFirst(\"#out\");\n\n        Elements childs = div.select(\"> div\");\n        assertSelectedIds(childs, \"1\", \"3\");\n\n        Elements descendents = div.select(\"* div\");\n        assertSelectedIds(descendents, \"1\", \"2\", \"3\");\n\n        Elements all = div.select(\"div\");\n        assertSelectedIds(all, \"out\", \"1\", \"2\", \"3\");\n    }\n\n    @Test void setTextOnSvgScriptSetsDataNode() {\n        // calling .text() on svg script will create a datanode, as defined in TagSet\n        String html = \"<svg><script></script></svg>\";\n        Document doc = Jsoup.parse(html);\n        Element script = doc.expectFirst(\"script\");\n        script.text(\"a < b\");\n        assertEquals(\"<script>a < b</script>\", script.outerHtml()); // not encoded\n        assertEquals(\"a < b\", script.data());\n    }\n\n    @Test void expectFirstNode() {\n        Document doc = Jsoup.parse(\"<span id=1>One</span> <span id=2>Two</span>\");\n        TextNode text = doc.expectFirstNode(\"::text\", TextNode.class);\n        assertEquals(\"1\", text.parent().id());\n\n        TextNode text2 = doc.selectFirstNode(\"::text\", TextNode.class);\n        assertSame(text, text2);\n\n        assertNull(doc.selectFirstNode(\"::comment\", Comment.class));\n    }\n\n    @Test void expectFirstThrows() {\n        Document doc = Jsoup.parse(\"<span id=1>One</span> <span id=2>Two</span>\");\n        boolean threw = false;\n        try {\n            doc.expectFirstNode(\"::comment\", Comment.class);\n        } catch (IllegalArgumentException e) {\n            threw = true;\n            assertEquals(\"No nodes matched the query '::comment' in the document.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void childByIndex() {\n        // uncached, cached paths\n        Element el = Jsoup.parse(\"<div>One <p>Two</p> Three <p>Four</p> Five <p>Six</p>\").expectFirst(\"div\");\n\n        // uncached\n        Element p0 = el.child(0);\n        Element p1 = el.child(1);\n        Element p2 = el.child(2);\n        assertNull(el.cachedChildren());\n\n        assertEquals(\"Two\", p0.text());\n        assertEquals(\"Four\", p1.text());\n        assertEquals(\"Six\", p2.text());\n\n        // cached\n        Elements children = el.children();\n        assertNotNull(el.cachedChildren());\n        assertSame(p0, el.child(0));\n        assertSame(p1, el.child(1));\n        assertSame(p2, el.child(2));\n    }\n\n    @Test public void testChildThrowsIndexOutOfBoundsWhenCachedChildrenIsNull() {\n        Element el = Jsoup.parse(\"<div><p>One</p></div>\").expectFirst(\"div\");\n        assertNull(el.cachedChildren());\n        Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> {\n            el.child(5);\n        });\n        assertTrue(exception.getMessage().contains(\"No child at index: 5\"));\n    }\n\n    @Test public void testChildrenSizeUncachedAndCached() {\n        Element el = Jsoup.parse(\"<div>One <p>Two</p> Three <p>Four</p> Five <p>Six</p>\").expectFirst(\"div\");\n\n        // uncached\n        assertNull(el.cachedChildren());\n        assertEquals(3, el.childrenSize());\n        // gets cached. As we have to iter elements anyway, might as well make and cache the list, so later child(i) is fast. supports for(i=0;i<el.childrenSize()){child=el.child(i)} case. (But better just to for el.children() ).\n        assertNotNull(el.cachedChildren());\n\n        el = Jsoup.parse(\"<div>One <p>Two</p> Three <p>Four</p> Five <p>Six</p> <b></b>\").expectFirst(\"div\"); // resest\n        assertNull(el.cachedChildren());\n        el.children();\n        assertNotNull(el.cachedChildren());\n        assertEquals(4, el.childrenSize());\n\n        Element empty = el.expectFirst(\"b\");\n        assertEquals(0, empty.childrenSize());\n        assertNull(empty.cachedChildren()); // 0 node fast path, does not create list\n    }\n\n    @Test public void testReplaceInvalidates() {\n        // https://github.com/jhy/jsoup/issues/2391\n        String html = \"<div>test</div>\";\n        Document doc = Jsoup.parseBodyFragment(html);\n        Element div = doc.expectFirst(\"div\");\n\n        // Cached\n        Elements divChildren = div.children(); // 0 child elements, 1 node\n        int origCount = divChildren.size();\n\n        // Modify child\n        TextNode text = (TextNode) div.childNode(0);\n        Element p = doc.createElement(\"p\");\n        text.replaceWith(p);\n        p.appendChild(text);\n\n        int reported = div.childrenSize(); // invalidated ^^\n        long actualSize = div.childNodes().stream().filter(node -> node instanceof Element).count();\n\n        assertEquals(0, origCount);\n        assertEquals(1, actualSize);\n        assertEquals(1, reported); // was 0 via cache\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/EntitiesTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\n\nimport static org.jsoup.nodes.Document.OutputSettings;\nimport static org.jsoup.nodes.Entities.EscapeMode.*;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class EntitiesTest {\n    @Test public void escape() {\n        // escape is maximal (as in the escapes cover use in both text and attributes; vs Element.html() which checks if attribute or text and minimises escapes\n        String text = \"Hello &<> Å å π 新 there ¾ © » ' \\\"\";\n        String escapedAscii = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(base));\n        String escapedAsciiFull = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(extended));\n        String escapedAsciiXhtml = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(xhtml));\n        String escapedUtfFull = Entities.escape(text, new OutputSettings().charset(\"UTF-8\").escapeMode(extended));\n        String escapedUtfMin = Entities.escape(text, new OutputSettings().charset(\"UTF-8\").escapeMode(xhtml));\n\n        assertEquals(\"Hello &amp;&lt;&gt; &Aring; &aring; &#x3c0; &#x65b0; there &frac34; &copy; &raquo; &apos; &quot;\", escapedAscii);\n        assertEquals(\"Hello &amp;&lt;&gt; &angst; &aring; &pi; &#x65b0; there &frac34; &copy; &raquo; &apos; &quot;\", escapedAsciiFull);\n        assertEquals(\"Hello &amp;&lt;&gt; &#xc5; &#xe5; &#x3c0; &#x65b0; there &#xbe; &#xa9; &#xbb; &#x27; &quot;\", escapedAsciiXhtml);\n        assertEquals(\"Hello &amp;&lt;&gt; Å å π 新 there ¾ © » &apos; &quot;\", escapedUtfFull);\n        assertEquals(\"Hello &amp;&lt;&gt; Å å π 新 there ¾ © » &#x27; &quot;\", escapedUtfMin);\n        // odd that it's defined as aring in base but angst in full\n\n        // round trip\n        assertEquals(text, Entities.unescape(escapedAscii));\n        assertEquals(text, Entities.unescape(escapedAsciiFull));\n        assertEquals(text, Entities.unescape(escapedAsciiXhtml));\n        assertEquals(text, Entities.unescape(escapedUtfFull));\n        assertEquals(text, Entities.unescape(escapedUtfMin));\n    }\n\n    @Test public void escapeDefaults() {\n        String text = \"Hello &<> Å å π 新 there ¾ © » ' \\\"\";\n        String escaped = Entities.escape(text);\n        assertEquals(\"Hello &amp;&lt;&gt; Å å π 新 there ¾ © » &apos; &quot;\", escaped);\n    }\n\n    @Test public void escapedSupplementary() {\n        String text = \"\\uD835\\uDD59\";\n        String escapedAscii = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(base));\n        assertEquals(\"&#x1d559;\", escapedAscii);\n        String escapedAsciiFull = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(extended));\n        assertEquals(\"&hopf;\", escapedAsciiFull);\n        String escapedUtf= Entities.escape(text, new OutputSettings().charset(\"UTF-8\").escapeMode(extended));\n        assertEquals(text, escapedUtf);\n    }\n\n    @Test public void unescapeMultiChars() {\n        String text = \"&NestedGreaterGreater; &nGg; &nGt; &nGtv; &Gt; &gg;\"; // gg is not combo, but 8811 could conflict with NestedGreaterGreater or others\n        String un = \"≫ ⋙̸ ≫⃒ ≫̸ ≫ ≫\";\n        assertEquals(un, Entities.unescape(text));\n        String escaped = Entities.escape(un, new OutputSettings().charset(\"ascii\").escapeMode(extended));\n        assertEquals(\"&Gt; &Gg;&#x338; &Gt;&#x20d2; &Gt;&#x338; &Gt; &Gt;\", escaped);\n        assertEquals(un, Entities.unescape(escaped));\n    }\n\n    @Test public void xhtml() {\n        assertEquals(38, xhtml.codepointForName(\"amp\"));\n        assertEquals(62, xhtml.codepointForName(\"gt\"));\n        assertEquals(60, xhtml.codepointForName(\"lt\"));\n        assertEquals(34, xhtml.codepointForName(\"quot\"));\n\n        assertEquals(\"amp\", xhtml.nameForCodepoint(38));\n        assertEquals(\"gt\", xhtml.nameForCodepoint(62));\n        assertEquals(\"lt\", xhtml.nameForCodepoint(60));\n        assertEquals(\"quot\", xhtml.nameForCodepoint(34));\n    }\n\n    @Test public void getByName() {\n        assertEquals(\"≫⃒\", Entities.getByName(\"nGt\"));\n        assertEquals(\"fj\", Entities.getByName(\"fjlig\"));\n        assertEquals(\"≫\", Entities.getByName(\"gg\"));\n        assertEquals(\"©\", Entities.getByName(\"copy\"));\n    }\n\n    @Test public void escapeSupplementaryCharacter() {\n        String text = new String(Character.toChars(135361));\n        String escapedAscii = Entities.escape(text, new OutputSettings().charset(\"ascii\").escapeMode(base));\n        assertEquals(\"&#x210c1;\", escapedAscii);\n        String escapedUtf = Entities.escape(text, new OutputSettings().charset(\"UTF-8\").escapeMode(base));\n        assertEquals(text, escapedUtf);\n    }\n\n    @Test public void notMissingMultis() {\n        String text = \"&nparsl;\";\n        String un = \"\\u2AFD\\u20E5\";\n        assertEquals(un, Entities.unescape(text));\n    }\n\n    @Test public void notMissingSupplementals() {\n        String text = \"&npolint; &qfr;\";\n        String un = \"⨔ \\uD835\\uDD2E\"; // 𝔮\n        assertEquals(un, Entities.unescape(text));\n    }\n\n    @Test public void unescape() {\n        String text = \"Hello &AElig; &amp;&LT&gt; &reg &angst; &angst &#960; &#960 &#x65B0; there &! &frac34; &copy; &COPY;\";\n        assertEquals(\"Hello Æ &<> ® Å &angst π π 新 there &! ¾ © ©\", Entities.unescape(text));\n\n        assertEquals(\"&0987654321; &unknown\", Entities.unescape(\"&0987654321; &unknown\"));\n    }\n\n    @Test public void strictUnescape() { // for attributes, enforce strict unescaping (must look like &#xxx; , not just &#xxx)\n        String text = \"Hello &amp= &amp;\";\n        assertEquals(\"Hello &amp= &\", Entities.unescape(text, true));\n        assertEquals(\"Hello &= &\", Entities.unescape(text));\n        assertEquals(\"Hello &= &\", Entities.unescape(text, false));\n    }\n\n    @Test public void prefixMatch() {\n        // https://github.com/jhy/jsoup/issues/2207\n        // example from https://html.spec.whatwg.org/multipage/parsing.html#character-reference-state\n        String text = \"I'm &notit; I tell you. I'm &notin; I tell you.\";\n        assertEquals(\"I'm ¬it; I tell you. I'm ∉ I tell you.\", Entities.unescape(text, false));\n        assertEquals(\"I'm &notit; I tell you. I'm ∉ I tell you.\", Entities.unescape(text, true)); // not for attributes\n    }\n\n    @Test public void caseSensitive() {\n        String unescaped = \"Ü ü & &\";\n        assertEquals(\"&Uuml; &uuml; &amp; &amp;\",\n                Entities.escape(unescaped, new OutputSettings().charset(\"ascii\").escapeMode(extended)));\n\n        String escaped = \"&Uuml; &uuml; &amp; &AMP\";\n        assertEquals(\"Ü ü & &\", Entities.unescape(escaped));\n    }\n\n    @Test public void quoteReplacements() {\n        String escaped = \"&#92; &#36;\";\n        String unescaped = \"\\\\ $\";\n\n        assertEquals(unescaped, Entities.unescape(escaped));\n    }\n\n    @Test public void letterDigitEntities() {\n        String html = \"<p>&sup1;&sup2;&sup3;&frac14;&frac12;&frac34;</p>\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().charset(\"ascii\");\n        Element p = doc.select(\"p\").first();\n        assertEquals(\"&sup1;&sup2;&sup3;&frac14;&frac12;&frac34;\", p.html());\n        assertEquals(\"¹²³¼½¾\", p.text());\n        doc.outputSettings().charset(\"UTF-8\");\n        assertEquals(\"¹²³¼½¾\", p.html());\n    }\n\n    @Test public void noSpuriousDecodes() {\n        String string = \"http://www.foo.com?a=1&num_rooms=1&children=0&int=VA&b=2\";\n        assertEquals(string, Entities.unescape(string));\n    }\n\n    @Test public void alwaysEscapeLtAndGtInAttributeValues() {\n        // https://github.com/jhy/jsoup/issues/2337\n\n        String docHtml = \"<a title='<p>One</p>'>One</a>\";\n        Document doc = Jsoup.parse(docHtml);\n        Element element = doc.select(\"a\").first();\n\n        doc.outputSettings().escapeMode(base);\n        assertEquals(\"<a title=\\\"&lt;p&gt;One&lt;/p&gt;\\\">One</a>\", element.outerHtml());\n\n        doc.outputSettings().escapeMode(xhtml);\n        assertEquals(\"<a title=\\\"&lt;p&gt;One&lt;/p&gt;\\\">One</a>\", element.outerHtml());\n    }\n\n    @Test public void controlCharactersAreEscaped() {\n        // https://github.com/jhy/jsoup/issues/1556\n        // escape in HTML for legibility; remove from xml\n        String input = \"<a foo=\\\"&#x1b;esc&#x7;bell\\\">Text &#x1b; &#x7;</a>\";\n        Document doc = Jsoup.parse(input);\n        assertEquals(input, doc.body().html());\n\n        Document xml = Jsoup.parse(input, \"\", Parser.xmlParser());\n        assertEquals(\"<a foo=\\\"escbell\\\">Text  </a>\", xml.html());\n    }\n    \n    @Test public void escapeByClonedOutputSettings() {\n        OutputSettings outputSettings = new OutputSettings();\n        String text = \"Hello &<> Å å π 新 there ¾ © »\";\n        OutputSettings clone1 = outputSettings.clone();\n        OutputSettings clone2 = outputSettings.clone();\n\n        String escaped1 = assertDoesNotThrow(() -> Entities.escape(text, clone1));\n        String escaped2 = assertDoesNotThrow(() -> Entities.escape(text, clone2));\n        assertEquals(escaped1, escaped2);\n    }\n\n    @Test void parseHtmlEncodedEmojiMultipoint() {\n        String emoji = Parser.unescapeEntities(\"&#55357;&#56495;\", false); // 💯\n        assertEquals(\"\\uD83D\\uDCAF\", emoji);\n    }\n\n    @Test void parseHtmlEncodedEmoji() {\n        String emoji = Parser.unescapeEntities(\"&#128175;\", false); // 💯\n        assertEquals(\"\\uD83D\\uDCAF\", emoji);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/FormElementTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.integration.TestServer;\nimport org.jsoup.integration.servlets.CookieServlet;\nimport org.jsoup.integration.servlets.EchoServlet;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.SelectorTest;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for FormElement\n *\n * @author Jonathan Hedley\n */\npublic class FormElementTest {\n    @BeforeAll\n    public static void setUp() {\n        TestServer.start();\n    }\n\n    @Test public void hasAssociatedControls() {\n        //\"button\", \"fieldset\", \"input\", \"keygen\", \"object\", \"output\", \"select\", \"textarea\"\n        String html = \"<form id=1><button id=1><fieldset id=2 /><input id=3><keygen id=4><object id=5><output id=6>\" +\n                \"<select id=7><option></select><textarea id=8><p id=9>\";\n        Document doc = Jsoup.parse(html);\n\n        FormElement form = (FormElement) doc.select(\"form\").first();\n        assertEquals(8, form.elements().size());\n    }\n\n    @Test public void createsFormData() {\n        String html = \"<form><input name='one' value='two'><select name='three'><option value='not'>\" +\n                \"<option value='four' selected><option value='five' selected><textarea name=six>seven</textarea>\" +\n                \"<input name='seven' type='radio' value='on' checked><input name='seven' type='radio' value='off'>\" +\n                \"<input name='eight' type='checkbox' checked><input name='nine' type='checkbox' value='unset'>\" +\n                \"<input name='ten' value='text' disabled>\" +\n                \"<input name='eleven' value='text' type='button'>\" +\n                \"<input name='twelve' value='text' type='image'>\" +\n                \"</form>\";\n        Document doc = Jsoup.parse(html);\n        FormElement form = (FormElement) doc.select(\"form\").first();\n        List<Connection.KeyVal> data = form.formData();\n\n        assertEquals(6, data.size());\n        assertEquals(\"one=two\", data.get(0).toString());\n        assertEquals(\"three=four\", data.get(1).toString());\n        assertEquals(\"three=five\", data.get(2).toString());\n        assertEquals(\"six=seven\", data.get(3).toString());\n        assertEquals(\"seven=on\", data.get(4).toString()); // set\n        assertEquals(\"eight=on\", data.get(5).toString()); // default\n        // nine should not appear, not checked checkbox\n        // ten should not appear, disabled\n        // eleven should not appear, button\n    }\n\n    @Test public void formDataUsesFirstAttribute() {\n        String html = \"<form><input name=test value=foo name=test2 value=bar>\";\n        Document doc = Jsoup.parse(html);\n        FormElement form = (FormElement) doc.selectFirst(\"form\");\n        assertEquals(\"test=foo\", form.formData().get(0).toString());\n    }\n\n    @Test public void createsSubmitableConnection() {\n        String html = \"<form action='/search'><input name='q'></form>\";\n        Document doc = Jsoup.parse(html, \"http://example.com/\");\n        doc.select(\"[name=q]\").attr(\"value\", \"jsoup\");\n\n        FormElement form = ((FormElement) doc.select(\"form\").first());\n        Connection con = form.submit();\n\n        assertEquals(Connection.Method.GET, con.request().method());\n        assertEquals(\"http://example.com/search\", con.request().url().toExternalForm());\n        List<Connection.KeyVal> dataList = (List<Connection.KeyVal>) con.request().data();\n        assertEquals(\"q=jsoup\", dataList.get(0).toString());\n\n        doc.select(\"form\").attr(\"method\", \"post\");\n        Connection con2 = form.submit();\n        assertEquals(Connection.Method.POST, con2.request().method());\n    }\n\n    @Test public void actionWithNoValue() {\n        String html = \"<form><input name='q'></form>\";\n        Document doc = Jsoup.parse(html, \"http://example.com/\");\n        FormElement form = ((FormElement) doc.select(\"form\").first());\n        Connection con = form.submit();\n\n        assertEquals(\"http://example.com/\", con.request().url().toExternalForm());\n    }\n\n    @Test public void actionWithNoBaseUri() {\n        String html = \"<form><input name='q'></form>\";\n        Document doc = Jsoup.parse(html);\n        FormElement form = ((FormElement) doc.select(\"form\").first());\n\n\n        boolean threw = false;\n        try {\n            form.submit();\n        } catch (IllegalArgumentException e) {\n            threw = true;\n            assertEquals(\"Could not determine a form action URL for submit. Ensure you set a base URI when parsing.\",\n                    e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test public void formsAddedAfterParseAreFormElements() {\n        Document doc = Jsoup.parse(\"<body />\");\n        doc.body().html(\"<form action='http://example.com/search'><input name='q' value='search'>\");\n        Element formEl = doc.select(\"form\").first();\n        assertTrue(formEl instanceof FormElement);\n\n        FormElement form = (FormElement) formEl;\n        assertEquals(1, form.elements().size());\n    }\n\n    @Test public void controlsAddedAfterParseAreLinkedWithForms() {\n        Document doc = Jsoup.parse(\"<body />\");\n        doc.body().html(\"<form />\");\n\n        Element formEl = doc.select(\"form\").first();\n        formEl.append(\"<input name=foo value=bar>\");\n\n        assertTrue(formEl instanceof FormElement);\n        FormElement form = (FormElement) formEl;\n        assertEquals(1, form.elements().size());\n\n        List<Connection.KeyVal> data = form.formData();\n        assertEquals(\"foo=bar\", data.get(0).toString());\n    }\n\n    @Test public void usesOnForCheckboxValueIfNoValueSet() {\n        Document doc = Jsoup.parse(\"<form><input type=checkbox checked name=foo></form>\");\n        FormElement form = (FormElement) doc.select(\"form\").first();\n        List<Connection.KeyVal> data = form.formData();\n        assertEquals(\"on\", data.get(0).value());\n        assertEquals(\"foo\", data.get(0).key());\n    }\n\n    @Test public void adoptedFormsRetainInputs() {\n        // test for https://github.com/jhy/jsoup/issues/249\n        String html = \"<html>\\n\" +\n                \"<body>  \\n\" +\n                \"  <table>\\n\" +\n                \"      <form action=\\\"/hello.php\\\" method=\\\"post\\\">\\n\" +\n                \"      <tr><td>User:</td><td> <input type=\\\"text\\\" name=\\\"user\\\" /></td></tr>\\n\" +\n                \"      <tr><td>Password:</td><td> <input type=\\\"password\\\" name=\\\"pass\\\" /></td></tr>\\n\" +\n                \"      <tr><td><input type=\\\"submit\\\" name=\\\"login\\\" value=\\\"login\\\" /></td></tr>\\n\" +\n                \"   </form>\\n\" +\n                \"  </table>\\n\" +\n                \"</body>\\n\" +\n                \"</html>\";\n        Document doc = Jsoup.parse(html);\n        FormElement form = (FormElement) doc.select(\"form\").first();\n        List<Connection.KeyVal> data = form.formData();\n        assertEquals(3, data.size());\n        assertEquals(\"user\", data.get(0).key());\n        assertEquals(\"pass\", data.get(1).key());\n        assertEquals(\"login\", data.get(2).key());\n    }\n\n    @Test public void removeFormElement() {\n        String html = \"<html>\\n\" +\n                \"  <body> \\n\" +\n                \"      <form action=\\\"/hello.php\\\" method=\\\"post\\\">\\n\" +\n                \"      User:<input type=\\\"text\\\" name=\\\"user\\\" />\\n\" +\n                \"      Password:<input type=\\\"password\\\" name=\\\"pass\\\" />\\n\" +\n                \"      <input type=\\\"submit\\\" name=\\\"login\\\" value=\\\"login\\\" />\\n\" +\n                \"   </form>\\n\" +\n                \"  </body>\\n\" +\n                \"</html>  \";\n        Document doc = Jsoup.parse(html);\n        FormElement form = (FormElement) doc.selectFirst(\"form\");\n        Element pass = form.selectFirst(\"input[name=pass]\");\n        pass.remove();\n\n        List<Connection.KeyVal> data = form.formData();\n        assertEquals(2, data.size());\n        assertEquals(\"user\", data.get(0).key());\n        assertEquals(\"login\", data.get(1).key());\n        assertNull(doc.selectFirst(\"input[name=pass]\"));\n    }\n\n    @Test public void formSubmissionCarriesCookiesFromSession() throws IOException {\n        String echoUrl = EchoServlet.Url; // this is a dirty hack to initialize the EchoServlet(!)\n        Document cookieDoc = Jsoup.connect(CookieServlet.Url)\n            .data(CookieServlet.SetCookiesParam, \"1\")\n            .get();\n        Document formDoc = cookieDoc.connection().newRequest() // carries cookies from above set\n            .url(FileServlet.urlTo(\"/htmltests/upload-form.html\"))\n            .get();\n        FormElement form = formDoc.select(\"form\").forms().get(0);\n        Document echo = form.submit().post();\n\n        assertEquals(echoUrl, echo.location());\n        Elements els = echo.select(\"th:contains(Cookie: One)\");\n        // ensure that the cookies are there and in path-specific order (two with same name)\n        assertEquals(\"EchoServlet\", els.get(0).nextElementSibling().text());\n        assertEquals(\"Root\", els.get(1).nextElementSibling().text());\n\n        // make sure that the session following kept unique requests\n        assertTrue(cookieDoc.connection().response().url().toExternalForm().contains(\"CookieServlet\"));\n        assertTrue(formDoc.connection().response().url().toExternalForm().contains(\"upload-form\"));\n        assertTrue(echo.connection().response().url().toExternalForm().contains(\"EchoServlet\"));\n    }\n\n    @Test void formElementsAreLive() {\n        final String html = \"<html><body><form><div id=d1><input id=foo name=foo value=none></div><input id=bar name=bar value=one></form></body></html>\";\n        final Document doc = Jsoup.parse(html);\n        doc.select(\"#d1\").remove();\n        final FormElement form = (FormElement) doc.selectFirst(\"form\");\n        form.appendElement(\"input\").attr(\"id\", \"baz\").attr(\"name\", \"baz\").attr(\"value\", \"two\");\n        SelectorTest.assertSelectedIds(form.elements(), \"bar\", \"baz\");\n\n        List<Connection.KeyVal> keyVals = form.formData();\n        assertEquals(\"one\", keyVals.get(0).value());\n        assertEquals(\"two\", keyVals.get(1).value());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/LeafNodeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.NodeFilter;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class LeafNodeTest {\n\n    @Test\n    public void doesNotGetAttributesTooEasily() {\n        // test to make sure we're not setting attributes on all nodes right away\n        String body = \"<p>One <!-- Two --> Three<![CDATA[Four]]></p>\";\n        Document doc = Jsoup.parse(body, \"https://example.com/\");\n        assertTrue(hasAnyAttributes(doc)); // should have one - the base uri on the doc\n\n        assertFalse(hasAnyAttributes(Jsoup.parse(\"<div>None</div>\"))); // no base uri\n\n        Element html = doc.child(0);\n        assertFalse(hasAnyAttributes(html));\n\n        String s = doc.outerHtml();\n        assertFalse(hasAnyAttributes(html));\n\n        Elements els = doc.select(\"p\");\n        Element p = els.first();\n        assertEquals(1, els.size());\n        assertFalse(hasAnyAttributes(html));\n\n        els = doc.select(\"p.none\");\n        assertFalse(hasAnyAttributes(html));\n\n        String id = p.id();\n        assertEquals(\"\", id);\n        assertFalse(p.hasClass(\"Foobs\"));\n        assertFalse(hasAnyAttributes(html));\n\n        p.addClass(\"Foobs\");\n        assertTrue(p.hasClass(\"Foobs\"));\n        assertTrue(hasAnyAttributes(html));\n        assertTrue(hasAnyAttributes(p));\n\n        Attributes attributes = p.attributes();\n        assertTrue(attributes.hasKey(\"class\"));\n        p.clearAttributes();\n        assertFalse(hasAnyAttributes(p));\n        assertFalse(hasAnyAttributes(html));\n        assertFalse(attributes.hasKey(\"class\"));\n    }\n\n    private boolean hasAnyAttributes(Node node) {\n        final boolean[] found = new boolean[1];\n        node.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                if (node.hasAttributes()) {\n                    found[0] = true;\n                    return FilterResult.STOP;\n                } else {\n                    return FilterResult.CONTINUE;\n                }\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                return FilterResult.CONTINUE;\n            }\n        });\n        return found[0];\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/NodeIteratorTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class NodeIteratorTest {\n    String html = \"<div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div>\";\n\n    @Test void canIterateNodes() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        assertIterates(it, \"#root;html;head;body;div#1;p;One;p;Two;div#2;p;Three;p;Four;\");\n        assertFalse(it.hasNext());\n\n        boolean threw = false;\n        try {\n            it.next();\n        } catch (NoSuchElementException e) {\n            threw = true;\n        }\n        assertTrue(threw);\n    }\n\n    @Test void hasNextIsPure() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        assertTrue(it.hasNext());\n        assertTrue(it.hasNext());\n        assertIterates(it, \"#root;html;head;body;div#1;p;One;p;Two;div#2;p;Three;p;Four;\");\n        assertFalse(it.hasNext());\n    }\n\n    @Test void iterateSubTree() {\n        Document doc = Jsoup.parse(html);\n\n        Element div1 = doc.expectFirst(\"div#1\");\n        NodeIterator<Node> it = NodeIterator.from(div1);\n        assertIterates(it, \"div#1;p;One;p;Two;\");\n        assertFalse(it.hasNext());\n\n        Element div2 = doc.expectFirst(\"div#2\");\n        NodeIterator<Node> it2 = NodeIterator.from(div2);\n        assertIterates(it2, \"div#2;p;Three;p;Four;\");\n        assertFalse(it2.hasNext());\n    }\n\n    @Test void canRestart() {\n        Document doc = Jsoup.parse(html);\n\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        assertIterates(it, \"#root;html;head;body;div#1;p;One;p;Two;div#2;p;Three;p;Four;\");\n\n        it.restart(doc.expectFirst(\"div#2\"));\n        assertIterates(it, \"div#2;p;Three;p;Four;\");\n    }\n\n    @Test void canIterateJustOneSibling() {\n        Document doc = Jsoup.parse(html);\n        Element p2 = doc.expectFirst(\"p:contains(Two)\");\n        assertEquals(\"Two\", p2.text());\n\n        NodeIterator<Node> it = NodeIterator.from(p2);\n        assertIterates(it, \"p;Two;\");\n\n        NodeIterator<Element> elIt = new NodeIterator<>(p2, Element.class);\n        Element found = elIt.next();\n        assertSame(p2, found);\n        assertFalse(elIt.hasNext());\n    }\n\n    @Test void canIterateFirstEmptySibling() {\n        Document doc = Jsoup.parse(\"<div><p id=1></p><p id=2>.</p><p id=3>..</p>\");\n        Element p1 = doc.expectFirst(\"p#1\");\n        assertEquals(\"\", p1.ownText());\n\n        NodeIterator<Node> it = NodeIterator.from(p1);\n        assertTrue(it.hasNext());\n        Node node = it.next();\n        assertSame(p1, node);\n        assertFalse(it.hasNext());\n    }\n\n    @Test void canRemoveViaIterator() {\n        String html = \"<div id=out1><div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div></div><div id=out2>Out2\";\n        Document doc = Jsoup.parse(html);\n\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            if (node.attr(\"id\").equals(\"1\"))\n                it.remove();\n            trackSeen(node, seen);\n        }\n        assertEquals(\"#root;html;head;body;div#out1;div#1;div#2;p;Three;p;Four;div#out2;Out2;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#out1;div#2;p;Three;p;Four;div#out2;Out2;\");\n\n        it = NodeIterator.from(doc);\n        seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            if (node.attr(\"id\").equals(\"2\"))\n                it.remove();\n            trackSeen(node, seen);\n        }\n        assertEquals(\"#root;html;head;body;div#out1;div#2;div#out2;Out2;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#out1;div#out2;Out2;\");\n    }\n\n    @Test void canRemoveViaNode() {\n        String html = \"<div id=out1><div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div></div><div id=out2>Out2\";\n        Document doc = Jsoup.parse(html);\n\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            if (node.attr(\"id\").equals(\"1\"))\n                node.remove();\n            trackSeen(node, seen);\n        }\n        assertEquals(\"#root;html;head;body;div#out1;div#1;div#2;p;Three;p;Four;div#out2;Out2;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#out1;div#2;p;Three;p;Four;div#out2;Out2;\");\n\n        it = NodeIterator.from(doc);\n        seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            if (node.attr(\"id\").equals(\"2\"))\n                node.remove();\n            trackSeen(node, seen);\n        }\n        assertEquals(\"#root;html;head;body;div#out1;div#2;div#out2;Out2;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#out1;div#out2;Out2;\");\n    }\n\n    @Test void canReplace() {\n        String html = \"<div id=out1><div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div></div><div id=out2>Out2\";\n        Document doc = Jsoup.parse(html);\n\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            trackSeen(node, seen);\n            if (node.attr(\"id\").equals(\"1\")) {\n                node.replaceWith(new Element(\"span\").text(\"Foo\"));\n            }\n        }\n        assertEquals(\"#root;html;head;body;div#out1;div#1;span;Foo;div#2;p;Three;p;Four;div#out2;Out2;\", seen.toString());\n        // ^^ we don't see <p>One, do see the replaced in <span>, and the subsequent nodes\n        assertContents(doc, \"#root;html;head;body;div#out1;span;Foo;div#2;p;Three;p;Four;div#out2;Out2;\");\n\n        it = NodeIterator.from(doc);\n        seen = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            trackSeen(node, seen);\n            if (node.attr(\"id\").equals(\"2\")) {\n                node.replaceWith(new Element(\"span\").text(\"Bar\"));\n            }\n        }\n        assertEquals(\"#root;html;head;body;div#out1;span;Foo;div#2;span;Bar;div#out2;Out2;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#out1;span;Foo;span;Bar;div#out2;Out2;\");\n    }\n\n    @Test void canWrap() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<Node> it = NodeIterator.from(doc);\n        boolean sawInner = false;\n        while (it.hasNext()) {\n            Node node = it.next();\n            if (node.attr(\"id\").equals(\"1\")) {\n                node.wrap(\"<div id=outer>\");\n            }\n            if (node instanceof TextNode && ((TextNode) node).text().equals(\"One\"))\n                sawInner = true;\n        }\n        assertContents(doc, \"#root;html;head;body;div#outer;div#1;p;One;p;Two;div#2;p;Three;p;Four;\");\n        assertTrue(sawInner);\n    }\n\n    @Test void canFilterForElements() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<Element> it = new NodeIterator<>(doc, Element.class);\n\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            Element el = it.next();\n            assertNotNull(el);\n            trackSeen(el, seen);\n        }\n\n        assertEquals(\"#root;html;head;body;div#1;p;p;div#2;p;p;\", seen.toString());\n    }\n\n    @Test void canFilterForTextNodes() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<TextNode> it = new NodeIterator<>(doc, TextNode.class);\n\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            TextNode text = it.next();\n            assertNotNull(text);\n            trackSeen(text, seen);\n        }\n\n        assertEquals(\"One;Two;Three;Four;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#1;p;One;p;Two;div#2;p;Three;p;Four;\");\n    }\n\n    @Test void canModifyFilteredElements() {\n        Document doc = Jsoup.parse(html);\n        NodeIterator<Element> it = new NodeIterator<>(doc, Element.class);\n\n        StringBuilder seen = new StringBuilder();\n        while (it.hasNext()) {\n            Element el = it.next();\n            if (!el.ownText().isEmpty())\n                el.text(el.ownText() + \"++\");\n            trackSeen(el, seen);\n        }\n\n        assertEquals(\"#root;html;head;body;div#1;p;p;div#2;p;p;\", seen.toString());\n        assertContents(doc, \"#root;html;head;body;div#1;p;One++;p;Two++;div#2;p;Three++;p;Four++;\");\n    }\n\n    static <T extends Node> void assertIterates(Iterator<T> it, String expected) {\n        Node previous = null;\n        StringBuilder actual = new StringBuilder();\n        while (it.hasNext()) {\n            Node node = it.next();\n            assertNotNull(node);\n            assertNotSame(previous, node);\n\n            trackSeen(node, actual);\n            previous = node;\n        }\n        assertEquals(expected, actual.toString());\n    }\n\n    static void assertContents(Element el, String expected) {\n        NodeIterator<Node> it = NodeIterator.from(el);\n        assertIterates(it, expected);\n    }\n\n    public static void trackSeen(Node node, StringBuilder actual) {\n        if (node instanceof Element) {\n            Element el = (Element) node;\n            actual.append(el.tagName());\n            if (el.hasAttr(\"id\"))\n                actual.append(\"#\").append(el.id());\n        }\n        else if (node instanceof TextNode)\n            actual.append(((TextNode) node).text());\n        else\n            actual.append(node.nodeName());\n        actual.append(\";\");\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/NodeStreamTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.nodes.NodeIteratorTest.trackSeen;\nimport static org.jsoup.nodes.NodeIteratorTest.assertContents;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class NodeStreamTest {\n\n    String html = \"<div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div>\";\n\n\n    @Test void canStream() {\n        Document doc = Jsoup.parse(html);\n        StringBuilder seen = new StringBuilder();\n        Stream<Node> stream = doc.nodeStream();\n        stream.forEachOrdered(node -> trackSeen(node, seen));\n        assertEquals(\"#root;html;head;body;div#1;p;One;p;Two;div#2;p;Three;p;Four;\", seen.toString());\n    }\n\n    @Test void canStreamParallel() {\n        Document doc = Jsoup.parse(html);\n        long count = doc.nodeStream().parallel().count();\n        assertEquals(14, count);\n    }\n\n    @Test void canFindFirst() {\n        Document doc = Jsoup.parse(html);\n        Optional<Node> first = doc.nodeStream().findFirst();\n        assertTrue(first.isPresent());\n        assertSame(doc, first.get());\n    }\n\n    @Test void canFilter() {\n        Document doc = Jsoup.parse(html);\n        StringBuilder seen = new StringBuilder();\n\n        doc.nodeStream()\n            .filter(node -> node instanceof TextNode)\n            .forEach(node -> trackSeen(node, seen));\n\n        assertEquals(\"One;Two;Three;Four;\", seen.toString());\n    }\n\n    @Test void canRemove() {\n        String html = \"<div id=1><p>One<p>Two</div><div id=2><p>Three<p>Four</div><div id=3><p>Five\";\n        Document doc = Jsoup.parse(html);\n\n        doc.nodeStream()\n            .filter(node -> node instanceof Element)\n                .filter(node -> node.attr(\"id\").equals(\"1\") || node.attr(\"id\").equals(\"2\"))\n                    .forEach(Node::remove);\n\n        assertContents(doc, \"#root;html;head;body;div#3;p;Five;\");\n    }\n\n    @Test void elementStream() {\n        Document doc = Jsoup.parse(html);\n        StringBuilder seen = new StringBuilder();\n        Stream<Element> stream = doc.stream();\n        stream.forEachOrdered(node -> trackSeen(node, seen));\n        assertEquals(\"#root;html;head;body;div#1;p;p;div#2;p;p;\", seen.toString());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/NodeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.select.Elements;\nimport org.jsoup.select.NodeVisitor;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.jsoup.parser.Parser.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests Nodes\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class NodeTest {\n    @Test public void handlesBaseUri() {\n        Tag tag = Tag.valueOf(\"a\");\n        Attributes attribs = new Attributes();\n        attribs.put(\"relHref\", \"/foo\");\n        attribs.put(\"absHref\", \"http://bar/qux\");\n\n        Element noBase = new Element(tag, \"\", attribs);\n        assertEquals(\"\", noBase.absUrl(\"relHref\")); // with no base, should NOT fallback to href attrib, whatever it is\n        assertEquals(\"http://bar/qux\", noBase.absUrl(\"absHref\")); // no base but valid attrib, return attrib\n\n        Element withBase = new Element(tag, \"http://foo/\", attribs);\n        assertEquals(\"http://foo/foo\", withBase.absUrl(\"relHref\")); // construct abs from base + rel\n        assertEquals(\"http://bar/qux\", withBase.absUrl(\"absHref\")); // href is abs, so returns that\n        assertEquals(\"\", withBase.absUrl(\"noval\"));\n\n        Element dodgyBase = new Element(tag, \"wtf://no-such-protocol/\", attribs);\n        assertEquals(\"http://bar/qux\", dodgyBase.absUrl(\"absHref\")); // base fails, but href good, so get that\n        assertEquals(\"\", dodgyBase.absUrl(\"relHref\")); // base fails, only rel href, so return nothing\n    }\n\n    @Test public void setBaseUriIsRecursive() {\n        Document doc = Jsoup.parse(\"<div><p></p></div>\");\n        String baseUri = \"https://jsoup.org\";\n        doc.setBaseUri(baseUri);\n\n        assertEquals(baseUri, doc.baseUri());\n        assertEquals(baseUri, doc.select(\"div\").first().baseUri());\n        assertEquals(baseUri, doc.select(\"p\").first().baseUri());\n    }\n\n    @Test public void handlesAbsPrefix() {\n        Document doc = Jsoup.parse(\"<a href=/foo>Hello</a>\", \"https://jsoup.org/\");\n        Element a = doc.select(\"a\").first();\n        assertEquals(\"/foo\", a.attr(\"href\"));\n        assertEquals(\"https://jsoup.org/foo\", a.attr(\"abs:href\"));\n        assertTrue(a.hasAttr(\"abs:href\"));\n    }\n\n    @Test public void handlesAbsOnImage() {\n        Document doc = Jsoup.parse(\"<p><img src=\\\"/rez/osi_logo.png\\\" /></p>\", \"https://jsoup.org/\");\n        Element img = doc.select(\"img\").first();\n        assertEquals(\"https://jsoup.org/rez/osi_logo.png\", img.attr(\"abs:src\"));\n        assertEquals(img.absUrl(\"src\"), img.attr(\"abs:src\"));\n    }\n\n    @Test public void handlesAbsPrefixOnHasAttr() {\n        // 1: no abs url; 2: has abs url\n        Document doc = Jsoup.parse(\"<a id=1 href='/foo'>One</a> <a id=2 href='https://jsoup.org/'>Two</a>\");\n        Element one = doc.select(\"#1\").first();\n        Element two = doc.select(\"#2\").first();\n\n        assertFalse(one.hasAttr(\"abs:href\"));\n        assertTrue(one.hasAttr(\"href\"));\n        assertEquals(\"\", one.absUrl(\"href\"));\n\n        assertTrue(two.hasAttr(\"abs:href\"));\n        assertTrue(two.hasAttr(\"href\"));\n        assertEquals(\"https://jsoup.org/\", two.absUrl(\"href\"));\n    }\n\n    @Test public void literalAbsPrefix() {\n        // if there is a literal attribute \"abs:xxx\", don't try and make absolute.\n        Document doc = Jsoup.parse(\"<a abs:href='odd'>One</a>\");\n        Element el = doc.select(\"a\").first();\n        assertTrue(el.hasAttr(\"abs:href\"));\n        assertEquals(\"odd\", el.attr(\"abs:href\"));\n    }\n\n    @Test public void handleAbsOnFileUris() {\n        Document doc = Jsoup.parse(\"<a href='password'>One/a><a href='/var/log/messages'>Two</a>\", \"file:/etc/\");\n        Element one = doc.select(\"a\").first();\n        assertEquals(\"file:/etc/password\", one.absUrl(\"href\"));\n        Element two = doc.select(\"a\").get(1);\n        assertEquals(\"file:/var/log/messages\", two.absUrl(\"href\"));\n    }\n\n    @Test\n    public void handleAbsOnLocalhostFileUris() {\n        Document doc = Jsoup.parse(\"<a href='password'>One/a><a href='/var/log/messages'>Two</a>\", \"file://localhost/etc/\");\n        Element one = doc.select(\"a\").first();\n        assertEquals(\"file://localhost/etc/password\", one.absUrl(\"href\"));\n    }\n\n    @Test\n    public void handlesAbsOnProtocolessAbsoluteUris() {\n        Document doc1 = Jsoup.parse(\"<a href='//example.net/foo'>One</a>\", \"http://example.com/\");\n        Document doc2 = Jsoup.parse(\"<a href='//example.net/foo'>One</a>\", \"https://example.com/\");\n\n        Element one = doc1.select(\"a\").first();\n        Element two = doc2.select(\"a\").first();\n\n        assertEquals(\"http://example.net/foo\", one.absUrl(\"href\"));\n        assertEquals(\"https://example.net/foo\", two.absUrl(\"href\"));\n\n        Document doc3 = Jsoup.parse(\"<img src=//www.google.com/images/errors/logo_sm.gif alt=Google>\", \"https://google.com\");\n        assertEquals(\"https://www.google.com/images/errors/logo_sm.gif\", doc3.select(\"img\").attr(\"abs:src\"));\n    }\n\n    /*\n    Test for an issue with Java's abs URL handler.\n     */\n    @Test public void absHandlesRelativeQuery() {\n        Document doc = Jsoup.parse(\"<a href='?foo'>One</a> <a href='bar.html?foo'>Two</a>\", \"https://jsoup.org/path/file?bar\");\n\n        Element a1 = doc.select(\"a\").first();\n        assertEquals(\"https://jsoup.org/path/file?foo\", a1.absUrl(\"href\"));\n\n        Element a2 = doc.select(\"a\").get(1);\n        assertEquals(\"https://jsoup.org/path/bar.html?foo\", a2.absUrl(\"href\"));\n    }\n\n    @Test public void absHandlesDotFromIndex() {\n        Document doc = Jsoup.parse(\"<a href='./one/two.html'>One</a>\", \"http://example.com\");\n        Element a1 = doc.select(\"a\").first();\n        assertEquals(\"http://example.com/one/two.html\", a1.absUrl(\"href\"));\n    }\n\n    @Test public void handlesAbsOnUnknownProtocols() {\n        // https://github.com/jhy/jsoup/issues/1610\n        // URL would throw on unknown protocol tel: as no stream handler is registered\n\n        String[] urls = {\"mailto:example@example.com\", \"tel:867-5309\"}; // mail has a handler, tel doesn't\n        for (String url : urls) {\n            Attributes attr = new Attributes().put(\"href\", url);\n            Element noBase = new Element(Tag.valueOf(\"a\"), null, attr);\n            assertEquals(url, noBase.absUrl(\"href\"));\n\n            Element withBase = new Element(Tag.valueOf(\"a\"), \"http://example.com/\", attr);\n            assertEquals(url, withBase.absUrl(\"href\"));\n        }\n    }\n\n    @Test public void testRemove() {\n        Document doc = Jsoup.parse(\"<p>One <span>two</span> three</p>\");\n        Element p = doc.select(\"p\").first();\n        p.childNode(0).remove();\n\n        assertEquals(\"two three\", p.text());\n        assertEquals(\"<span>two</span> three\", TextUtil.stripNewlines(p.html()));\n    }\n\n    @Test void removeOnOrphanIsNoop() {\n        // https://github.com/jhy/jsoup/issues/1898\n        Element node = new Element(\"div\");\n        assertNull(node.parentNode());\n        node.remove();\n        assertNull(node.parentNode());\n    }\n\n    @Test public void testReplace() {\n        Document doc = Jsoup.parse(\"<p>One <span>two</span> three</p>\");\n        Element p = doc.select(\"p\").first();\n        Element insert = doc.createElement(\"em\").text(\"foo\");\n        p.childNode(1).replaceWith(insert);\n\n        assertEquals(\"One <em>foo</em> three\", p.html());\n    }\n\n    @Test public void testReplaceTwice() {\n        // https://github.com/jhy/jsoup/issues/2212\n        Document doc = Jsoup.parse(\"<p><span>Child One</span><span>Child Two</span><span>Child Three</span><span>Child Four</span></p>\");\n        Elements children = doc.select(\"p\").first().children();\n        // first swap 0 and 1\n        children.set(0,  children.set(1, children.get(0)));\n        // then swap 1 and 2\n        children.set(2, children.set(1, children.get(2)));\n\n        assertEquals(\"Child TwoChild ThreeChild OneChild Four\",\n                TextUtil.stripNewlines(children.html()));\n    }\n\n    @Test public void ownerDocument() {\n        Document doc = Jsoup.parse(\"<p>Hello\");\n        Element p = doc.select(\"p\").first();\n        assertSame(p.ownerDocument(), doc);\n        assertSame(doc.ownerDocument(), doc);\n        assertNull(doc.parent());\n    }\n\n    @Test public void root() {\n        Document doc = Jsoup.parse(\"<div><p>Hello\");\n        Element p = doc.select(\"p\").first();\n        Node root = p.root();\n        assertSame(doc, root);\n        assertNull(root.parent());\n        assertSame(doc.root(), doc);\n        assertSame(doc.root(), doc.ownerDocument());\n\n        Element standAlone = new Element(Tag.valueOf(\"p\"), \"\");\n        assertNull(standAlone.parent());\n        assertSame(standAlone.root(), standAlone);\n        assertNull(standAlone.ownerDocument());\n    }\n\n    @Test public void before() {\n        Document doc = Jsoup.parse(\"<p>One <b>two</b> three</p>\");\n        Element newNode = new Element(Tag.valueOf(\"em\"), \"\");\n        newNode.appendText(\"four\");\n\n        doc.select(\"b\").first().before(newNode);\n        assertEquals(\"<p>One <em>four</em><b>two</b> three</p>\", doc.body().html());\n\n        doc.select(\"b\").first().before(\"<i>five</i>\");\n        assertEquals(\"<p>One <em>four</em><i>five</i><b>two</b> three</p>\", doc.body().html());\n    }\n\n    @Test void beforeShuffle() {\n        // https://github.com/jhy/jsoup/issues/1898\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div>\");\n        Element div = doc.select(\"div\").get(0);\n        Elements ps = doc.select(\"p\");\n        Element p1 = ps.get(0);\n        Element p2 = ps.get(1);\n        Element p3 = ps.get(2);\n\n        p2.before(p1);\n        p3.before(p2);\n        // ^ should be no-ops, they are already before\n        assertEquals(\"One Two Three\", div.text());\n\n        p2.before(p1);\n        p1.before(p3);\n        assertEquals(\"Three One Two\", div.text());\n    }\n\n    @Test public void after() {\n        Document doc = Jsoup.parse(\"<p>One <b>two</b> three</p>\");\n        Element newNode = new Element(Tag.valueOf(\"em\"), \"\");\n        newNode.appendText(\"four\");\n\n        doc.select(\"b\").first().after(newNode);\n        assertEquals(\"<p>One <b>two</b><em>four</em> three</p>\", doc.body().html());\n\n        doc.select(\"b\").first().after(\"<i>five</i>\");\n        assertEquals(\"<p>One <b>two</b><i>five</i><em>four</em> three</p>\", doc.body().html());\n    }\n\n    @Test void afterShuffle() {\n        // https://github.com/jhy/jsoup/issues/1898\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div>\");\n        Element div = doc.select(\"div\").get(0);\n        Elements ps = doc.select(\"p\");\n        Element p1 = ps.get(0);\n        Element p2 = ps.get(1);\n        Element p3 = ps.get(2);\n\n        p1.after(p2);\n        p2.after(p3);\n        // ^ should be no-ops, they are already before\n        assertEquals(\"One Two Three\", div.text());\n\n        p3.after(p1);\n        p1.after(p2);\n        assertEquals(\"Three One Two\", div.text());\n    }\n\n    @Test public void unwrap() {\n        Document doc = Jsoup.parse(\"<div>One <span>Two <b>Three</b></span> Four</div>\");\n        Element span = doc.select(\"span\").first();\n        Node twoText = span.childNode(0);\n        Node node = span.unwrap();\n\n        assertEquals(\"<div>One Two <b>Three</b> Four</div>\", TextUtil.stripNewlines(doc.body().html()));\n        assertTrue(node instanceof TextNode);\n        assertEquals(\"Two \", ((TextNode) node).text());\n        assertEquals(node, twoText);\n        assertEquals(node.parent(), doc.select(\"div\").first());\n    }\n\n    @Test public void unwrapNoChildren() {\n        Document doc = Jsoup.parse(\"<div>One <span></span> Two</div>\");\n        Element span = doc.select(\"span\").first();\n        Node node = span.unwrap();\n        assertEquals(\"<div>One Two</div>\", TextUtil.stripNewlines(doc.body().html()));\n        assertNull(node);\n    }\n\n    @Test public void traverse() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        doc.select(\"div\").first().traverse(new NodeVisitor() {\n            @Override\n            public void head(Node node, int depth) {\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n            }\n        });\n        assertEquals(\"<div><p><#text></#text></p></div>\", accum.toString());\n    }\n\n    @Test public void forEachNode() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div><div id=1>Gone<p></div>\");\n        doc.forEachNode(node -> {\n            if (node instanceof TextNode) {\n                TextNode textNode = (TextNode) node;\n                if (textNode.text().equals(\"There\")) {\n                    textNode.text(\"There Now\");\n                    textNode.after(\"<p>Another\");\n                }\n            } else if (node.attr(\"id\").equals(\"1\"))\n                node.remove();\n        });\n        assertEquals(\"<div><p>Hello</p></div><div>There Now<p>Another</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void orphanNodeReturnsNullForSiblingElements() {\n        Node node = new Element(Tag.valueOf(\"p\"), \"\");\n        Element el = new Element(Tag.valueOf(\"p\"), \"\");\n\n        assertEquals(0, node.siblingIndex());\n        assertEquals(0, node.siblingNodes().size());\n\n        assertNull(node.previousSibling());\n        assertNull(node.nextSibling());\n\n        assertEquals(0, el.siblingElements().size());\n        assertNull(el.previousElementSibling());\n        assertNull(el.nextElementSibling());\n    }\n\n    @Test public void nodeIsNotASiblingOfItself() {\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three</div>\");\n        Element p2 = doc.select(\"p\").get(1);\n\n        assertEquals(\"Two\", p2.text());\n        List<Node> nodes = p2.siblingNodes();\n        assertEquals(2, nodes.size());\n        assertEquals(\"<p>One</p>\", nodes.get(0).outerHtml());\n        assertEquals(\"<p>Three</p>\", nodes.get(1).outerHtml());\n    }\n\n    @Test public void childNodesCopy() {\n        Document doc = Jsoup.parse(\"<div id=1>Text 1 <p>One</p> Text 2 <p>Two<p>Three</div><div id=2>\");\n        Element div1 = doc.select(\"#1\").first();\n        Element div2 = doc.select(\"#2\").first();\n        List<Node> divChildren = div1.childNodesCopy();\n        assertEquals(5, divChildren.size());\n        TextNode tn1 = (TextNode) div1.childNode(0);\n        TextNode tn2 = (TextNode) divChildren.get(0);\n        tn2.text(\"Text 1 updated\");\n        assertEquals(\"Text 1 \", tn1.text());\n        div2.insertChildren(-1, divChildren);\n        assertEquals(\"<div id=\\\"1\\\">Text 1<p>One</p>Text 2<p>Two</p><p>Three</p></div><div id=\\\"2\\\">Text 1 updated<p>One</p>Text 2<p>Two</p><p>Three</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void supportsClone() {\n        Document doc = org.jsoup.Jsoup.parse(\"<div class=foo>Text</div>\");\n        Element el = doc.select(\"div\").first();\n        assertTrue(el.hasClass(\"foo\"));\n\n        Element elClone = doc.clone().select(\"div\").first();\n        assertTrue(elClone.hasClass(\"foo\"));\n        assertEquals(\"Text\", elClone.text());\n\n        el.removeClass(\"foo\");\n        el.text(\"None\");\n        assertFalse(el.hasClass(\"foo\"));\n        assertTrue(elClone.hasClass(\"foo\"));\n        assertEquals(\"None\", el.text());\n        assertEquals(\"Text\", elClone.text());\n    }\n\n    @Test public void changingAttributeValueShouldReplaceExistingAttributeCaseInsensitive() {\n        Document document = Jsoup.parse(\"<INPUT id=\\\"foo\\\" NAME=\\\"foo\\\" VALUE=\\\"\\\">\");\n        Element inputElement = document.select(\"#foo\").first();\n\n        inputElement.attr(\"value\",\"bar\");\n\n        assertEquals(singletonAttributes(), getAttributesCaseInsensitive(inputElement));\n    }\n\n    private Attributes getAttributesCaseInsensitive(Element element) {\n        Attributes matches = new Attributes();\n        for (Attribute attribute : element.attributes()) {\n            if (attribute.getKey().equalsIgnoreCase(\"value\")) {\n                matches.put(attribute);\n            }\n        }\n        return matches;\n    }\n\n    private Attributes singletonAttributes() {\n        Attributes attributes = new Attributes();\n        attributes.put(\"value\", \"bar\");\n        return attributes;\n    }\n\n    @Test void clonedNodesHaveOwnerDocsAndIndependentSettings() {\n        // https://github.com/jhy/jsoup/issues/763\n        Document doc = Jsoup.parse(\"<div>Text</div><div>Two</div>\");\n        doc.outputSettings().prettyPrint(false);\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        TextNode text = (TextNode) div.childNode(0);\n        assertNotNull(text);\n\n        TextNode textClone = text.clone();\n        Document docClone = textClone.ownerDocument();\n        assertNotNull(docClone);\n        assertFalse(docClone.outputSettings().prettyPrint());\n        assertNotSame(doc, docClone);\n\n        doc.outputSettings().prettyPrint(true);\n        assertTrue(doc.outputSettings().prettyPrint());\n        assertFalse(docClone.outputSettings().prettyPrint());\n        assertEquals(1, docClone.childNodes().size()); // check did not get the second div as the owner's children\n        assertEquals(textClone, docClone.childNode(0)); // note not the head or the body -- not normalized\n    }\n\n    @Test\n    void firstAndLastChild() {\n        String html = \"<div>One <span>Two</span> <a href></a> Three</div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.selectFirst(\"div\");\n        Element a = doc.selectFirst(\"a\");\n        assertNotNull(div);\n        assertNotNull(a);\n\n        // nodes\n        TextNode first = (TextNode) div.firstChild();\n        assertEquals(\"One \", first.text());\n\n        TextNode last = (TextNode) div.lastChild();\n        assertEquals(\" Three\", last.text());\n\n        assertNull(a.firstChild());\n        assertNull(a.lastChild());\n\n        // elements\n        Element firstEl = div.firstElementChild();\n        assertEquals(\"span\", firstEl.tagName());\n\n        Element lastEl = div.lastElementChild();\n        assertEquals(\"a\", lastEl.tagName());\n\n        assertNull(a.firstElementChild());\n        assertNull(a.lastElementChild());\n\n        assertNull(firstEl.firstElementChild());\n        assertNull(firstEl.lastElementChild());\n    }\n\n    @Test void firstLastSibling() {\n        Document doc = Jsoup.parse(\"<div><span>One</span> Two <span>Three</span>\");\n        Elements spans = doc.select(\"span\");\n        TextNode text = (TextNode) spans.first().nextSibling();\n        assertSame(spans.get(0), text.firstSibling());\n        assertSame(spans.get(1), text.lastSibling());\n    }\n\n    @Test void nodeName() {\n        Element div = new Element(\"DIV\");\n        assertEquals(\"DIV\", div.tagName());\n        assertEquals(\"DIV\", div.nodeName());\n        assertEquals(\"div\", div.normalName());\n        assertTrue(div.nameIs(\"div\"));\n\n        TextNode text = new TextNode(\"Some Text\");\n        assertEquals(\"#text\", text.nodeName());\n        assertEquals(\"#text\", text.normalName());\n    }\n\n    @Test void elementIs() {\n        String html = \"<div><p>One</p>\";\n        Document doc = Jsoup.parse(html);\n\n        Element p = doc.expectFirst(\"p\");\n        TextNode text = (TextNode) p.childNode(0);\n\n        assertTrue(text.parentElementIs(\"p\", NamespaceHtml));\n        assertFalse(text.parentElementIs(\"div\", NamespaceHtml));\n        assertFalse(text.parentElementIs(\"p\", NamespaceXml));\n\n        assertTrue(p.parentElementIs(\"div\", NamespaceHtml));\n        assertTrue(p.elementIs(\"p\", NamespaceHtml));\n        assertTrue(p.nameIs(\"p\"));\n        assertFalse(p.nameIs(\"P\"));\n    }\n\n    @Test void svgElementIs() {\n        String html = \"<div><svg><path>1,2,3</path></svg></div>\";\n        Document doc = Jsoup.parse(html);\n\n        Element svg = doc.expectFirst(\"svg\");\n        assertTrue(svg.nameIs(\"svg\"));\n        assertFalse(svg.elementIs(\"svg\", NamespaceHtml));\n        assertTrue(svg.elementIs(\"svg\", NamespaceSvg));\n\n        TextNode data = (TextNode) svg.childNode(0).childNode(0);\n        assertTrue(data.parentElementIs(\"path\", NamespaceSvg));\n        assertTrue(data.parentNameIs(\"path\"));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/PrinterTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static org.jsoup.integration.ParseTest.getFile;\nimport static org.jsoup.integration.ParseTest.getFileAsString;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Test for the HTML / XML serialization of elements, including the Pretty Printer, Outline mode, and the base.\n */\npublic class PrinterTest {\n    @Test void pretty() throws IOException {\n        // parse /printertests/input-1.html, check formatted same as pretty-1.html\n        File in = getFile(\"/printertests/input-1.html\");\n        Document doc = Jsoup.parse(in);\n\n        String expected = getFileAsString(getFile(\"/printertests/pretty-1.html\"));\n        String html = doc.html();\n        assertEquals(expected, html);\n        assertEquals(html, doc.outerHtml());\n    }\n\n    @Test void passthru() throws IOException {\n        // disable pretty, should be almost 1:1 of input (other than a couple parse normalizations; doctype, pre)\n        File in = getFile(\"/printertests/input-1.html\");\n        Document doc = Jsoup.parse(in);\n        doc.outputSettings().prettyPrint(false);\n\n        String expected = getFileAsString(getFile(\"/printertests/passthru-1.html\"));\n        String html = doc.html();\n        assertEquals(expected, html);\n        assertEquals(html, doc.outerHtml());\n    }\n\n    @Test void outline() throws IOException {\n        // outline mode, most everything gets indented\n        File in = getFile(\"/printertests/input-1.html\");\n        Document doc = Jsoup.parse(in);\n        doc.outputSettings().outline(true);\n\n        String expected = getFileAsString(getFile(\"/printertests/outline-1.html\"));\n        String html = doc.html();\n        assertEquals(expected, html);\n        assertEquals(html, doc.outerHtml());\n    }\n\n    @Test void sequentialTextNodesDontCollapse() {\n        // tests that the pretty printer does not collapse (trim leading | trailing whitespace) when there are\n        // sequential textnodes. That doesn't happen in a parse, but can when manipulated.\n        Document doc = Jsoup.parse(\"<div><div></div>Hello</div>\"); // needs to be text that would indent\n        Element div = doc.expectFirst(\"div\");\n        TextNode hello = (TextNode) div.childNode(1);\n        hello.after(\" there.\");\n        assertEquals(\"Hello\", hello.getWholeText());\n        assertEquals(\" there.\", ((TextNode) hello.nextSibling()).getWholeText());\n\n        assertEquals(\"Hello there.\", div.text());\n        assertEquals(\"<div></div>\\nHello there.\", div.html());\n        assertEquals(\"<div>\\n <div></div>\\n Hello there.\\n</div>\", div.outerHtml());\n    }\n\n    @Test void sequentialTextNodesCollapseAdjacentWhitespace() {\n        // https://github.com/jhy/jsoup/pull/2349\n        // Tests that the pretty printer collapses whitespace between sequential text nodes into a single space.\n        // This must also work with intermediate empty and blank text nodes.\n        Document doc = Jsoup.parseBodyFragment(\"Before <span> </span> After\");\n        doc.expectFirst(\"span\")\n                .after(new TextNode(\"\")).after(new TextNode(\"\")).after(new TextNode(\" \")).after(new TextNode(\"\"))\n                .remove();\n        assertEquals(6, doc.body().textNodes().size()); // no collapse before printing\n        assertEquals(\"Before After\", doc.body().html());\n    }\n\n    @Test void dontCollapseTextAfterNonElements() {\n        Document doc = Jsoup.parse(\"<div><div></div>Hello <!-- -_- --> there</div>\");\n        Element body = doc.body();\n        assertEquals(\"Hello there\", body.text());\n        assertEquals(\"<div>\\n <div></div>\\n Hello <!-- -_- -->\\n  there\\n</div>\", body.html());\n    }\n\n    @Test void spaceAfterSpanInBlock() {\n        Document doc = Jsoup.parse(\"<div> <span>Span</span> \\n Text  <span>Follow</span></div> <p> <span>Span</span>  Text <span>Follow</span> </p>\");\n        Element body = doc.body();\n        assertEquals(\"Span Text Follow Span Text Follow\", body.text());\n        assertEquals(\"<div>\\n <span>Span</span> Text <span>Follow</span>\\n</div>\\n<p><span>Span</span> Text <span>Follow</span></p>\", body.html());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/nodes/TextNodeTest.java",
    "content": "package org.jsoup.nodes;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.helper.ValidationException;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.select.Nodes;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Test TextNodes\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class TextNodeTest {\n    @Test public void testBlank() {\n        TextNode one = new TextNode(\"\");\n        TextNode two = new TextNode(\"     \");\n        TextNode three = new TextNode(\"  \\n\\n   \");\n        TextNode four = new TextNode(\"Hello\");\n        TextNode five = new TextNode(\"  \\nHello \");\n\n        assertTrue(one.isBlank());\n        assertTrue(two.isBlank());\n        assertTrue(three.isBlank());\n        assertFalse(four.isBlank());\n        assertFalse(five.isBlank());\n    }\n\n    @Test public void testTextBean() {\n        Document doc = Jsoup.parse(\"<p>One <span>two &amp;</span> three &amp;</p>\");\n        Element p = doc.select(\"p\").first();\n\n        Element span = doc.select(\"span\").first();\n        assertEquals(\"two &\", span.text());\n        TextNode spanText = (TextNode) span.childNode(0);\n        assertEquals(\"two &\", spanText.text());\n\n        TextNode tn = (TextNode) p.childNode(2);\n        assertEquals(\" three &\", tn.text());\n\n        tn.text(\" POW!\");\n        assertEquals(\"One <span>two &amp;</span> POW!\", TextUtil.stripNewlines(p.html()));\n\n        tn.attr(tn.nodeName(), \"kablam &\");\n        assertEquals(\"kablam &\", tn.text());\n        assertEquals(\"One <span>two &amp;</span>kablam &amp;\", TextUtil.stripNewlines(p.html()));\n    }\n\n    @Test public void testSplitText() {\n        Document doc = Jsoup.parse(\"<div>Hello there</div>\");\n        Element div = doc.select(\"div\").first();\n        TextNode tn = (TextNode) div.childNode(0);\n        TextNode tail = tn.splitText(6);\n        assertEquals(\"Hello \", tn.getWholeText());\n        assertEquals(\"there\", tail.getWholeText());\n        tail.text(\"there!\");\n        assertEquals(\"Hello there!\", div.text());\n        assertSame(tn.parent(), tail.parent());\n    }\n\n    @Test public void testSplitAndEmbolden() {\n        Document doc = Jsoup.parse(\"<div>Hello there</div>\");\n        Element div = doc.select(\"div\").first();\n        TextNode tn = (TextNode) div.childNode(0);\n        TextNode tail = tn.splitText(6);\n        tail.wrap(\"<b></b>\");\n\n        assertEquals(\"Hello <b>there</b>\", div.html());\n    }\n\n    @Test void testSplitTextValidation() {\n        Document doc = Jsoup.parse(\"<div>Hello there</div>\");\n        Element div = doc.expectFirst(\"div\");\n        TextNode tn = (TextNode) div.childNode(0);\n        Throwable ex = assertThrows(ValidationException.class,\n            () -> tn.splitText(-5));\n        assertEquals(\"Split offset must be not be negative\", ex.getMessage());\n\n        ex = assertThrows(ValidationException.class,\n            () -> tn.splitText(500));\n        assertEquals(\"Split offset must not be greater than current text length\", ex.getMessage());\n    }\n\n    @Test public void testWithSupplementaryCharacter(){\n        Document doc = Jsoup.parse(new String(Character.toChars(135361)));\n        TextNode t = doc.body().textNodes().get(0);\n        assertEquals(new String(Character.toChars(135361)), t.outerHtml().trim());\n    }\n\n    @Test public void testLeadNodesHaveNoChildren() {\n        Document doc = Jsoup.parse(\"<div>Hello there</div>\");\n        Element div = doc.select(\"div\").first();\n        TextNode tn = (TextNode) div.childNode(0);\n        List<Node> nodes = tn.childNodes();\n        assertEquals(0, nodes.size());\n    }\n\n    @Test public void testSpaceNormalise() {\n        // https://github.com/jhy/jsoup/issues/1309\n        String whole = \"Two  spaces\";\n        String norm = \"Two spaces\";\n        TextNode tn = new TextNode(whole); // there are 2 spaces between the words\n        assertEquals(whole, tn.getWholeText());\n        assertEquals(norm, tn.text());\n        assertEquals(norm, tn.outerHtml());\n        assertEquals(norm, tn.toString());\n\n        Element el = new Element(\"p\");\n        el.appendChild(tn); // this used to change the context\n        //tn.setParentNode(el); // set any parent\n        assertEquals(whole, tn.getWholeText());\n        assertEquals(norm, tn.text());\n        assertEquals(norm, tn.outerHtml());\n        assertEquals(norm, tn.toString());\n\n        assertEquals(\"<p>\" + norm + \"</p>\", el.outerHtml());\n        assertEquals(norm, el.html());\n        assertEquals(whole, el.wholeText());\n    }\n\n    @Test\n    public void testClone() {\n        // https://github.com/jhy/jsoup/issues/1176\n        TextNode x = new TextNode(\"zzz\");\n        TextNode y = x.clone();\n\n        assertNotSame(x, y);\n        assertEquals(x.outerHtml(), y.outerHtml());\n\n        y.text(\"yyy\");\n        assertNotEquals(x.outerHtml(), y.outerHtml());\n        assertEquals(\"zzz\", x.text());\n\n        x.attributes(); // already cloned so no impact\n        y.text(\"xxx\");\n        assertEquals(\"zzz\", x.text());\n        assertEquals(\"xxx\", y.text());\n    }\n\n    @Test\n    public void testCloneAfterAttributesHit() {\n        // https://github.com/jhy/jsoup/issues/1176\n        TextNode x = new TextNode(\"zzz\");\n        x.attributes(); // moves content from leafnode value to attributes, which were missed in clone\n        TextNode y = x.clone();\n        y.text(\"xxx\");\n        assertEquals(\"zzz\", x.text());\n        assertEquals(\"xxx\", y.text());\n    }\n\n    @Test\n    public void testHasTextWhenIterating() {\n        // https://github.com/jhy/jsoup/issues/1170\n        Document doc = Jsoup.parse(\"<div>One <p>Two <p>Three\");\n        boolean foundFirst = false;\n        for (Element el : doc.getAllElements()) {\n            for (Node node : el.childNodes()) {\n                if (node instanceof TextNode) {\n                    TextNode textNode = (TextNode) node;\n                    assertFalse(StringUtil.isBlank(textNode.text()));\n                    if (!foundFirst) {\n                        foundFirst = true;\n                        assertEquals(\"One \", textNode.text());\n                        assertEquals(\"One \", textNode.getWholeText());\n                    }\n                }\n            }\n        }\n        assertTrue(foundFirst);\n    }\n\n    @Test void createFromEncoded() {\n        TextNode tn = TextNode.createFromEncoded(\"&lt;One&gt;\");\n        assertEquals(\"<One>\", tn.text());\n    }\n\n    @Test void normaliseWhitespace() {\n        assertEquals(\" One Two \", TextNode.normaliseWhitespace(\"  One \\n Two\\n\"));\n    }\n\n    @Test void stripLeadingWhitespace() {\n        assertEquals(\"One Two  \", TextNode.stripLeadingWhitespace(\"\\n One Two  \"));\n    }\n\n    // Lead Node tests\n    @Test void leafNodeAttributes() {\n        TextNode t = new TextNode(\"First\");\n\n        // will hit the !hasAttributes flow\n        t.attr(t.nodeName(), \"One\");\n        assertEquals(\"One\", t.attr(t.nodeName()));\n        assertFalse(t.hasAttributes());\n\n        Attributes attr = t.attributes();\n        assertEquals(1, attr.asList().size()); // vivifies 'One' as an attribute\n        assertEquals(\"One\", attr.get(t.nodeName()));\n        t.coreValue(\"Two\");\n        assertEquals(\"Two\", t.text());\n\n        // arbitrary attributes\n        assertFalse(t.hasAttr(\"foo\"));\n        t.attr(\"foo\", \"bar\");\n        assertTrue(t.hasAttr(\"foo\"));\n        t.removeAttr(\"foo\");\n        assertFalse(t.hasAttr(\"foo\"));\n\n        assertEquals(\"\", t.baseUri());\n        t.attr(\"href\", \"/foo.html\");\n        assertEquals(\"\", t.absUrl(\"href\")); // cannot abs\n\n        Element p = new Element(\"p\");\n        p.doSetBaseUri(\"https://example.com/\");\n        p.appendChild(t);\n        assertEquals(\"https://example.com/foo.html\", t.absUrl(\"href\"));\n\n        assertEquals(0, t.childNodeSize());\n        assertSame(t, t.empty());\n        assertEquals(0, t.ensureChildNodes().size());\n\n        TextNode clone = t.clone();\n        assertTrue(t.hasSameValue(clone));\n        assertEquals(\"/foo.html\", clone.attr(\"href\"));\n        assertEquals(\"Two\", clone.text());\n    }\n\n    @Test void parentElement() {\n        Document doc = Jsoup.parse(\"<p>Text</p>\");\n        TextNode text = doc.selectNodes(\"::text\", TextNode.class).first();\n        assertNotNull(text);\n        Element p = text.parent();\n        assertNotNull(p);\n        p = ((Node) text).parentElement();\n        assertNotNull(p);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/AttributeParseTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Test suite for attribute parser.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class AttributeParseTest {\n\n    @Test public void parsesRoughAttributeString() {\n        String html = \"<a id=\\\"123\\\" class=\\\"baz = 'bar'\\\" style = 'border: 2px'qux zim foo = 12 mux=18 />\";\n        // should be: <id=123>, <class=baz = 'bar'>, <qux=>, <zim=>, <foo=12>, <mux.=18>\n\n        Element el = Jsoup.parse(html).getElementsByTag(\"a\").get(0);\n        Attributes attr = el.attributes();\n        assertEquals(7, attr.size());\n        assertEquals(\"123\", attr.get(\"id\"));\n        assertEquals(\"baz = 'bar'\", attr.get(\"class\"));\n        assertEquals(\"border: 2px\", attr.get(\"style\"));\n        assertEquals(\"\", attr.get(\"qux\"));\n        assertEquals(\"\", attr.get(\"zim\"));\n        assertEquals(\"12\", attr.get(\"foo\"));\n        assertEquals(\"18\", attr.get(\"mux\"));\n    }\n\n    @Test public void handlesNewLinesAndReturns() {\n        String html = \"<a\\r\\nfoo='bar\\r\\nqux'\\r\\nbar\\r\\n=\\r\\ntwo>One</a>\";\n        Element el = Jsoup.parse(html).select(\"a\").first();\n        assertEquals(2, el.attributes().size());\n        assertEquals(\"bar\\r\\nqux\", el.attr(\"foo\")); // currently preserves newlines in quoted attributes. todo confirm if should.\n        assertEquals(\"two\", el.attr(\"bar\"));\n    }\n\n    @Test public void parsesEmptyString() {\n        String html = \"<a />\";\n        Element el = Jsoup.parse(html).getElementsByTag(\"a\").get(0);\n        Attributes attr = el.attributes();\n        assertEquals(0, attr.size());\n    }\n\n    @Test public void canStartWithEq() {\n        String html = \"<a =empty />\";\n        // TODO this is the weirdest thing in the spec - why not consider this an attribute with an empty name, not where name is '='?\n        // am I reading it wrong? https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state\n        Element el = Jsoup.parse(html).getElementsByTag(\"a\").get(0);\n        Attributes attr = el.attributes();\n        assertEquals(1, attr.size());\n        assertTrue(attr.hasKey(\"=empty\"));\n        assertEquals(\"\", attr.get(\"=empty\"));\n    }\n\n    @Test public void strictAttributeUnescapes() {\n        String html = \"<a id=1 href='?foo=bar&mid&lt=true'>One</a> <a id=2 href='?foo=bar&lt;qux&lg=1'>Two</a>\";\n        Elements els = Jsoup.parse(html).select(\"a\");\n        assertEquals(\"?foo=bar&mid&lt=true\", els.first().attr(\"href\"));\n        assertEquals(\"?foo=bar<qux&lg=1\", els.last().attr(\"href\"));\n    }\n\n    @Test public void moreAttributeUnescapes() {\n        String html = \"<a href='&wr_id=123&mid-size=true&ok=&wr'>Check</a>\";\n        Elements els = Jsoup.parse(html).select(\"a\");\n        assertEquals(\"&wr_id=123&mid-size=true&ok=&wr\", els.first().attr(\"href\"));\n    }\n\n    @Test public void parsesBooleanAttributes() {\n        String html = \"<a normal=\\\"123\\\" boolean empty=\\\"\\\"></a>\";\n        Element el = Jsoup.parse(html).select(\"a\").first();\n\n        assertEquals(\"123\", el.attr(\"normal\"));\n        assertEquals(\"\", el.attr(\"boolean\"));\n        assertEquals(\"\", el.attr(\"empty\"));\n\n        List<Attribute> attributes = el.attributes().asList();\n        assertEquals(3, attributes.size(), \"There should be 3 attribute present\");\n\n        assertEquals(html, el.outerHtml()); // vets boolean syntax\n    }\n\n    @Test public void dropsSlashFromAttributeName() {\n        String html = \"<img /onerror='doMyJob'/>\";\n        Document doc = Jsoup.parse(html);\n        assertFalse(doc.select(\"img[onerror]\").isEmpty(), \"SelfClosingStartTag ignores last character\");\n        assertEquals(\"<img onerror=\\\"doMyJob\\\">\", doc.body().html());\n\n        doc = Jsoup.parse(html, \"\", Parser.xmlParser());\n        assertEquals(\"<img onerror=\\\"doMyJob\\\" />\", doc.html());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/CharacterReaderTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.internal.StringUtil;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.io.UncheckedIOException;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Test suite for character reader.\n *\n * @author Jonathan Hedley, jonathan@hedley.net\n */\npublic class CharacterReaderTest {\n    public final static int maxBufferLen = CharacterReader.BufferSize;\n\n    @Test public void consume() {\n        CharacterReader r = new CharacterReader(\"one\");\n        assertEquals(0, r.pos());\n        assertEquals('o', r.current());\n        assertEquals('o', r.consume());\n        assertEquals(1, r.pos());\n        assertEquals('n', r.current());\n        assertEquals(1, r.pos());\n        assertEquals('n', r.consume());\n        assertEquals('e', r.consume());\n        assertTrue(r.isEmpty());\n        assertEquals(CharacterReader.EOF, r.consume());\n        assertTrue(r.isEmpty());\n        assertEquals(CharacterReader.EOF, r.consume());\n    }\n\n    @Test public void unconsume() {\n        CharacterReader r = new CharacterReader(\"one\");\n        assertEquals('o', r.consume());\n        assertEquals('n', r.current());\n        r.unconsume();\n        assertEquals('o', r.current());\n\n        assertEquals('o', r.consume());\n        assertEquals('n', r.consume());\n        assertEquals('e', r.consume());\n        assertTrue(r.isEmpty());\n        r.unconsume();\n        assertFalse(r.isEmpty());\n        assertEquals('e', r.current());\n        assertEquals('e', r.consume());\n        assertTrue(r.isEmpty());\n\n        assertEquals(CharacterReader.EOF, r.consume());\n        r.unconsume(); // read past, so have to eat again\n        assertTrue(r.isEmpty());\n        r.unconsume();\n        assertFalse(r.isEmpty());\n\n        assertEquals('e', r.consume());\n        assertTrue(r.isEmpty());\n\n        assertEquals(CharacterReader.EOF, r.consume());\n        assertTrue(r.isEmpty());\n\n        // unconsume all remaining characters\n        for (int i = 0; i < 4; i++) {\n            r.unconsume();\n        }\n        assertThrows(UncheckedIOException.class, r::unconsume);\n    }\n\n    @Test public void mark() {\n        CharacterReader r = new CharacterReader(\"one\");\n        r.consume();\n        r.mark();\n        assertEquals(1, r.pos());\n        assertEquals('n', r.consume());\n        assertEquals('e', r.consume());\n        assertTrue(r.isEmpty());\n        r.rewindToMark();\n        assertEquals(1, r.pos());\n        assertEquals('n', r.consume());\n        assertFalse(r.isEmpty());\n        assertEquals(2, r.pos());\n    }\n\n    @Test public void rewindToMark() {\n        CharacterReader r = new CharacterReader(\"nothing\");\n        // marking should be invalid\n        assertThrows(UncheckedIOException.class, r::rewindToMark);\n    }\n\n    @Test public void consumeToEnd() {\n        String in = \"one two three\";\n        CharacterReader r = new CharacterReader(in);\n        String toEnd = r.consumeToEnd();\n        assertEquals(in, toEnd);\n        assertTrue(r.isEmpty());\n    }\n\n    @Test public void nextIndexOfChar() {\n        String in = \"blah blah\";\n        CharacterReader r = new CharacterReader(in);\n\n        assertEquals(-1, r.nextIndexOf('x'));\n        assertEquals(3, r.nextIndexOf('h'));\n        String pull = r.consumeTo('h');\n        assertEquals(\"bla\", pull);\n        r.consume();\n        assertEquals(2, r.nextIndexOf('l'));\n        assertEquals(\" blah\", r.consumeToEnd());\n        assertEquals(-1, r.nextIndexOf('x'));\n    }\n\n    @Test public void nextIndexOfString() {\n        String in = \"One Two something Two Three Four\";\n        CharacterReader r = new CharacterReader(in);\n\n        assertEquals(-1, r.nextIndexOf(\"Foo\"));\n        assertEquals(4, r.nextIndexOf(\"Two\"));\n        assertEquals(\"One Two \", r.consumeTo(\"something\"));\n        assertEquals(10, r.nextIndexOf(\"Two\"));\n        assertEquals(\"something Two Three Four\", r.consumeToEnd());\n        assertEquals(-1, r.nextIndexOf(\"Two\"));\n    }\n\n    @Test public void nextIndexOfUnmatched() {\n        CharacterReader r = new CharacterReader(\"<[[one]]\");\n        assertEquals(-1, r.nextIndexOf(\"]]>\"));\n    }\n\n    @Test public void consumeToChar() {\n        CharacterReader r = new CharacterReader(\"One Two Three\");\n        assertEquals(\"One \", r.consumeTo('T'));\n        assertEquals(\"\", r.consumeTo('T')); // on Two\n        assertEquals('T', r.consume());\n        assertEquals(\"wo \", r.consumeTo('T'));\n        assertEquals('T', r.consume());\n        assertEquals(\"hree\", r.consumeTo('T')); // consume to end\n    }\n\n    @Test public void consumeToString() {\n        CharacterReader r = new CharacterReader(\"One Two Two Four\");\n        assertEquals(\"One \", r.consumeTo(\"Two\"));\n        assertEquals('T', r.consume());\n        assertEquals(\"wo \", r.consumeTo(\"Two\"));\n        assertEquals('T', r.consume());\n        // To handle strings straddling across buffers, consumeTo() may return the\n        // data in multiple pieces near EOF.\n        StringBuilder builder = new StringBuilder();\n        String part;\n        do {\n            part = r.consumeTo(\"Qux\");\n            builder.append(part);\n        } while (!part.isEmpty());\n        assertEquals(\"wo Four\", builder.toString());\n    }\n\n    @Test public void advance() {\n        CharacterReader r = new CharacterReader(\"One Two Three\");\n        assertEquals('O', r.consume());\n        r.advance();\n        assertEquals('e', r.consume());\n    }\n\n    @Test public void consumeToAny() {\n        CharacterReader r = new CharacterReader(\"One &bar; qux\");\n        assertEquals(\"One \", r.consumeToAny('&', ';'));\n        assertTrue(r.matches('&'));\n        assertTrue(r.matches(\"&bar;\"));\n        assertEquals('&', r.consume());\n        assertEquals(\"bar\", r.consumeToAny('&', ';'));\n        assertEquals(';', r.consume());\n        assertEquals(\" qux\", r.consumeToAny('&', ';'));\n    }\n\n    @Test public void consumeLetterSequence() {\n        CharacterReader r = new CharacterReader(\"One &bar; qux\");\n        assertEquals(\"One\", r.consumeLetterSequence());\n        assertEquals(\" &\", r.consumeTo(\"bar;\"));\n        assertEquals(\"bar\", r.consumeLetterSequence());\n        assertEquals(\"; qux\", r.consumeToEnd());\n    }\n\n    @Test public void consumeLetterThenDigitSequence() {\n        CharacterReader r = new CharacterReader(\"One12 Two &bar; qux\");\n        assertEquals(\"One12\", r.consumeLetterThenDigitSequence());\n        assertEquals(' ', r.consume());\n        assertEquals(\"Two\", r.consumeLetterThenDigitSequence());\n        assertEquals(\" &bar; qux\", r.consumeToEnd());\n    }\n\n    @Test public void matches() {\n        CharacterReader r = new CharacterReader(\"One Two Three\");\n        assertTrue(r.matches('O'));\n        assertTrue(r.matches(\"One Two Three\"));\n        assertTrue(r.matches(\"One\"));\n        assertFalse(r.matches(\"one\"));\n        assertEquals('O', r.consume());\n        assertFalse(r.matches(\"One\"));\n        assertTrue(r.matches(\"ne Two Three\"));\n        assertFalse(r.matches(\"ne Two Three Four\"));\n        assertEquals(\"ne Two Three\", r.consumeToEnd());\n        assertFalse(r.matches(\"ne\"));\n        assertTrue(r.isEmpty());\n    }\n\n    @Test\n    public void matchesIgnoreCase() {\n        CharacterReader r = new CharacterReader(\"One Two Three\");\n        assertTrue(r.matchesIgnoreCase(\"O\"));\n        assertTrue(r.matchesIgnoreCase(\"o\"));\n        assertTrue(r.matches('O'));\n        assertFalse(r.matches('o'));\n        assertTrue(r.matchesIgnoreCase(\"One Two Three\"));\n        assertTrue(r.matchesIgnoreCase(\"ONE two THREE\"));\n        assertTrue(r.matchesIgnoreCase(\"One\"));\n        assertTrue(r.matchesIgnoreCase(\"one\"));\n        assertEquals('O', r.consume());\n        assertFalse(r.matchesIgnoreCase(\"One\"));\n        assertTrue(r.matchesIgnoreCase(\"NE Two Three\"));\n        assertFalse(r.matchesIgnoreCase(\"ne Two Three Four\"));\n        assertEquals(\"ne Two Three\", r.consumeToEnd());\n        assertFalse(r.matchesIgnoreCase(\"ne\"));\n    }\n\n    @Test public void containsIgnoreCase() {\n        CharacterReader r = new CharacterReader(\"One TWO three\");\n        assertTrue(r.containsIgnoreCase(\"two\"));\n        assertTrue(r.containsIgnoreCase(\"three\"));\n        // weird one: does not find one, because it scans for consistent case only\n        assertFalse(r.containsIgnoreCase(\"one\"));\n    }\n\n    @Test void containsIgnoreCaseBuffer() {\n        String html = \"<p><p><p></title><p></TITLE><p>\" + BufferBuster(\"Foo Bar Qux \") + \"<foo><bar></title>\";\n        CharacterReader r = new CharacterReader(html);\n\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n        assertFalse(r.containsIgnoreCase(\"</not>\"));\n        assertFalse(r.containsIgnoreCase(\"</not>\")); // cached, but we only test functionally here\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n        r.consumeTo(\"</title>\");\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n        r.consumeTo(\"<p>\");\n        assertTrue(r.matches(\"<p>\"));\n\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n        assertFalse(r.containsIgnoreCase(\"</not>\"));\n        assertFalse(r.containsIgnoreCase(\"</not>\"));\n\n        r.consumeTo(\"</TITLE>\");\n        r.consumeTo(\"<p>\");\n        assertTrue(r.matches(\"<p>\"));\n        assertFalse(r.containsIgnoreCase(\"</title>\")); // because we haven't buffered up yet, we don't know\n        r.consumeTo(\"<foo>\");\n        assertFalse(r.matches(\"<foo>\")); // buffer underrun\n        r.consumeTo(\"<foo>\");\n        assertTrue(r.matches(\"<foo>\")); // cross the buffer\n        assertTrue(r.containsIgnoreCase(\"</TITLE>\"));\n        assertTrue(r.containsIgnoreCase(\"</title>\"));\n    }\n\n    static String BufferBuster(String content) {\n        StringBuilder builder = new StringBuilder();\n        while (builder.length() < maxBufferLen)\n            builder.append(content);\n        return builder.toString();\n    }\n\n    @Test public void matchesAny() {\n        char[] scan = {' ', '\\n', '\\t'};\n        CharacterReader r = new CharacterReader(\"One\\nTwo\\tThree\");\n        assertFalse(r.matchesAny(scan));\n        assertEquals(\"One\", r.consumeToAny(scan));\n        assertTrue(r.matchesAny(scan));\n        assertEquals('\\n', r.consume());\n        assertFalse(r.matchesAny(scan));\n        // nothing to match\n        r.consumeToEnd();\n        assertTrue(r.isEmpty());\n        assertFalse(r.matchesAny(scan));\n    }\n\n    @Test public void matchesDigit() {\n        CharacterReader r = new CharacterReader(\"42\");\n        r.consumeToEnd();\n        assertTrue(r.isEmpty());\n        // nothing to match\n        assertFalse(r.matchesDigit());\n        r.unconsume();\n        assertTrue(r.matchesDigit());\n    }\n\n    @Test public void cachesStrings() {\n        CharacterReader r = new CharacterReader(\"Check\\tCheck\\tCheck\\tCHOKE\\tA string that is longer than 16 chars\");\n        String one = r.consumeTo('\\t');\n        r.consume();\n        String two = r.consumeTo('\\t');\n        r.consume();\n        String three = r.consumeTo('\\t');\n        r.consume();\n        String four = r.consumeTo('\\t');\n        r.consume();\n        String five = r.consumeTo('\\t');\n\n        assertEquals(\"Check\", one);\n        assertEquals(\"Check\", two);\n        assertEquals(\"Check\", three);\n        assertEquals(\"CHOKE\", four);\n        assertSame(one, two);\n        assertSame(two, three);\n        assertNotSame(three, four);\n        assertNotSame(four, five);\n        assertEquals(five, \"A string that is longer than 16 chars\");\n    }\n\n    @Test\n    public void rangeEquals() {\n        CharacterReader r = new CharacterReader(\"Check\\tCheck\\tCheck\\tCHOKE\");\n        assertTrue(r.rangeEquals(0, 5, \"Check\"));\n        assertFalse(r.rangeEquals(0, 5, \"CHOKE\"));\n        assertFalse(r.rangeEquals(0, 5, \"Chec\"));\n\n        assertTrue(r.rangeEquals(6, 5, \"Check\"));\n        assertFalse(r.rangeEquals(6, 5, \"Chuck\"));\n\n        assertTrue(r.rangeEquals(12, 5, \"Check\"));\n        assertFalse(r.rangeEquals(12, 5, \"Cheeky\"));\n\n        assertTrue(r.rangeEquals(18, 5, \"CHOKE\"));\n        assertFalse(r.rangeEquals(18, 5, \"CHIKE\"));\n    }\n\n    @Test\n    public void empty() {\n        CharacterReader r = new CharacterReader(\"One\");\n        assertTrue(r.matchConsume(\"One\"));\n        assertTrue(r.isEmpty());\n\n        r = new CharacterReader(\"Two\");\n        String two = r.consumeToEnd();\n        assertEquals(\"Two\", two);\n    }\n\n    @Test\n    public void consumeToNonexistentEndWhenAtAnd() {\n        CharacterReader r = new CharacterReader(\"<!\");\n        assertTrue(r.matchConsume(\"<!\"));\n        assertTrue(r.isEmpty());\n\n        String after = r.consumeTo('>');\n        assertEquals(\"\", after);\n\n        assertTrue(r.isEmpty());\n    }\n\n    @Test\n    public void notEmptyAtBufferSplitPoint() {\n        int len = CharacterReader.BufferSize * 12;\n        StringBuilder builder = StringUtil.borrowBuilder();\n        while (builder.length() <= len) builder.append('!');\n        CharacterReader r = new CharacterReader(builder.toString());\n        StringUtil.releaseBuilder(builder);\n\n        // consume through\n        for (int pos = 0; pos < len; pos ++) {\n            assertEquals(pos, r.pos());\n            assertFalse(r.isEmpty());\n            assertEquals('!', r.consume());\n            assertEquals(pos + 1, r.pos());\n            assertFalse(r.isEmpty());\n        }\n        assertEquals('!', r.consume());\n        assertTrue(r.isEmpty());\n        assertEquals(CharacterReader.EOF, r.consume());\n    }\n\n    @Test public void bufferUp() {\n        String note = \"HelloThere\"; // + ! = 11 chars\n        int loopCount = 64;\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < loopCount; i++) {\n            sb.append(note);\n            sb.append(\"!\");\n        }\n\n        String s = sb.toString();\n        BufferedReader br = new BufferedReader(new StringReader(s));\n\n        CharacterReader r = new CharacterReader(br);\n        for (int i = 0; i < loopCount; i++) {\n            String pull = r.consumeTo('!');\n            assertEquals(note, pull);\n            assertEquals('!', r.current());\n            r.advance();\n        }\n\n        assertTrue(r.isEmpty());\n    }\n\n    @Test public void canEnableAndDisableLineNumberTracking() {\n        CharacterReader reader = new CharacterReader(\"Hello!\");\n        assertFalse(reader.isTrackNewlines());\n        reader.trackNewlines(true);\n        assertTrue(reader.isTrackNewlines());\n        reader.trackNewlines(false);\n        assertFalse(reader.isTrackNewlines());\n    }\n\n    @Test public void canTrackNewlines() {\n        StringBuilder builder = new StringBuilder();\n        builder.append(\"<foo>\\n<bar>\\n<qux>\\n\");\n        while (builder.length() < maxBufferLen)\n            builder.append(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\");\n        builder.append(\"[foo]\\n[bar]\");\n        String content = builder.toString();\n\n        CharacterReader noTrack = new CharacterReader(content);\n        assertFalse(noTrack.isTrackNewlines());\n        CharacterReader track = new CharacterReader(content);\n        track.trackNewlines(true);\n        assertTrue(track.isTrackNewlines());\n\n        // check that no tracking works as expected (pos is 0 indexed, line number stays at 1, col is pos+1)\n        assertEquals(0, noTrack.pos());\n        assertEquals(1, noTrack.lineNumber());\n        assertEquals(1, noTrack.columnNumber());\n        noTrack.consumeTo(\"<qux>\");\n        assertEquals(12, noTrack.pos());\n        assertEquals(1, noTrack.lineNumber());\n        assertEquals(13, noTrack.columnNumber());\n        assertEquals(\"1:13\", noTrack.posLineCol());\n        // get over the buffer\n        while (!noTrack.matches(\"[foo]\"))\n            noTrack.consumeTo(\"[foo]\");\n        assertEquals(2090, noTrack.pos());\n        assertEquals(1, noTrack.lineNumber());\n        assertEquals(noTrack.pos()+1, noTrack.columnNumber());\n        assertEquals(\"1:2091\", noTrack.posLineCol());\n\n        // and the line numbers: \"<foo>\\n<bar>\\n<qux>\\n\"\n        assertEquals(0, track.pos());\n        assertEquals(1, track.lineNumber());\n        assertEquals(1, track.columnNumber());\n\n        track.consumeTo('\\n');\n        assertEquals(1, track.lineNumber());\n        assertEquals(6, track.columnNumber());\n        track.consume();\n        assertEquals(2, track.lineNumber());\n        assertEquals(1, track.columnNumber());\n\n        assertEquals(\"<bar>\", track.consumeTo('\\n'));\n        assertEquals(2, track.lineNumber());\n        assertEquals(6, track.columnNumber());\n\n        assertEquals(\"\\n\", track.consumeTo(\"<qux>\"));\n        assertEquals(12, track.pos());\n        assertEquals(3, track.lineNumber());\n        assertEquals(1, track.columnNumber());\n        assertEquals(\"3:1\", track.posLineCol());\n        assertEquals(\"<qux>\", track.consumeTo('\\n'));\n        assertEquals(\"3:6\", track.posLineCol());\n        // get over the buffer\n        while (!track.matches(\"[foo]\"))\n            track.consumeTo(\"[foo]\");\n        assertEquals(2090, track.pos());\n        assertEquals(4, track.lineNumber());\n        assertEquals(2073, track.columnNumber());\n        assertEquals(\"4:2073\", track.posLineCol());\n        track.consumeTo('\\n');\n        assertEquals(\"4:2078\", track.posLineCol());\n\n        track.consumeTo(\"[bar]\");\n        assertEquals(5, track.lineNumber());\n        assertEquals(\"5:1\", track.posLineCol());\n        track.consumeToEnd();\n        assertEquals(\"5:6\", track.posLineCol());\n    }\n\n    @Test public void countsColumnsOverBufferWhenNoNewlines() {\n        StringBuilder builder = new StringBuilder();\n        while (builder.length() < maxBufferLen * 4)\n            builder.append(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\");\n        String content = builder.toString();\n        CharacterReader reader = new CharacterReader(content);\n        reader.trackNewlines(true);\n\n        assertEquals(\"1:1\", reader.posLineCol());\n        StringBuilder seen = new StringBuilder();\n        while (!reader.isEmpty())\n            seen.append(reader.consume());\n        assertEquals(content, seen.toString());\n        assertEquals(content.length(), reader.pos());\n        assertEquals(reader.pos() + 1, reader.columnNumber());\n        assertEquals(1, reader.lineNumber());\n    }\n\n    @Test public void linenumbersAgreeWithEditor() throws IOException {\n        String content = ParseTest.getFileAsString(ParseTest.getFile(\"/htmltests/large.html\"));\n        CharacterReader reader = new CharacterReader(content);\n        reader.trackNewlines(true);\n\n        String scan = \"<p>VESTIBULUM\"; // near the end of the file\n        while (!reader.matches(scan))\n            reader.consumeTo(scan);\n\n        assertEquals(280218, reader.pos());\n        assertEquals(1002, reader.lineNumber());\n        assertEquals(1, reader.columnNumber());\n        reader.consumeTo(' ');\n        assertEquals(1002, reader.lineNumber());\n        assertEquals(14, reader.columnNumber());\n    }\n\n    @Test public void consumeDoubleQuotedAttributeConsumesThruSingleQuote() {\n        String html = \"He'llo\\\" >\";\n        CharacterReader r = new CharacterReader(html);\n        assertEquals(\"He'llo\", r.consumeAttributeQuoted(false));\n        assertEquals('\"', r.consume());\n    }\n\n    @Test public void consumeSingleQuotedAttributeConsumesThruDoubleQuote() {\n        String html = \"He\\\"llo' >\";\n        CharacterReader r = new CharacterReader(html);\n        assertEquals(\"He\\\"llo\", r.consumeAttributeQuoted(true));\n        assertEquals('\\'', r.consume());\n    }\n\n    @Test public void consumeDoubleQuotedAttributeConsumesThruSingleQuoteToAmp() {\n        String html = \"He'llo &copy;\\\" >\";\n        CharacterReader r = new CharacterReader(html);\n        assertEquals(\"He'llo \", r.consumeAttributeQuoted(false));\n        assertEquals('&', r.consume());\n    }\n\n    @Test public void consumeSingleQuotedAttributeConsumesThruDoubleQuoteToAmp() {\n        String html = \"He\\\"llo &copy;' >\";\n        CharacterReader r = new CharacterReader(html);\n        assertEquals(\"He\\\"llo \", r.consumeAttributeQuoted(true));\n        assertEquals('&', r.consume());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/HtmlParserTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.*;\nimport org.jsoup.safety.Safelist;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.parser.ParseSettings.preserveCase;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for the Parser\n *\n * @author Jonathan Hedley, jonathan@hedley.net\n */\npublic class HtmlParserTest {\n\n    @Test public void parsesSimpleDocument() {\n        String html = \"<html><head><title>First!</title></head><body><p>First post! <img src=\\\"foo.png\\\" /></p></body></html>\";\n        Document doc = Jsoup.parse(html);\n        // need a better way to verify these:\n        Element p = doc.body().child(0);\n        assertEquals(\"p\", p.tagName());\n        Element img = p.child(0);\n        assertEquals(\"foo.png\", img.attr(\"src\"));\n        assertEquals(\"img\", img.tagName());\n    }\n\n    @Test public void parsesRoughAttributes() {\n        String html = \"<html><head><title>First!</title></head><body><p class=\\\"foo > bar\\\">First post! <img src=\\\"foo.png\\\" /></p></body></html>\";\n        Document doc = Jsoup.parse(html);\n\n        // need a better way to verify these:\n        Element p = doc.body().child(0);\n        assertEquals(\"p\", p.tagName());\n        assertEquals(\"foo > bar\", p.attr(\"class\"));\n    }\n\n    @ParameterizedTest @MethodSource(\"dupeAttributeData\")\n    public void dropsDuplicateAttributes(String html, String expected) {\n        Parser parser = Parser.htmlParser().setTrackErrors(10);\n        Document doc = parser.parseInput(html, \"\");\n\n        Element el = doc.expectFirst(\"body > *\");\n        assertEquals(expected, el.outerHtml()); // normalized names due to lower casing\n        String tag = el.normalName();\n\n        assertEquals(1, parser.getErrors().size());\n        assertEquals(\"Dropped duplicate attribute(s) in tag [\" + tag + \"]\", parser.getErrors().get(0).getErrorMessage());\n    }\n\n    private static Stream<Arguments> dupeAttributeData() {\n        return Stream.of(\n            Arguments.of(\"<p One=One ONE=Two Two=two one=Three One=Four two=Five>Text</p>\", \"<p one=\\\"One\\\" two=\\\"two\\\">Text</p>\"),\n            Arguments.of(\"<img One=One ONE=Two Two=two one=Three One=Four two=Five>\", \"<img one=\\\"One\\\" two=\\\"two\\\">\"),\n            Arguments.of(\"<form One=One ONE=Two Two=two one=Three One=Four two=Five></form>\", \"<form one=\\\"One\\\" two=\\\"two\\\"></form>\")\n        );\n    }\n\n    @Test public void retainsAttributesOfDifferentCaseIfSensitive() {\n        String html = \"<p One=One One=Two one=Three two=Four two=Five Two=Six>Text</p>\";\n        Parser parser = Parser.htmlParser().settings(preserveCase);\n        Document doc = parser.parseInput(html, \"\");\n        assertEquals(\"<p One=\\\"One\\\" one=\\\"Three\\\" two=\\\"Four\\\" Two=\\\"Six\\\">Text</p>\", doc.selectFirst(\"p\").outerHtml());\n    }\n\n    @Test public void parsesQuiteRoughAttributes() {\n        String html = \"<p =a>One<a <p>Something</p>Else\";\n        // this gets a <p> with attr '=a' and an <a tag with an attribute named '<p'; and then auto-recreated\n        Document doc = Jsoup.parse(html);\n\n        // =a is output as _a\n        assertEquals(\"<p _a>One<a <p>Something</a></p><a <p>Else</a>\", TextUtil.stripNewlines(doc.body().html()));\n        Element p = doc.expectFirst(\"p\");\n        assertNotNull(p.attribute(\"=a\"));\n\n        doc = Jsoup.parse(\"<p .....>\");\n        assertEquals(\"<p .....></p>\", doc.body().html());\n    }\n\n    @Test public void parsesComments() {\n        String html = \"<html><head></head><body><img src=foo><!-- <table><tr><td></table> --><p>Hello</p></body></html>\";\n        Document doc = Jsoup.parse(html);\n\n        Element body = doc.body();\n        Comment comment = (Comment) body.childNode(1); // comment should not be sub of img, as it's an empty tag\n        assertEquals(\" <table><tr><td></table> \", comment.getData());\n        Element p = body.child(1);\n        TextNode text = (TextNode) p.childNode(0);\n        assertEquals(\"Hello\", text.getWholeText());\n    }\n\n    @Test public void parsesUnterminatedComments() {\n        String html = \"<p>Hello<!-- <tr><td>\";\n        Document doc = Jsoup.parse(html);\n        Element p = doc.getElementsByTag(\"p\").get(0);\n        assertEquals(\"Hello\", p.text());\n        TextNode text = (TextNode) p.childNode(0);\n        assertEquals(\"Hello\", text.getWholeText());\n        Comment comment = (Comment) p.childNode(1);\n        assertEquals(\" <tr><td>\", comment.getData());\n    }\n\n    @Test void allDashCommentsAreNotParseErrors() {\n        // https://github.com/jhy/jsoup/issues/1667\n        // <!-----> is not a parse error\n        String html = \"<!------>\";\n        Parser parser = Parser.htmlParser().setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        Comment comment = (Comment) doc.childNode(0);\n        assertEquals(\"--\", comment.getData());\n        assertEquals(0, parser.getErrors().size());\n    }\n\n    @Test public void dropsUnterminatedTag() {\n        // jsoup used to parse this to <p>, but whatwg, webkit will drop.\n        String h1 = \"<p\";\n        Document doc = Jsoup.parse(h1);\n        assertEquals(0, doc.getElementsByTag(\"p\").size());\n        assertEquals(\"\", doc.text());\n\n        String h2 = \"<div id=1<p id='2'\";\n        doc = Jsoup.parse(h2);\n        assertEquals(\"\", doc.text());\n    }\n\n    @Test public void dropsUnterminatedAttribute() {\n        // jsoup used to parse this to <p id=\"foo\">, but whatwg, webkit will drop.\n        String h1 = \"<p id=\\\"foo\";\n        Document doc = Jsoup.parse(h1);\n        assertEquals(\"\", doc.text());\n    }\n\n    @Test public void parsesUnterminatedTextarea() {\n        // don't parse right to end, but break on <p>\n        Document doc = Jsoup.parse(\"<body><p><textarea>one<p>two\");\n        Element t = doc.select(\"textarea\").first();\n        assertEquals(\"one\", t.text());\n        assertEquals(\"two\", doc.select(\"p\").get(1).text());\n    }\n\n    @Test public void parsesUnterminatedOption() {\n        // bit weird this -- browsers and spec get stuck in select until there's a </select>\n        Document doc = Jsoup.parse(\"<body><p><select><option>One<option>Two</p><p>Three</p>\");\n        Elements options = doc.select(\"option\");\n        assertEquals(2, options.size());\n        assertEquals(\"One\", options.first().text());\n        assertEquals(\"TwoThree\", options.last().text());\n    }\n\n    @Test public void testSelectWithOption() {\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document document = parser.parseInput(\"<select><option>Option 1</option></select>\", \"http://jsoup.org\");\n        assertEquals(0, parser.getErrors().size());\n    }\n\n    @Test public void testSpaceAfterTag() {\n        Document doc = Jsoup.parse(\"<div > <a name=\\\"top\\\"></a ><p id=1 >Hello</p></div>\");\n        assertEquals(\"<div><a name=\\\"top\\\"></a><p id=\\\"1\\\">Hello</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void createsDocumentStructure() {\n        String html = \"<meta name=keywords /><link rel=stylesheet /><title>jsoup</title><p>Hello world</p>\";\n        Document doc = Jsoup.parse(html);\n        Element head = doc.head();\n        Element body = doc.body();\n\n        assertEquals(1, doc.children().size()); // root node: contains html node\n        assertEquals(2, doc.child(0).children().size()); // html node: head and body\n        assertEquals(3, head.children().size());\n        assertEquals(1, body.children().size());\n\n        assertEquals(\"keywords\", head.getElementsByTag(\"meta\").get(0).attr(\"name\"));\n        assertEquals(0, body.getElementsByTag(\"meta\").size());\n        assertEquals(\"jsoup\", doc.title());\n        assertEquals(\"Hello world\", body.text());\n        assertEquals(\"Hello world\", body.children().get(0).text());\n    }\n\n    @Test public void createsStructureFromBodySnippet() {\n        // the bar baz stuff naturally goes into the body, but the 'foo' goes into root, and the normalisation routine\n        // needs to move into the start of the body\n        String html = \"foo <b>bar</b> baz\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"foo bar baz\", doc.text());\n    }\n\n    @Test public void handlesEscapedData() {\n        String html = \"<div title='Surf &amp; Turf'>Reef &amp; Beef</div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.getElementsByTag(\"div\").get(0);\n\n        assertEquals(\"Surf & Turf\", div.attr(\"title\"));\n        assertEquals(\"Reef & Beef\", div.text());\n    }\n\n    @Test public void handlesDataOnlyTags() {\n        String t = \"<style>font-family: bold</style>\";\n        List<Element> tels = Jsoup.parse(t).getElementsByTag(\"style\");\n        assertEquals(\"font-family: bold\", tels.get(0).data());\n        assertEquals(\"\", tels.get(0).text());\n\n        String s = \"<p>Hello</p><script>obj.insert('<a rel=\\\"none\\\" />');\\ni++;</script><p>There</p>\";\n        Document doc = Jsoup.parse(s);\n        assertEquals(\"Hello There\", doc.text());\n        assertEquals(\"obj.insert('<a rel=\\\"none\\\" />');\\ni++;\", doc.data());\n    }\n\n    @Test public void handlesTextAfterData() {\n        String h = \"<html><body>pre <script>inner</script> aft</body></html>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<html><head></head><body>pre<script>inner</script>aft</body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void handlesTextArea() {\n        Document doc = Jsoup.parse(\"<textarea>Hello</textarea>\");\n        Elements els = doc.select(\"textarea\");\n        assertEquals(\"Hello\", els.text());\n        assertEquals(\"Hello\", els.val());\n    }\n\n    @Test public void preservesSpaceInTextArea() {\n        // preserve because the tag is marked as preserve white space\n        Document doc = Jsoup.parse(\"<textarea>\\n\\tOne\\n\\tTwo\\n\\tThree\\n</textarea>\");\n        String expect = \"One\\n\\tTwo\\n\\tThree\"; // the leading and trailing spaces are dropped as a convenience to authors\n        Element el = doc.expectFirst(\"textarea\");\n        assertEquals(expect, el.text());\n        assertEquals(expect, el.val());\n        assertEquals(expect, el.html());\n        assertEquals(\"<textarea>\\n\\t\" + expect + \"\\n</textarea>\", el.outerHtml()); // but preserved in round-trip html\n    }\n\n    @Test public void preservesSpaceInScript() {\n        // preserve because it's content is a data node\n        Document doc = Jsoup.parse(\"<script>\\nOne\\n\\tTwo\\n\\tThree\\n</script>\");\n        String expect = \"\\nOne\\n\\tTwo\\n\\tThree\\n\";\n        Element el = doc.select(\"script\").first();\n        assertEquals(expect, el.data());\n        assertEquals(\"One\\n\\tTwo\\n\\tThree\", el.html());\n        assertEquals(\"<script>\" + expect + \"</script>\", el.outerHtml());\n    }\n\n    @Test public void doesNotCreateImplicitLists() {\n        // old jsoup used to wrap this in <ul>, but that's not to spec\n        String h = \"<li>Point one<li>Point two\";\n        Document doc = Jsoup.parse(h);\n        Elements ol = doc.select(\"ul\"); // should NOT have created a default ul.\n        assertEquals(0, ol.size());\n        Elements lis = doc.select(\"li\");\n        assertEquals(2, lis.size());\n        assertEquals(\"body\", lis.first().parent().tagName());\n\n        // no fiddling with non-implicit lists\n        String h2 = \"<ol><li><p>Point the first<li><p>Point the second\";\n        Document doc2 = Jsoup.parse(h2);\n\n        assertEquals(0, doc2.select(\"ul\").size());\n        assertEquals(1, doc2.select(\"ol\").size());\n        assertEquals(2, doc2.select(\"ol li\").size());\n        assertEquals(2, doc2.select(\"ol li p\").size());\n        assertEquals(1, doc2.select(\"ol li\").get(0).children().size()); // one p in first li\n    }\n\n    @Test public void discardsNakedTds() {\n        // jsoup used to make this into an implicit table; but browsers make it into a text run\n        String h = \"<td>Hello<td><p>There<p>now\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"Hello<p>There</p><p>now</p>\", TextUtil.stripNewlines(doc.body().html()));\n        // <tbody> is introduced if no implicitly creating table, but allows tr to be directly under table\n    }\n\n    @Test public void handlesNestedImplicitTable() {\n        Document doc = Jsoup.parse(\"<table><td>1</td></tr> <td>2</td></tr> <td> <table><td>3</td> <td>4</td></table> <tr><td>5</table>\");\n        assertEquals(\"<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr><tr><td><table><tbody><tr><td>3</td><td>4</td></tr></tbody></table></td></tr><tr><td>5</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesWhatWgExpensesTableExample() {\n        // http://www.whatwg.org/specs/web-apps/current-work/multipage/tabular-data.html#examples-0\n        Document doc = Jsoup.parse(\"<table> <colgroup> <col> <colgroup> <col> <col> <col> <thead> <tr> <th> <th>2008 <th>2007 <th>2006 <tbody> <tr> <th scope=rowgroup> Research and development <td> $ 1,109 <td> $ 782 <td> $ 712 <tr> <th scope=row> Percentage of net sales <td> 3.4% <td> 3.3% <td> 3.7% <tbody> <tr> <th scope=rowgroup> Selling, general, and administrative <td> $ 3,761 <td> $ 2,963 <td> $ 2,433 <tr> <th scope=row> Percentage of net sales <td> 11.6% <td> 12.3% <td> 12.6% </table>\");\n        assertEquals(\"<table><colgroup><col></colgroup><colgroup><col><col><col></colgroup><thead><tr><th></th><th>2008</th><th>2007</th><th>2006</th></tr></thead><tbody><tr><th scope=\\\"rowgroup\\\">Research and development</th><td>$ 1,109</td><td>$ 782</td><td>$ 712</td></tr><tr><th scope=\\\"row\\\">Percentage of net sales</th><td>3.4%</td><td>3.3%</td><td>3.7%</td></tr></tbody><tbody><tr><th scope=\\\"rowgroup\\\">Selling, general, and administrative</th><td>$ 3,761</td><td>$ 2,963</td><td>$ 2,433</td></tr><tr><th scope=\\\"row\\\">Percentage of net sales</th><td>11.6%</td><td>12.3%</td><td>12.6%</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesTbodyTable() {\n        Document doc = Jsoup.parse(\"<html><head></head><body><table><tbody><tr><td>aaa</td><td>bbb</td></tr></tbody></table></body></html>\");\n        assertEquals(\"<table><tbody><tr><td>aaa</td><td>bbb</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesImplicitCaptionClose() {\n        Document doc = Jsoup.parse(\"<table><caption>A caption<td>One<td>Two\");\n        assertEquals(\"<table><caption>A caption</caption><tbody><tr><td>One</td><td>Two</td></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void noTableDirectInTable() {\n        Document doc = Jsoup.parse(\"<table> <td>One <td><table><td>Two</table> <table><td>Three\");\n        assertEquals(\"<table><tbody><tr><td>One</td><td><table><tbody><tr><td>Two</td></tr></tbody></table><table><tbody><tr><td>Three</td></tr></tbody></table></td></tr></tbody></table>\",\n            TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void ignoresDupeEndTrTag() {\n        Document doc = Jsoup.parse(\"<table><tr><td>One</td><td><table><tr><td>Two</td></tr></tr></table></td><td>Three</td></tr></table>\"); // two </tr></tr>, must ignore or will close table\n        assertEquals(\"<table><tbody><tr><td>One</td><td><table><tbody><tr><td>Two</td></tr></tbody></table></td><td>Three</td></tr></tbody></table>\",\n            TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesBaseTags() {\n        // only listen to the first base href\n        String h = \"<a href=1>#</a><base href='/2/'><a href='3'>#</a><base href='http://bar'><a href=/4>#</a>\";\n        Document doc = Jsoup.parse(h, \"http://foo/\");\n        assertEquals(\"http://foo/2/\", doc.baseUri()); // gets set once, so doc and descendants have first only\n\n        Elements anchors = doc.getElementsByTag(\"a\");\n        assertEquals(3, anchors.size());\n\n        assertEquals(\"http://foo/2/\", anchors.get(0).baseUri());\n        assertEquals(\"http://foo/2/\", anchors.get(1).baseUri());\n        assertEquals(\"http://foo/2/\", anchors.get(2).baseUri());\n\n        assertEquals(\"http://foo/2/1\", anchors.get(0).absUrl(\"href\"));\n        assertEquals(\"http://foo/2/3\", anchors.get(1).absUrl(\"href\"));\n        assertEquals(\"http://foo/4\", anchors.get(2).absUrl(\"href\"));\n    }\n\n    @Test public void handlesProtocolRelativeUrl() {\n        String base = \"https://example.com/\";\n        String html = \"<img src='//example.net/img.jpg'>\";\n        Document doc = Jsoup.parse(html, base);\n        Element el = doc.select(\"img\").first();\n        assertEquals(\"https://example.net/img.jpg\", el.absUrl(\"src\"));\n    }\n\n    @Test public void handlesCdata() {\n        // todo: as this is html namespace, should actually treat as bogus comment, not cdata. keep as cdata for now\n        String h = \"<div id=1><![CDATA[<html>\\n <foo><&amp;]]></div>\"; // the &amp; in there should remain literal\n        Document doc = Jsoup.parse(h);\n        Element div = doc.getElementById(\"1\");\n        assertEquals(\"<html>\\n <foo><&amp;\", div.text());\n        assertEquals(0, div.children().size());\n        assertEquals(1, div.childNodeSize()); // no elements, one text node\n    }\n\n    @Test public void roundTripsCdata() {\n        String h = \"<div id=1><![CDATA[\\n<html>\\n <foo><&amp;]]></div>\";\n        Document doc = Jsoup.parse(h);\n        Element div = doc.getElementById(\"1\");\n        assertEquals(\"<html>\\n <foo><&amp;\", div.text());\n        assertEquals(0, div.children().size());\n        assertEquals(1, div.childNodeSize()); // no elements, one text node\n\n        assertEquals(\"<div id=\\\"1\\\"><![CDATA[\\n<html>\\n <foo><&amp;]]></div>\", div.outerHtml());\n\n        CDataNode cdata = (CDataNode) div.textNodes().get(0);\n        assertEquals(\"\\n<html>\\n <foo><&amp;\", cdata.text());\n    }\n\n    @Test public void handlesCdataAcrossBuffer() {\n        StringBuilder sb = new StringBuilder();\n        while (sb.length() <= CharacterReader.BufferSize) {\n            sb.append(\"A suitable amount of CData.\\n\");\n        }\n        String cdata = sb.toString();\n        String h = \"<div><![CDATA[\" + cdata + \"]]></div>\";\n        Document doc = Jsoup.parse(h);\n        Element div = doc.selectFirst(\"div\");\n\n        CDataNode node = (CDataNode) div.textNodes().get(0);\n        assertEquals(cdata, node.text());\n    }\n\n    @Test public void handlesCdataInScript() {\n        String html = \"<script type=\\\"text/javascript\\\">//<![CDATA[\\n\\n  foo();\\n//]]></script>\";\n        Document doc = Jsoup.parse(html);\n\n        String data = \"//<![CDATA[\\n\\n  foo();\\n//]]>\";\n        Element script = doc.selectFirst(\"script\");\n        assertEquals(\"\", script.text()); // won't be parsed as cdata because in script data section\n        assertEquals(data, script.data());\n        assertEquals(html, script.outerHtml());\n\n        DataNode dataNode = (DataNode) script.childNode(0);\n        assertEquals(data, dataNode.getWholeData());\n        // see - not a cdata node, because in script. contrast with XmlTreeBuilder - will be cdata.\n    }\n\n    @Test public void handlesUnclosedCdataAtEOF() {\n        // https://github.com/jhy/jsoup/issues/349 would crash, as character reader would try to seek past EOF\n        String h = \"<![CDATA[]]\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(1, doc.body().childNodeSize());\n    }\n\n    @Test public void handleCDataInText() {\n        String h = \"<p>One <![CDATA[Two <&]]> Three</p>\";\n        Document doc = Jsoup.parse(h);\n        Element p = doc.selectFirst(\"p\");\n\n        List<Node> nodes = p.childNodes();\n        assertEquals(\"One \", ((TextNode) nodes.get(0)).getWholeText());\n        assertEquals(\"Two <&\", ((TextNode) nodes.get(1)).getWholeText());\n        assertEquals(\"Two <&\", ((CDataNode) nodes.get(1)).getWholeText());\n        assertEquals(\" Three\", ((TextNode) nodes.get(2)).getWholeText());\n\n        assertEquals(h, p.outerHtml());\n    }\n\n    @Test public void cdataNodesAreTextNodes() {\n        String h = \"<p>One <![CDATA[ Two <& ]]> Three</p>\";\n        Document doc = Jsoup.parse(h);\n        Element p = doc.selectFirst(\"p\");\n\n        List<TextNode> nodes = p.textNodes();\n        assertEquals(\"One \", nodes.get(0).text());\n        assertEquals(\" Two <& \", nodes.get(1).text());\n        assertEquals(\" Three\", nodes.get(2).text());\n    }\n\n    @Test public void handlesInvalidStartTags() {\n        String h = \"<div>Hello < There <&amp;></div>\"; // parse to <div {#text=Hello < There <&>}>\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"Hello < There <&>\", doc.select(\"div\").first().text());\n    }\n\n    @Test public void handlesUnknownTags() {\n        String h = \"<div><foo title=bar>Hello<foo title=qux>there</foo></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements foos = doc.select(\"foo\");\n        assertEquals(2, foos.size());\n        assertEquals(\"bar\", foos.first().attr(\"title\"));\n        assertEquals(\"qux\", foos.last().attr(\"title\"));\n        assertEquals(\"there\", foos.last().text());\n    }\n\n    @Test public void handlesUnknownInlineTags() {\n        String h = \"<p><cust>Test</cust></p><p><cust><cust>Test</cust></cust></p>\";\n        Document doc = Jsoup.parseBodyFragment(h);\n        String out = doc.body().html();\n        assertEquals(h, TextUtil.stripNewlines(out));\n    }\n\n    @Test public void parsesBodyFragment() {\n        String h = \"<!-- comment --><p><a href='foo'>One</a></p>\";\n        Document doc = Jsoup.parseBodyFragment(h, \"http://example.com\");\n        assertEquals(\"<body><!-- comment --><p><a href=\\\"foo\\\">One</a></p></body>\", TextUtil.stripNewlines(doc.body().outerHtml()));\n        assertEquals(\"http://example.com/foo\", doc.select(\"a\").first().absUrl(\"href\"));\n    }\n\n    @Test public void parseBodyIsIndexNoAttributes() {\n        // https://github.com/jhy/jsoup/issues/1404\n        String expectedHtml = \"<isindex></isindex>\";\n        Document doc = Jsoup.parse(\"<isindex>\");\n        assertEquals(expectedHtml, doc.body().html());\n\n        doc = Jsoup.parseBodyFragment(\"<isindex>\");\n        assertEquals(expectedHtml, doc.body().html());\n\n        doc = Jsoup.parseBodyFragment(\"<table><input></table>\");\n        assertEquals(\"<input>\\n<table></table>\", doc.body().html());\n    }\n\n    @Test public void siblingIndexFromFragment() {\n        Document doc = Jsoup.parseBodyFragment(\"<table><input></table>\");\n        Element input = doc.expectFirst(\"input\");\n        Element table = doc.expectFirst(\"table\");\n        assertEquals(0, input.siblingIndex());\n        assertEquals(1, table.siblingIndex());\n    }\n\n    @Test public void siblingIndexFromParse() {\n        Document doc = Jsoup.parse(\"<table><input></table>\");\n        Element input = doc.expectFirst(\"input\");\n        Element table = doc.expectFirst(\"table\");\n        assertEquals(0, input.siblingIndex());\n        assertEquals(1, table.siblingIndex());\n    }\n\n    @Test public void handlesUnknownNamespaceTags() {\n        String h = \"<foo:bar id='1' /><abc:def id=2>Foo<p>Hello</p></abc:def><foo:bar>There</foo:bar>\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"foo:bar\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(h, parser);\n        assertEquals(\"<foo:bar id=\\\"1\\\"></foo:bar><abc:def id=\\\"2\\\">Foo<p>Hello</p></abc:def><foo:bar>There</foo:bar>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    // we used to allow self-closing for any tag in html, but spec no longer allows\n    @Test public void handlesKnownEmptyBlocks() {\n        // by default, self-closing flag has no impact (see detailed tests for self-closing below)\n        String h = \"<div id='1' /><script src='/foo'></script><div id=2><img /><img></div><a id=3 /><i /><foo /><foo>One</foo> <hr /> hr text <hr> hr text two\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<div id=\\\"1\\\"><script src=\\\"/foo\\\"></script><div id=\\\"2\\\"><img><img></div><a id=\\\"3\\\"><i><foo><foo>One</foo><hr>hr text<hr>hr text two</foo></i></a></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesEmptyNoFrames() {\n        // can modify parser to allow self closing\n        String h = \"<html><head><noframes /><meta name=foo></head><body>One</body></html>\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"noframes\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(h, parser);\n        assertEquals(\"<html><head><noframes></noframes><meta name=\\\"foo\\\"></head><body>One</body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void handlesKnownEmptyStyle() {\n        String h = \"<html><head><style /><meta name=foo></head><body>One</body></html>\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"style\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(h, parser);\n        assertEquals(\"<html><head><style></style><meta name=\\\"foo\\\"></head><body>One</body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void handlesKnownEmptyTitle() {\n        String h = \"<html><head><title /><meta name=foo></head><body>One</body></html>\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"title\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(h, parser);\n        assertEquals(\"<html><head><title></title><meta name=\\\"foo\\\"></head><body>One</body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void handlesKnownEmptyIframe() {\n        String h = \"<p>One</p><iframe id=1 /><p>Two\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"iframe\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(h, parser);\n        assertEquals(\"<html><head></head><body><p>One</p><iframe id=\\\"1\\\"></iframe><p>Two</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void handlesSolidusAtAttributeEnd() {\n        // this test makes sure [<a href=/>link</a>] is parsed as [<a href=\"/\">link</a>], not [<a href=\"\" /><a>link</a>]\n        String h = \"<a href=/>link</a>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<a href=\\\"/\\\">link</a>\", doc.body().html());\n    }\n\n    @Test public void handlesMultiClosingBody() {\n        String h = \"<body><p>Hello</body><p>there</p></body></body></html><p>now\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(3, doc.select(\"p\").size());\n        assertEquals(3, doc.body().children().size());\n    }\n\n    @Test public void handlesUnclosedDefinitionLists() {\n        // jsoup used to create a <dl>, but that's not to spec\n        String h = \"<dt>Foo<dd>Bar<dt>Qux<dd>Zug\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(0, doc.select(\"dl\").size()); // no auto dl\n        assertEquals(4, doc.select(\"dt, dd\").size());\n        Elements dts = doc.select(\"dt\");\n        assertEquals(2, dts.size());\n        assertEquals(\"Zug\", dts.get(1).nextElementSibling().text());\n    }\n\n    @Test public void handlesBlocksInDefinitions() {\n        // per the spec, dt and dd are inline, but in practise are block\n        String h = \"<dl><dt><div id=1>Term</div></dt><dd><div id=2>Def</div></dd></dl>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"dt\", doc.select(\"#1\").first().parent().tagName());\n        assertEquals(\"dd\", doc.select(\"#2\").first().parent().tagName());\n        assertEquals(\"<dl><dt><div id=\\\"1\\\">Term</div></dt><dd><div id=\\\"2\\\">Def</div></dd></dl>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesFrames() {\n        String h = \"<html><head><script></script><noscript></noscript></head><frameset><frame src=foo></frame><frame src=foo></frameset></html>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<html><head><script></script><noscript></noscript></head><frameset><frame src=\\\"foo\\\"><frame src=\\\"foo\\\"></frameset></html>\",\n            TextUtil.stripNewlines(doc.html()));\n        // no body auto vivification\n    }\n\n    @Test public void ignoresContentAfterFrameset() {\n        String h = \"<html><head><title>One</title></head><frameset><frame /><frame /></frameset><table></table></html>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<html><head><title>One</title></head><frameset><frame><frame></frameset></html>\", TextUtil.stripNewlines(doc.html()));\n        // no body, no table. No crash!\n    }\n\n    @Test public void handlesJavadocFont() {\n        String h = \"<TD BGCOLOR=\\\"#EEEEFF\\\" CLASS=\\\"NavBarCell1\\\">    <A HREF=\\\"deprecated-list.html\\\"><FONT CLASS=\\\"NavBarFont1\\\"><B>Deprecated</B></FONT></A>&nbsp;</TD>\";\n        Document doc = Jsoup.parse(h);\n        Element a = doc.select(\"a\").first();\n        assertEquals(\"Deprecated\", a.text());\n        assertEquals(\"font\", a.child(0).tagName());\n        assertEquals(\"b\", a.child(0).child(0).tagName());\n    }\n\n    @Test public void handlesBaseWithoutHref() {\n        String h = \"<head><base target='_blank'></head><body><a href=/foo>Test</a></body>\";\n        Document doc = Jsoup.parse(h, \"http://example.com/\");\n        Element a = doc.select(\"a\").first();\n        assertEquals(\"/foo\", a.attr(\"href\"));\n        assertEquals(\"http://example.com/foo\", a.attr(\"abs:href\"));\n    }\n\n    @Test public void normalisesDocument() {\n        String h = \"<!doctype html>One<html>Two<head>Three<link></head>Four<body>Five </body>Six </html>Seven \";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<!doctype html><html><head></head><body>OneTwoThree<link>FourFive Six Seven</body></html>\",\n            TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void normalisesEmptyDocument() {\n        Document doc = Jsoup.parse(\"\");\n        assertEquals(\"<html><head></head><body></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void normalisesHeadlessBody() {\n        Document doc = Jsoup.parse(\"<html><body><span class=\\\"foo\\\">bar</span>\");\n        assertEquals(\"<html><head></head><body><span class=\\\"foo\\\">bar</span></body></html>\",\n            TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void normalisedBodyAfterContent() {\n        Document doc = Jsoup.parse(\"<font face=Arial><body class=name><div>One</div></body></font>\");\n        assertEquals(\"<html><head></head><body class=\\\"name\\\"><font face=\\\"Arial\\\"><div>One</div></font></body></html>\",\n            TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void findsCharsetInMalformedMeta() {\n        String h = \"<meta http-equiv=Content-Type content=text/html; charset=gb2312>\";\n        // example cited for reason of html5's <meta charset> element\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"gb2312\", doc.select(\"meta\").attr(\"charset\"));\n    }\n\n    @Test public void testHgroup() {\n        // jsoup used to not allow hgroup in h{n}, but that's not in spec, and browsers are OK\n        Document doc = Jsoup.parse(\"<h1>Hello <h2>There <hgroup><h1>Another<h2>headline</hgroup> <hgroup><h1>More</h1><p>stuff</p></hgroup>\");\n        assertEquals(\"<h1>Hello</h1><h2>There<hgroup><h1>Another</h1><h2>headline</h2></hgroup><hgroup><h1>More</h1><p>stuff</p></hgroup></h2>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void testRelaxedTags() {\n        Document doc = Jsoup.parse(\"<abc_def id=1>Hello</abc_def> <abc-def>There</abc-def>\");\n        assertEquals(\"<abc_def id=\\\"1\\\">Hello</abc_def> <abc-def>There</abc-def>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void testHeaderContents() {\n        // h* tags (h1 .. h9) in browsers can handle any internal content other than other h*. which is not per any\n        // spec, which defines them as containing phrasing content only. so, reality over theory.\n        Document doc = Jsoup.parse(\"<h1>Hello <div>There</div> now</h1> <h2>More <h3>Content</h3></h2>\");\n        assertEquals(\"<h1>Hello<div>There</div>now</h1><h2>More</h2><h3>Content</h3>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void testSpanContents() {\n        // like h1 tags, the spec says SPAN is phrasing only, but browsers and publisher treat span as a block tag\n        Document doc = Jsoup.parse(\"<span>Hello <div>there</div> <span>now</span></span>\");\n        assertEquals(\"<span>Hello <div>there</div> <span>now</span></span>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void testNoImagesInNoScriptInHead() {\n        // jsoup used to allow, but against spec if parsing with noscript\n        Document doc = Jsoup.parse(\"<html><head><noscript><img src='foo'></noscript></head><body><p>Hello</p></body></html>\");\n        assertEquals(\"<html><head><noscript>&lt;img src=\\\"foo\\\"&gt;</noscript></head><body><p>Hello</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void testUnclosedNoscriptInHead() {\n        // Was getting \"EOF\" in html output, because the #anythingElse handler was calling an undefined toString, so used object.toString.\n        String[] strings = {\"<noscript>\", \"<noscript>One\"};\n        for (String html : strings) {\n            Document doc = Jsoup.parse(html);\n            assertEquals(html + \"</noscript>\", TextUtil.stripNewlines(doc.head().html()));\n        }\n    }\n\n    @Test public void testAFlowContents() {\n        // html5 has <a> as either phrasing or block\n        Document doc = Jsoup.parse(\"<a>Hello <div>there</div> <span>now</span></a>\");\n        assertEquals(\"<a>Hello \\n <div>there</div> \\n <span>now</span></a>\", (doc.body().html()));\n    }\n\n    @Test public void testFontFlowContents() {\n        // html5 has no definition of <font>; often used as flow\n        Document doc = Jsoup.parse(\"<font>Hello <div>there</div> <span>now</span></font>\");\n        assertEquals(\"<font>Hello <div>there</div> <span>now</span></font>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesMisnestedTagsBI() {\n        // whatwg: <b><i></b></i>\n        String h = \"<p>1<b>2<i>3</b>4</i>5</p>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<p>1<b>2<i>3</i></b><i>4</i>5</p>\", doc.body().html());\n        // adoption agency on </b>, reconstruction of formatters on 4.\n    }\n\n    @Test public void handlesMisnestedTagsBP() {\n        //  whatwg: <b><p></b></p>\n        String h = \"<b>1<p>2</b>3</p>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<b>1</b>\\n<p><b>2</b>3</p>\", doc.body().html());\n    }\n\n    @Test public void handlesMisnestedAInDivs() {\n        String h = \"<a 1><div 2><div 3><a 4>child</a></div></div></a>\";\n        String w = \"<a 1></a> <div 2> <a 1=\\\"\\\"></a> <div 3> <a 1=\\\"\\\"></a><a 4>child</a> </div> </div>\"; // chrome checked\n        // todo - come back to how we copy the attributes, to keep boolean setting (not =\"\")\n\n        Document doc = Jsoup.parse(h);\n        assertEquals(\n            StringUtil.normaliseWhitespace(w),\n            StringUtil.normaliseWhitespace(doc.body().html()));\n    }\n\n    @Test public void handlesUnexpectedMarkupInTables() {\n        // whatwg - tests markers in active formatting (if they didn't work, would get in table)\n        // also tests foster parenting\n        String h = \"<table><b><tr><td>aaa</td></tr>bbb</table>ccc\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<b></b><b>bbb</b><table><tbody><tr><td>aaa</td></tr></tbody></table><b>ccc</b>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesUnclosedFormattingElements() {\n        // whatwg: formatting elements get collected and applied, but excess elements are thrown away\n        String h = \"<!DOCTYPE html>\\n\" +\n            \"<p><b class=x><b class=x><b><b class=x><b class=x><b>X\\n\" +\n            \"<p>X\\n\" +\n            \"<p><b><b class=x><b>X\\n\" +\n            \"<p></b></b></b></b></b></b>X\";\n        Document doc = Jsoup.parse(h);\n        doc.outputSettings().indentAmount(0);\n        String want = \"<!doctype html>\\n\" +\n            \"<html>\\n\" +\n            \"<head></head>\\n\" +\n            \"<body>\\n\" +\n            \"<p><b class=\\\"x\\\"><b class=\\\"x\\\"><b><b class=\\\"x\\\"><b class=\\\"x\\\"><b>X </b></b></b></b></b></b></p>\\n\" +\n            \"<p><b class=\\\"x\\\"><b><b class=\\\"x\\\"><b class=\\\"x\\\"><b>X </b></b></b></b></b></p>\\n\" +\n            \"<p><b class=\\\"x\\\"><b><b class=\\\"x\\\"><b class=\\\"x\\\"><b><b><b class=\\\"x\\\"><b>X </b></b></b></b></b></b></b></b></p>\\n\" +\n            \"<p>X</p>\\n\" +\n            \"</body>\\n\" +\n            \"</html>\";\n        assertEquals(want, doc.html());\n    }\n\n    @Test public void handlesUnclosedAnchors() {\n        String h = \"<a href='http://example.com/'>Link<p>Error link</a>\";\n        Document doc = Jsoup.parse(h);\n        String want = \"<a href=\\\"http://example.com/\\\">Link</a>\\n<p><a href=\\\"http://example.com/\\\">Error link</a></p>\";\n        assertEquals(want, doc.body().html());\n    }\n\n    @Test public void reconstructFormattingElements() {\n        // tests attributes and multi b\n        String h = \"<p><b class=one>One <i>Two <b>Three</p><p>Hello</p>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<p><b class=\\\"one\\\">One <i>Two <b>Three</b></i></b></p>\\n<p><b class=\\\"one\\\"><i><b>Hello</b></i></b></p>\", doc.body().html());\n    }\n\n    @Test public void reconstructFormattingElementsInTable() {\n        // tests that tables get formatting markers -- the <b> applies outside the table and does not leak in,\n        // and the <i> inside the table and does not leak out.\n        String h = \"<p><b>One</p> <table><tr><td><p><i>Three<p>Four</i></td></tr></table> <p>Five</p>\";\n        Document doc = Jsoup.parse(h);\n        String want = \"<p><b>One</b></p><b> <table><tbody><tr><td><p><i>Three</i></p><p><i>Four</i></p></td></tr></tbody></table> <p>Five</p></b>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void commentBeforeHtml() {\n        String h = \"<!-- comment --><!-- comment 2 --><p>One</p>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<!-- comment --><!-- comment 2 --><html><head></head><body><p>One</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void emptyTdTag() {\n        String h = \"<table><tr><td>One</td><td id='2' /></tr></table>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<td>One</td>\\n<td id=\\\"2\\\"></td>\", doc.select(\"tr\").first().html());\n    }\n\n    @Test public void handlesSolidusInA() {\n        // test for bug #66\n        String h = \"<a class=lp href=/lib/14160711/>link text</a>\";\n        Document doc = Jsoup.parse(h);\n        Element a = doc.select(\"a\").first();\n        assertEquals(\"link text\", a.text());\n        assertEquals(\"/lib/14160711/\", a.attr(\"href\"));\n    }\n\n    @Test public void handlesSpanInTbody() {\n        // test for bug 64\n        String h = \"<table><tbody><span class='1'><tr><td>One</td></tr><tr><td>Two</td></tr></span></tbody></table>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(doc.select(\"span\").first().children().size(), 0); // the span gets closed\n        assertEquals(doc.select(\"table\").size(), 1); // only one table\n    }\n\n    @Test public void handlesUnclosedTitleAtEof() {\n        assertEquals(\"Data\", Jsoup.parse(\"<title>Data\").title());\n        assertEquals(\"Data<\", Jsoup.parse(\"<title>Data<\").title());\n        assertEquals(\"Data</\", Jsoup.parse(\"<title>Data</\").title());\n        assertEquals(\"Data</t\", Jsoup.parse(\"<title>Data</t\").title());\n        assertEquals(\"Data</ti\", Jsoup.parse(\"<title>Data</ti\").title());\n        assertEquals(\"Data\", Jsoup.parse(\"<title>Data</title>\").title());\n        assertEquals(\"Data\", Jsoup.parse(\"<title>Data</title >\").title());\n    }\n\n    @Test public void handlesUnclosedTitle() {\n        Document one = Jsoup.parse(\"<title>One <b>Two <b>Three</TITLE><p>Test</p>\"); // has title, so <b> is plain text\n        assertEquals(\"One <b>Two <b>Three\", one.title());\n        assertEquals(\"Test\", one.select(\"p\").first().text());\n\n        Document two = Jsoup.parse(\"<title>One<b>Two <p>Test</p>\"); // no title, so <b> causes </title> breakout\n        assertEquals(\"One\", two.title());\n        assertEquals(\"<b>Two \\n <p>Test</p></b>\", two.body().html());\n    }\n\n    @Test public void handlesUnclosedScriptAtEof() {\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data\").select(\"script\").first().data());\n        assertEquals(\"Data<\", Jsoup.parse(\"<script>Data<\").select(\"script\").first().data());\n        assertEquals(\"Data</sc\", Jsoup.parse(\"<script>Data</sc\").select(\"script\").first().data());\n        assertEquals(\"Data</-sc\", Jsoup.parse(\"<script>Data</-sc\").select(\"script\").first().data());\n        assertEquals(\"Data</sc-\", Jsoup.parse(\"<script>Data</sc-\").select(\"script\").first().data());\n        assertEquals(\"Data</sc--\", Jsoup.parse(\"<script>Data</sc--\").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script>\").select(\"script\").first().data());\n        assertEquals(\"Data</script\", Jsoup.parse(\"<script>Data</script\").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script \").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script n\").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script n=\").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script n=\\\"\").select(\"script\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<script>Data</script n=\\\"p\").select(\"script\").first().data());\n    }\n\n    @Test public void handlesUnclosedRawtextAtEof() {\n        assertEquals(\"Data\", Jsoup.parse(\"<style>Data\").select(\"style\").first().data());\n        assertEquals(\"Data</st\", Jsoup.parse(\"<style>Data</st\").select(\"style\").first().data());\n        assertEquals(\"Data\", Jsoup.parse(\"<style>Data</style>\").select(\"style\").first().data());\n        assertEquals(\"Data</style\", Jsoup.parse(\"<style>Data</style\").select(\"style\").first().data());\n        assertEquals(\"Data</-style\", Jsoup.parse(\"<style>Data</-style\").select(\"style\").first().data());\n        assertEquals(\"Data</style-\", Jsoup.parse(\"<style>Data</style-\").select(\"style\").first().data());\n        assertEquals(\"Data</style--\", Jsoup.parse(\"<style>Data</style--\").select(\"style\").first().data());\n    }\n\n    @Test public void noImplicitFormForTextAreas() {\n        // old jsoup parser would create implicit forms for form children like <textarea>, but no more\n        Document doc = Jsoup.parse(\"<textarea>One</textarea>\");\n        assertEquals(\"<textarea>One</textarea>\", doc.body().html());\n    }\n\n    @Test public void handlesEscapedScript() {\n        Document doc = Jsoup.parse(\"<script><!-- one <script>Blah</script> --></script>\");\n        assertEquals(\"<!-- one <script>Blah</script> -->\", doc.select(\"script\").first().data());\n    }\n\n    @Test public void handles0CharacterAsText() {\n        Document doc = Jsoup.parse(\"0<p>0</p>\");\n        assertEquals(\"0\\n<p>0</p>\", doc.body().html());\n    }\n\n    @Test public void handlesNullInData() {\n        Document doc = Jsoup.parse(\"<p id=\\u0000>Blah \\u0000</p>\");\n        assertEquals(\"<p id=\\\"\\uFFFD\\\">Blah</p>\", doc.body().html()); // replaced in attr, discarded in data\n    }\n\n    @Test public void handlesNullInComments() {\n        Document doc = Jsoup.parse(\"<body><!-- \\u0000 \\u0000 -->\");\n        assertEquals(\"<!-- \\uFFFD \\uFFFD -->\", doc.body().html());\n    }\n\n    @Test public void handlesNewlinesAndWhitespaceInTag() {\n        Document doc = Jsoup.parse(\"<a \\n href=\\\"one\\\" \\r\\n id=\\\"two\\\" \\f >\");\n        assertEquals(\"<a href=\\\"one\\\" id=\\\"two\\\"></a>\", doc.body().html());\n    }\n\n    @Test public void handlesWhitespaceInoDocType() {\n        String html = \"<!DOCTYPE html\\r\\n\" +\n            \"      PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\"\\r\\n\" +\n            \"      \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\", doc.childNode(0).outerHtml());\n    }\n\n    @Test public void tracksErrorsWhenRequested() {\n        String html = \"<p>One</p href='no'>\\n<!DOCTYPE html>\\n&arrgh;<font />&#33 &amp &#x110000;<br /></div><foo\";\n        Parser parser = Parser.htmlParser().setTrackErrors(500);\n        Document doc = Jsoup.parse(html, \"http://example.com\", parser);\n\n        List<ParseError> errors = parser.getErrors();\n        assertEquals(10, errors.size());\n        assertEquals(\"<1:21>: Attributes incorrectly present on end tag [/p]\", errors.get(0).toString());\n        assertEquals(\"<2:16>: Unexpected Doctype token [<!doctype html>] when in state [InBody]\", errors.get(1).toString());\n        assertEquals(\"<3:2>: Invalid character reference: invalid named reference [arrgh]\", errors.get(2).toString());\n        assertEquals(\"<3:16>: Tag [font] cannot be self-closing; not a void tag\", errors.get(3).toString());\n        assertEquals(\"<3:20>: Invalid character reference: missing semicolon on [&#33]\", errors.get(4).toString());\n        assertEquals(\"<3:25>: Invalid character reference: missing semicolon on [&amp]\", errors.get(5).toString());\n        assertEquals(\"<3:36>: Invalid character reference: character [1114112] outside of valid range\", errors.get(6).toString());\n        assertEquals(\"<3:48>: Unexpected EndTag token [</div>] when in state [InBody]\", errors.get(7).toString());\n        assertEquals(\"<3:53>: Unexpectedly reached end of file (EOF) in input state [TagName]\", errors.get(8).toString());\n        assertEquals(\"<3:53>: Unexpected EOF token [] when in state [InBody]\", errors.get(9).toString());\n    }\n\n    @Test public void tracksLimitedErrorsWhenRequested() {\n        String html = \"<p>One</p href='no'>\\n<!DOCTYPE html>\\n&arrgh;<font /><br /><foo\";\n        Parser parser = Parser.htmlParser().setTrackErrors(3);\n        Document doc = parser.parseInput(html, \"http://example.com\");\n\n        List<ParseError> errors = parser.getErrors();\n        assertEquals(3, errors.size());\n        assertEquals(\"<1:21>: Attributes incorrectly present on end tag [/p]\", errors.get(0).toString());\n        assertEquals(\"<2:16>: Unexpected Doctype token [<!doctype html>] when in state [InBody]\", errors.get(1).toString());\n        assertEquals(\"<3:2>: Invalid character reference: invalid named reference [arrgh]\", errors.get(2).toString());\n    }\n\n    @Test public void noErrorsByDefault() {\n        String html = \"<p>One</p href='no'>&arrgh;<font /><br /><foo\";\n        Parser parser = Parser.htmlParser();\n        Document doc = Jsoup.parse(html, \"http://example.com\", parser);\n\n        List<ParseError> errors = parser.getErrors();\n        assertEquals(0, errors.size());\n    }\n\n    @Test public void optionalPClosersAreNotErrors() {\n        String html = \"<body><div><p>One<p>Two</div></body>\";\n        Parser parser = Parser.htmlParser().setTrackErrors(128);\n        Document doc = Jsoup.parse(html, \"\", parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(0, errors.size());\n    }\n\n    @Test public void handlesCommentsInTable() {\n        String html = \"<table><tr><td>text</td><!-- Comment --></tr></table>\";\n        Document node = Jsoup.parseBodyFragment(html);\n        assertEquals(\"<html><head></head><body><table><tbody><tr><td>text</td><!-- Comment --></tr></tbody></table></body></html>\", TextUtil.stripNewlines(node.outerHtml()));\n    }\n\n    @Test public void handlesQuotesInCommentsInScripts() {\n        String html = \"<script>\\n\" +\n            \"  <!--\\n\" +\n            \"    document.write('</scr' + 'ipt>');\\n\" +\n            \"  // -->\\n\" +\n            \"</script>\";\n        Document node = Jsoup.parseBodyFragment(html);\n        assertEquals(\"<script>\\n\" +\n            \"  <!--\\n\" +\n            \"    document.write('</scr' + 'ipt>');\\n\" +\n            \"  // -->\\n\" +\n            \"</script>\", node.body().html());\n    }\n\n    @Test public void handleNullContextInParseFragment() {\n        String html = \"<ol><li>One</li></ol><p>Two</p>\";\n        List<Node> nodes = Parser.parseFragment(html, null, \"http://example.com/\");\n        assertEquals(1, nodes.size()); // returns <html> node (not document) -- no context means doc gets created\n        assertEquals(\"html\", nodes.get(0).nodeName());\n        assertEquals(\"<html> <head></head> <body> <ol> <li>One</li> </ol> <p>Two</p> </body> </html>\", StringUtil.normaliseWhitespace(nodes.get(0).outerHtml()));\n    }\n\n    @Test public void doesNotFindExtendedPrefixMatchingEntity() {\n        // only base entities, not extended entities, should allow prefix match (i.e., those in the spec named list that don't include a trailing ; - https://html.spec.whatwg.org/multipage/named-characters.html)\n        String html = \"One &clubsuite; &clubsuit;\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(StringUtil.normaliseWhitespace(\"One &amp;clubsuite; ♣\"), doc.body().html());\n    }\n\n    @Test public void relaxedBaseEntityMatchAndStrictExtendedMatch() {\n        // extended entities need a ; at the end to match, base does not\n        String html = \"&amp &quot &reg &icy &hopf &icy; &hopf;\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended).charset(\"ascii\"); // modifies output only to clarify test\n        assertEquals(\"&amp; \\\" &reg; &amp;icy &amp;hopf &icy; &hopf;\", doc.body().html());\n    }\n\n    @Test public void findsBasePrefixEntity() {\n        // https://github.com/jhy/jsoup/issues/2207\n        String html = \"a&nbspc&shyc I'm &notit; I tell you. I'm &notin; I tell you.\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended).charset(\"ascii\");\n        assertEquals(\"a&nbsp;c&shy;c I'm &not;it; I tell you. I'm &notin; I tell you.\", doc.body().html());\n        assertEquals(\"a cc I'm ¬it; I tell you. I'm ∉ I tell you.\", doc.body().text());\n\n        // and in an attribute:\n        html = \"<a title=\\\"&nbspc&shyc I'm &notit; I tell you. I'm &notin; I tell you.\\\">One</a>\";\n        doc = Jsoup.parse(html);\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended).charset(\"ascii\");\n        Element el = doc.expectFirst(\"a\");\n        assertEquals(\"<a title=\\\"&amp;nbspc&amp;shyc I'm &amp;notit; I tell you. I'm &notin; I tell you.\\\">One</a>\", el.outerHtml());\n        assertEquals(\"&nbspc&shyc I'm &notit; I tell you. I'm ∉ I tell you.\", el.attr(\"title\"));\n    }\n\n    @Test public void handlesXmlDeclarationAsBogusComment() {\n        String html = \"<?xml encoding='UTF-8' ?><body>One</body>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<!--?xml encoding='UTF-8' ?--> <html> <head></head> <body>One</body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n    }\n\n    @Test public void handlesTagsInTextarea() {\n        String html = \"<textarea><p>Jsoup</p></textarea>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<textarea>&lt;p&gt;Jsoup&lt;/p&gt;</textarea>\", doc.body().html());\n    }\n\n    // form tests\n    @Test public void createsFormElements() {\n        String html = \"<body><form><input id=1><input id=2></form></body>\";\n        Document doc = Jsoup.parse(html);\n        Element el = doc.select(\"form\").first();\n\n        assertTrue(el instanceof FormElement, \"Is form element\");\n        FormElement form = (FormElement) el;\n        Elements controls = form.elements();\n        assertEquals(2, controls.size());\n        assertEquals(\"1\", controls.get(0).id());\n        assertEquals(\"2\", controls.get(1).id());\n    }\n\n    @Test public void associatedFormControlsWithDisjointForms() {\n        // form gets closed, isn't parent of controls\n        String html = \"<table><tr><form><input type=hidden id=1><td><input type=text id=2></td><tr></table>\";\n        Document doc = Jsoup.parse(html);\n        Element el = doc.select(\"form\").first();\n\n        assertTrue(el instanceof FormElement, \"Is form element\");\n        FormElement form = (FormElement) el;\n        Elements controls = form.elements();\n        assertEquals(2, controls.size());\n        assertEquals(\"1\", controls.get(0).id());\n        assertEquals(\"2\", controls.get(1).id());\n\n        assertEquals(\"<table><tbody><tr><form></form><input type=\\\"hidden\\\" id=\\\"1\\\"><td><input type=\\\"text\\\" id=\\\"2\\\"></td></tr><tr></tr></tbody></table>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void handlesInputInTable() {\n        String h = \"<body>\\n\" +\n            \"<input type=\\\"hidden\\\" name=\\\"a\\\" value=\\\"\\\">\\n\" +\n            \"<table>\\n\" +\n            \"<input type=\\\"hidden\\\" name=\\\"b\\\" value=\\\"\\\" />\\n\" +\n            \"</table>\\n\" +\n            \"</body>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(1, doc.select(\"table input\").size());\n        assertEquals(2, doc.select(\"input\").size());\n    }\n\n    @Test public void convertsImageToImg() {\n        // image to img, unless in a svg. old html cruft.\n        String h = \"<body><image><svg><image /></svg></body>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"<img>\\n<svg>\\n <image />\\n</svg>\", doc.body().html());\n    }\n\n    @Test public void handlesInvalidDoctypes() {\n        // would previously throw invalid name exception on empty doctype\n        Document doc = Jsoup.parse(\"<!DOCTYPE>\");\n        assertEquals(\n            \"<!doctype> <html> <head></head> <body></body> </html>\",\n            StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        doc = Jsoup.parse(\"<!DOCTYPE><html><p>Foo</p></html>\");\n        assertEquals(\n            \"<!doctype> <html> <head></head> <body> <p>Foo</p> </body> </html>\",\n            StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        doc = Jsoup.parse(\"<!DOCTYPE \\u0000>\");\n        assertEquals(\n            \"<!doctype �> <html> <head></head> <body></body> </html>\",\n            StringUtil.normaliseWhitespace(doc.outerHtml()));\n    }\n\n    @Test public void handlesManyChildren() {\n        // Arrange\n        StringBuilder longBody = new StringBuilder(500000);\n        for (int i = 0; i < 25000; i++) {\n            longBody.append(i).append(\"<br>\");\n        }\n\n        // Act\n        long start = System.currentTimeMillis();\n        Document doc = Parser.parseBodyFragment(longBody.toString(), \"\");\n\n        // Assert\n        assertEquals(50000, doc.body().childNodeSize());\n        assertTrue(System.currentTimeMillis() - start < 1000);\n    }\n\n    @Test\n    public void testInvalidTableContents() throws IOException {\n        File in = ParseTest.getFile(\"/htmltests/table-invalid-elements.html\");\n        Document doc = Jsoup.parse(in, \"UTF-8\");\n        doc.outputSettings().prettyPrint(true);\n        String rendered = doc.toString();\n        int endOfEmail = rendered.indexOf(\"Comment\");\n        int guarantee = rendered.indexOf(\"Why am I here?\");\n        assertTrue(endOfEmail > -1, \"Comment not found\");\n        assertTrue(guarantee > -1, \"Search text not found\");\n        assertTrue(guarantee > endOfEmail, \"Search text did not come after comment\");\n    }\n\n    @Test public void testNormalisesIsIndex() {\n        Document doc = Jsoup.parse(\"<body><isindex action='/submit'></body>\");\n        // There used to be rules so this became: <form action=\"/submit\"> <hr><label>This is a searchable index. Enter search keywords: <input name=\"isindex\"></label> <hr> </form>\n        assertEquals(\"<isindex action=\\\"/submit\\\"></isindex>\",\n            StringUtil.normaliseWhitespace(doc.body().html()));\n    }\n\n    @Test public void testReinsertionModeForThCelss() {\n        String body = \"<body> <table> <tr> <th> <table><tr><td></td></tr></table> <div> <table><tr><td></td></tr></table> </div> <div></div> <div></div> <div></div> </th> </tr> </table> </body>\";\n        Document doc = Jsoup.parse(body);\n        assertEquals(1, doc.body().children().size());\n    }\n\n    @Test public void testUsingSingleQuotesInQueries() {\n        String body = \"<body> <div class='main'>hello</div></body>\";\n        Document doc = Jsoup.parse(body);\n        Elements main = doc.select(\"div[class='main']\");\n        assertEquals(\"hello\", main.text());\n    }\n\n    @Test public void testSupportsNonAsciiTags() {\n        String body = \"<a進捗推移グラフ>Yes</a進捗推移グラフ><bрусский-тэг>Correct</<bрусский-тэг>\";\n        Document doc = Jsoup.parse(body);\n        Elements els = doc.select(\"a進捗推移グラフ\");\n        assertEquals(\"Yes\", els.text());\n        els = doc.select(\"bрусский-тэг\");\n        assertEquals(\"Correct\", els.text());\n    }\n\n    @Test public void testSupportsPartiallyNonAsciiTags() {\n        String body = \"<div>Check</divá>\";\n        Document doc = Jsoup.parse(body);\n        Elements els = doc.select(\"div\");\n        assertEquals(\"Check\", els.text());\n    }\n\n    @Test public void testFragment() {\n        // make sure when parsing a body fragment, a script tag at start goes into the body\n        String html =\n            \"<script type=\\\"text/javascript\\\">console.log('foo');</script>\\n\" +\n                \"<div id=\\\"somecontent\\\">some content</div>\\n\" +\n                \"<script type=\\\"text/javascript\\\">console.log('bar');</script>\";\n\n        Document body = Jsoup.parseBodyFragment(html);\n        assertEquals(\"<script type=\\\"text/javascript\\\">console.log('foo');</script>\\n\" +\n            \"<div id=\\\"somecontent\\\">some content</div>\\n\" +\n            \"<script type=\\\"text/javascript\\\">console.log('bar');</script>\", body.body().html());\n    }\n\n    @Test public void testHtmlLowerCase() {\n        String html = \"<!doctype HTML><DIV ID=1>One</DIV>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<!doctype html> <html> <head></head> <body> <div id=\\\"1\\\">One</div> </body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        Element div = doc.selectFirst(\"#1\");\n        div.after(\"<TaG>One</TaG>\");\n        assertEquals(\"<tag>One</tag>\", TextUtil.stripNewlines(div.nextElementSibling().outerHtml()));\n    }\n\n    @Test public void testHtmlLowerCaseAttributesOfVoidTags() {\n        String html = \"<!doctype HTML><IMG ALT=One></DIV>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<!doctype html> <html> <head></head> <body> <img alt=\\\"One\\\"> </body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n    }\n\n    @Test public void testHtmlLowerCaseAttributesForm() {\n        String html = \"<form NAME=one>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<form name=\\\"one\\\"></form>\", StringUtil.normaliseWhitespace(doc.body().html()));\n    }\n\n    @Test public void canPreserveTagCase() {\n        Parser parser = Parser.htmlParser();\n        parser.settings(new ParseSettings(true, false));\n        Document doc = parser.parseInput(\"<div id=1><SPAN ID=2>\", \"\");\n        assertEquals(\"<html> <head></head> <body> <div id=\\\"1\\\"> <SPAN id=\\\"2\\\"></SPAN> </div> </body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        Element div = doc.selectFirst(\"#1\");\n        div.after(\"<TaG ID=one>One</TaG>\");\n        assertEquals(\"<TaG id=\\\"one\\\">One</TaG>\", TextUtil.stripNewlines(div.nextElementSibling().outerHtml()));\n    }\n\n    @Test public void canPreserveAttributeCase() {\n        Parser parser = Parser.htmlParser();\n        parser.settings(new ParseSettings(false, true));\n        Document doc = parser.parseInput(\"<div id=1><SPAN ID=2>\", \"\");\n        assertEquals(\"<html> <head></head> <body> <div id=\\\"1\\\"> <span ID=\\\"2\\\"></span> </div> </body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        Element div = doc.selectFirst(\"#1\");\n        div.after(\"<TaG ID=one>One</TaG>\");\n        assertEquals(\"<tag ID=\\\"one\\\">One</tag>\", TextUtil.stripNewlines(div.nextElementSibling().outerHtml()));\n    }\n\n    @Test public void canPreserveBothCase() {\n        Parser parser = Parser.htmlParser();\n        parser.settings(new ParseSettings(true, true));\n        Document doc = parser.parseInput(\"<div id=1><SPAN ID=2>\", \"\");\n        assertEquals(\"<html> <head></head> <body> <div id=\\\"1\\\"> <SPAN ID=\\\"2\\\"></SPAN> </div> </body> </html>\", StringUtil.normaliseWhitespace(doc.outerHtml()));\n\n        Element div = doc.selectFirst(\"#1\");\n        div.after(\"<TaG ID=one>One</TaG>\");\n        assertEquals(\"<TaG ID=\\\"one\\\">One</TaG>\", TextUtil.stripNewlines(div.nextElementSibling().outerHtml()));\n    }\n\n    @Test public void handlesControlCodeInAttributeName() {\n        Document doc = Jsoup.parse(\"<p><a \\06=foo>One</a><a/\\06=bar><a foo\\06=bar>Two</a></p>\");\n        assertEquals(\"<p><a>One</a><a></a><a foo=\\\"bar\\\">Two</a></p>\", doc.body().html());\n    }\n\n    @Test public void caseSensitiveParseTree() {\n        String html = \"<r><X>A</X><y>B</y></r>\";\n        Parser parser = Parser.htmlParser();\n        parser.settings(preserveCase);\n        Document doc = parser.parseInput(html, \"\");\n        assertEquals(\"<r>\\n <X>A</X><y>B</y>\\n</r>\", doc.body().html());\n    }\n\n    @Test public void caseInsensitiveParseTree() {\n        String html = \"<r><X>A</X><y>B</y></r>\";\n        Parser parser = Parser.htmlParser();\n        Document doc = parser.parseInput(html, \"\");\n        assertEquals(\"<r>\\n <x>A</x><y>B</y>\\n</r>\", doc.body().html());\n    }\n\n    @Test public void preservedCaseLinksCantNest() {\n        String html = \"<A>ONE <A>Two</A></A>\";\n        Document doc = Parser.htmlParser()\n            .settings(preserveCase)\n            .parseInput(html, \"\");\n        assertEquals(\"<A>ONE </A><A>Two</A>\", doc.body().html());\n    }\n\n    @Test public void normalizesDiscordantTags() {\n        Document document = Jsoup.parse(\"<div>test</DIV><p></p>\");\n        assertEquals(\"<div>test</div>\\n<p></p>\", document.body().html());\n    }\n\n    @Test public void selfClosingVoidIsNotAnError() {\n        String html = \"<p>test<br/>test<br/></p>\";\n        Parser parser = Parser.htmlParser().setTrackErrors(5);\n        parser.parseInput(html, \"\");\n        assertEquals(0, parser.getErrors().size());\n\n        assertTrue(Jsoup.isValid(html, Safelist.basic()));\n        String clean = Jsoup.clean(html, Safelist.basic());\n        assertEquals(\"<p>test\\n <br>\\n test\\n <br></p>\", clean);\n    }\n\n    @Test public void selfClosingOnNonvoidIsError() {\n        String html = \"<p>test</p>\\n\\n<div /><div>Two</div>\";\n        Parser parser = Parser.htmlParser().setTrackErrors(5);\n        parser.parseInput(html, \"\");\n        assertErrorsContain(\"<3:8>: Tag [div] cannot be self-closing; not a void tag\", parser.getErrors());\n\n        assertFalse(Jsoup.isValid(html, Safelist.relaxed()));\n        String clean = Jsoup.clean(html, Safelist.relaxed());\n        assertEquals(\"<p>test</p> <div> <div>Two</div> </div>\", StringUtil.normaliseWhitespace(clean)); // did not close\n    }\n\n    @Test public void testTemplateInsideTable() throws IOException {\n        File in = ParseTest.getFile(\"/htmltests/table-polymer-template.html\");\n        Document doc = Jsoup.parse(in, \"UTF-8\");\n        doc.outputSettings().prettyPrint(true);\n\n        Elements templates = doc.body().getElementsByTag(\"template\");\n        for (Element template : templates) {\n            assertTrue(template.childNodes().size() > 1);\n        }\n    }\n\n    @Test public void testHandlesDeepSpans() {\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < 200; i++) {\n            sb.append(\"<span>\");\n        }\n\n        sb.append(\"<p>One</p>\");\n\n        Document doc = Jsoup.parse(sb.toString());\n        assertEquals(200, doc.select(\"span\").size());\n        assertEquals(1, doc.select(\"p\").size());\n    }\n\n    @Test public void commentAtEnd() {\n        Document doc = Jsoup.parse(\"<!\");\n        assertTrue(doc.childNode(0) instanceof Comment);\n    }\n\n    @Test public void preSkipsFirstNewline() {\n        Document doc = Jsoup.parse(\"<pre>\\n\\nOne\\nTwo\\n</pre>\");\n        Element pre = doc.selectFirst(\"pre\");\n        assertEquals(\"One\\nTwo\", pre.text());\n        assertEquals(\"\\nOne\\nTwo\\n\", pre.wholeText());\n    }\n\n    @Test public void handlesXmlDeclAndCommentsBeforeDoctype() throws IOException {\n        File in = ParseTest.getFile(\"/htmltests/comments.html\");\n        Document doc = Jsoup.parse(in, \"UTF-8\");\n\n        // split out to confirm comment nodes indent correct\n        assertEquals(\"<!--?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?-->\\n\" +\n            \"<!-- so -->\\n\" +\n            \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\\n\" +\n            \"<!-- what -->\\n\" +\n            \"<html xml:lang=\\\"en\\\" lang=\\\"en\\\" xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n\" +\n            \" <!-- now -->\\n\" +\n            \" <head>\\n\" +\n            \"  <!-- then -->\\n\" +\n            \"  <meta http-equiv=\\\"Content-type\\\" content=\\\"text/html; charset=utf-8\\\">\\n\" +\n            \"  <title>A Certain Kind of Test</title>\\n\" +\n            \" </head>\\n\" +\n            \" <body>\\n\" +\n            \"  <h1>Hello</h1>\\n\" +\n            \"  (There is a UTF8 hidden BOM at the top of this file.)\\n\" +\n            \" </body>\\n\" +\n            \"</html>\", doc.html());\n\n        assertEquals(\"A Certain Kind of Test\", doc.head().select(\"title\").text());\n    }\n\n    @Test public void fallbackToUtfIfCantEncode() throws IOException {\n        // that charset can't be encoded, so make sure we flip to utf\n\n        String in = \"<html><meta charset=\\\"ISO-2022-CN\\\"/>One</html>\";\n        Document doc = Jsoup.parse(new ByteArrayInputStream(in.getBytes()), null, \"\");\n\n        assertEquals(\"UTF-8\", doc.charset().name());\n        assertEquals(\"One\", doc.text());\n\n        String html = doc.outerHtml();\n        assertEquals(\"<html><head><meta charset=\\\"UTF-8\\\"></head><body>One</body></html>\", TextUtil.stripNewlines(html));\n    }\n\n    @Test public void characterReaderBuffer() throws IOException {\n        File in = ParseTest.getFile(\"/htmltests/character-reader-buffer.html.gz\");\n        Document doc = Jsoup.parse(in, \"UTF-8\");\n\n        String expectedHref = \"http://www.domain.com/path?param_one=value&param_two=value\";\n\n        Elements links = doc.select(\"a\");\n        assertEquals(2, links.size());\n        assertEquals(expectedHref, links.get(0).attr(\"href\")); // passes\n        assertEquals(expectedHref, links.get(1).attr(\"href\")); // fails, \"but was:<...ath?param_one=value&[]_two-value>\"\n    }\n\n    @Test\n    public void selfClosingTextAreaDoesntLeaveDroppings() {\n        // https://github.com/jhy/jsoup/issues/1220\n        // must be configured to allow self closing\n        String html = \"<div><div><textarea/></div></div>\";\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().valueOf(\"textarea\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(html, parser);\n        assertFalse(doc.body().html().contains(\"&lt;\"));\n        assertFalse(doc.body().html().contains(\"&gt;\"));\n        assertEquals(\"<div><div><textarea></textarea></div></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    public void testNoSpuriousSpace() {\n        Document doc = Jsoup.parse(\"Just<a>One</a><a>Two</a>\");\n        assertEquals(\"Just<a>One</a><a>Two</a>\", doc.body().html());\n        assertEquals(\"JustOneTwo\", doc.body().text());\n    }\n\n    @Test\n    public void pTagsGetIndented() {\n        String html = \"<div><p><a href=one>One</a><p><a href=two>Two</a></p></div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<div>\\n\" +\n            \" <p><a href=\\\"one\\\">One</a></p>\\n\" +\n            \" <p><a href=\\\"two\\\">Two</a></p>\\n\" +\n            \"</div>\", doc.body().html());\n    }\n\n    @Test\n    public void indentRegardlessOfCase() {\n        String html = \"<p>1</p><P>2</P>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\n            \"<body>\\n\" +\n            \" <p>1</p>\\n\" +\n            \" <p>2</p>\\n\" +\n            \"</body>\", doc.body().outerHtml());\n\n        Document caseDoc = Jsoup.parse(html, \"\", Parser.htmlParser().settings(preserveCase));\n        assertEquals(\n            \"<body>\\n\" +\n            \" <p>1</p>\\n\" +\n            \" <P>2</P>\\n\" +\n            \"</body>\", caseDoc.body().outerHtml());\n    }\n\n    @Test\n    public void testH20() {\n        // https://github.com/jhy/jsoup/issues/731\n        String html = \"H<sub>2</sub>O\";\n        String clean = Jsoup.clean(html, Safelist.basic());\n        assertEquals(\"H<sub>2</sub>O\", clean);\n\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"H2O\", doc.text());\n    }\n\n    @Test\n    public void testUNewlines() {\n        // https://github.com/jhy/jsoup/issues/851\n        String html = \"t<u>es</u>t <b>on</b> <i>f</i><u>ir</u>e\";\n        String clean = Jsoup.clean(html, Safelist.basic());\n        assertEquals(\"t<u>es</u>t <b>on</b> <i>f</i><u>ir</u>e\", clean);\n\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"test on fire\", doc.text());\n    }\n\n    @Test public void testFarsi() {\n        // https://github.com/jhy/jsoup/issues/1227\n        String text = \"نیمه\\u200Cشب\";\n        Document doc = Jsoup.parse(\"<p>\" + text);\n        assertEquals(text, doc.text());\n    }\n\n    @Test public void testStartOptGroup() {\n        // https://github.com/jhy/jsoup/issues/1313\n        String html = \"<select>\\n\" +\n            \"  <optgroup label=\\\"a\\\">\\n\" +\n            \"  <option>one\\n\" +\n            \"  <option>two\\n\" +\n            \"  <option>three\\n\" +\n            \"  <optgroup label=\\\"b\\\">\\n\" +\n            \"  <option>four\\n\" +\n            \"  <option>fix\\n\" +\n            \"  <option>six\\n\" +\n            \"</select>\";\n        Document doc = Jsoup.parse(html);\n        Element select = doc.selectFirst(\"select\");\n        assertEquals(2, select.childrenSize());\n\n        assertEquals(\"<optgroup label=\\\"a\\\"> <option>one </option><option>two </option><option>three </option></optgroup><optgroup label=\\\"b\\\"> <option>four </option><option>fix </option><option>six </option></optgroup>\", select.html());\n    }\n\n    @Test public void readerClosedAfterParse() {\n        Document doc = Jsoup.parse(\"Hello\");\n        TreeBuilder treeBuilder = doc.parser().getTreeBuilder();\n        assertNull(treeBuilder.reader);\n        assertNull(treeBuilder.tokeniser);\n    }\n\n    @Test public void scriptInDataNode() {\n        Document doc = Jsoup.parse(\"<script>Hello</script><style>There</style>\");\n        assertTrue(doc.selectFirst(\"script\").childNode(0) instanceof DataNode);\n        assertTrue(doc.selectFirst(\"style\").childNode(0) instanceof DataNode);\n\n        doc = Jsoup.parse(\"<SCRIPT>Hello</SCRIPT><STYLE>There</STYLE>\", \"\", Parser.htmlParser().settings(preserveCase));\n        assertTrue(doc.selectFirst(\"script\").childNode(0) instanceof DataNode);\n        assertTrue(doc.selectFirst(\"style\").childNode(0) instanceof DataNode);\n    }\n\n    @Test public void textareaValue() {\n        String html = \"<TEXTAREA>YES YES</TEXTAREA>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"YES YES\", doc.selectFirst(\"textarea\").val());\n\n        doc = Jsoup.parse(html, \"\", Parser.htmlParser().settings(preserveCase));\n        assertEquals(\"YES YES\", doc.selectFirst(\"textarea\").val());\n    }\n\n    @Test public void preserveWhitespaceInHead() {\n        String html = \"\\n<!doctype html>\\n<html>\\n<head>\\n<title>Hello</title>\\n</head>\\n<body>\\n<p>One</p>\\n</body>\\n</html>\\n\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().prettyPrint(false);\n        assertEquals(\"<!doctype html>\\n<html>\\n<head>\\n<title>Hello</title>\\n</head>\\n<body>\\n<p>One</p>\\n</body>\\n</html>\\n\", doc.outerHtml());\n    }\n\n    @Test public void handleContentAfterBody() {\n        String html = \"<body>One</body>  <p>Hello!</p></html> <p>There</p>\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().prettyPrint(false);\n        assertEquals(\"<html><head></head><body>One<p>Hello!</p><p>There</p></body>  </html> \", doc.outerHtml());\n    }\n\n    @Test public void preservesTabs() {\n        // testcase to demonstrate tab retention - https://github.com/jhy/jsoup/issues/1240\n        String html = \"<pre>One\\tTwo</pre><span>\\tThree\\tFour</span>\";\n        Document doc = Jsoup.parse(html);\n\n        Element pre = doc.selectFirst(\"pre\");\n        Element span = doc.selectFirst(\"span\");\n\n        assertEquals(\"One\\tTwo\", pre.text());\n        assertEquals(\"Three Four\", span.text()); // normalized, including overall trim\n        assertEquals(\"\\tThree\\tFour\", span.wholeText()); // text normalizes, wholeText retains original spaces incl tabs\n        assertEquals(\"One\\tTwo Three Four\", doc.body().text());\n\n        assertEquals(\"<pre>One\\tTwo</pre>\\n<span> Three Four</span>\", doc.body().html()); // html output provides normalized space, incl tab in pre but not in span\n\n        doc.outputSettings().prettyPrint(false);\n        assertEquals(html, doc.body().html()); // disabling pretty-printing - round-trips the tab throughout, as no normalization occurs\n    }\n\n    @Test void wholeTextTreatsBRasNewline() {\n        String html = \"<div>\\nOne<br>Two <p>Three<br>Four</div>\";\n        Document doc = Jsoup.parse(html);\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        assertEquals(\"\\nOne\\nTwo Three\\nFour\", div.wholeText());\n        assertEquals(\"\\nOne\\nTwo \", div.wholeOwnText());\n    }\n\n    @Test public void canDetectAutomaticallyAddedElements() {\n        String bare = \"<script>One</script>\";\n        String full = \"<html><head><title>Check</title></head><body><p>One</p></body></html>\";\n\n        assertTrue(didAddElements(bare));\n        assertFalse(didAddElements(full));\n    }\n\n    private boolean didAddElements(String input) {\n        // two passes, one as XML and one as HTML. XML does not vivify missing/optional tags\n        Document html = Jsoup.parse(input);\n        Document xml = Jsoup.parse(input, \"\", Parser.xmlParser());\n\n        int htmlElementCount = html.getAllElements().size();\n        int xmlElementCount = xml.getAllElements().size();\n        return htmlElementCount > xmlElementCount;\n    }\n\n    @Test public void canSetHtmlOnCreatedTableElements() {\n        // https://github.com/jhy/jsoup/issues/1603\n        Element element = new Element(\"tr\");\n        element.html(\"<tr><td>One</td></tr>\");\n        assertEquals(\"<tr>\\n <tr>\\n  <td>One</td>\\n </tr>\\n</tr>\", element.outerHtml());\n    }\n\n    @Test public void parseFragmentOnCreatedDocument() {\n        // https://github.com/jhy/jsoup/issues/1601\n        String bareFragment = \"<h2>text</h2>\";\n        List<Node> nodes = new Document(\"\").parser().parseFragmentInput(bareFragment, new Element(\"p\"), \"\");\n        assertEquals(1, nodes.size());\n        Node node = nodes.get(0);\n        assertEquals(\"h2\", node.nodeName());\n        assertEquals(\"<p>\\n <h2>text</h2>\\n</p>\", node.parent().outerHtml());\n    }\n\n    @Test public void nestedPFragments() {\n        // https://github.com/jhy/jsoup/issues/1602\n        String bareFragment = \"<p></p><a></a>\";\n        List<Node> nodes = new Document(\"\").parser().parseFragmentInput(bareFragment, new Element(\"p\"), \"\");\n        assertEquals(2, nodes.size());\n        Node node = nodes.get(0);\n        assertEquals(\"<p><p></p><a></a></p>\", TextUtil.stripNewlines(node.parent().outerHtml())); // mis-nested because fragment forced into the element, OK\n    }\n\n    @Test public void nestedAnchorAdoption() {\n        // https://github.com/jhy/jsoup/issues/1608\n        String html = \"<a>\\n<b>\\n<div>\\n<a>test</a>\\n</div>\\n</b>\\n</a>\";\n        Document doc = Jsoup.parse(html);\n        assertNotNull(doc);\n        assertEquals(\"<a> <b> </b></a><b><div><a> </a><a>test</a></div> </b>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void adoption() throws IOException {\n        // https://github.com/jhy/jsoup/issues/2267\n        File input = ParseTest.getFile(\"/htmltests/adopt-1.html\");\n        Document doc = Jsoup.parse(input);\n        assertEquals(\"TEXT-AAA TEXT-BBB TEXT-CCC TEXT-DDD\", doc.text());\n    }\n\n    @Test public void tagsMustStartWithAscii() {\n        // https://github.com/jhy/jsoup/issues/1006\n        String[] valid = {\"a一\", \"a会员挂单金额5\", \"table(╯°□°)╯\"};\n        String[] invalid = {\"一\", \"会员挂单金额5\", \"(╯°□°)╯\"};\n\n        for (String tag : valid) {\n            Document doc = Jsoup.parse(\"<\" + tag + \">Text</\" + tag + \">\");\n            Elements els = doc.getElementsByTag(tag);\n            assertEquals(1, els.size());\n            assertEquals(tag, els.get(0).tagName());\n            assertEquals(\"Text\", els.get(0).text());\n        }\n\n        for (String tag : invalid) {\n            Document doc = Jsoup.parse(\"<\" + tag + \">Text</\" + tag + \">\");\n            Elements els = doc.getElementsByTag(tag);\n            assertEquals(0, els.size());\n            assertEquals(\"&lt;\" + tag + \"&gt;Text<!--/\" + tag + \"-->\", doc.body().html());\n        }\n    }\n\n    @Test void htmlOutputCorrectsInvalidAttributeNames() {\n        String html = \"<body style=\\\"color: red\\\" \\\" name\\\"><div =\\\"\\\"></div></body>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(Document.OutputSettings.Syntax.html, doc.outputSettings().syntax());\n\n        String out = doc.body().outerHtml();\n        assertEquals(\"<body style=\\\"color: red\\\" _ name_>\\n <div _></div>\\n</body>\", out);\n    }\n\n    @Test void templateInHead() {\n        // https://try.jsoup.org/~EGp3UZxQe503TJDHQEQEzm8IeUc\n        String html = \"<head><template id=1><meta name=tmpl></template><title>Test</title><style>One</style></head><body><p>Two</p>\";\n        Document doc = Jsoup.parse(html);\n\n        String want = \"<html><head><template id=\\\"1\\\"><meta name=\\\"tmpl\\\"></template><title>Test</title><style>One</style></head><body><p>Two</p></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n\n        Elements template = doc.select(\"template#1\");\n        template.select(\"meta\").attr(\"content\", \"Yes\");\n        template.unwrap();\n\n        want = \"<html><head><meta name=\\\"tmpl\\\" content=\\\"Yes\\\"><title>Test</title><style>One</style></head><body><p>Two</p></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void nestedTemplateInBody() {\n        String html = \"<body><template id=1><table><tr><template id=2><td>One</td><td>Two</td></template></tr></template></body>\";\n        Document doc = Jsoup.parse(html);\n\n        String want = \"<html><head></head><body><template id=\\\"1\\\"><table><tbody><tr><template id=\\\"2\\\"><td>One</td><td>Two</td></template></tr></tbody></table></template></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n\n        // todo - will be nice to add some simpler template element handling like clone children etc?\n        Element tmplTbl = doc.selectFirst(\"template#1\");\n        Element tmplRow = doc.selectFirst(\"template#2\");\n        assertNotNull(tmplRow);\n        assertNotNull(tmplTbl);\n        tmplRow.appendChild(tmplRow.clone());\n        doc.select(\"template\").unwrap();\n\n        want = \"<html><head></head><body><table><tbody><tr><td>One</td><td>Two</td><td>One</td><td>Two</td></tr></tbody></table></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void canSelectIntoTemplate() {\n        String html = \"<body><div><template><p>Hello</p>\";\n        Document doc = Jsoup.parse(html);\n        String want = \"<html><head></head><body><div><template><p>Hello</p></template></div></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n\n        Element p = doc.selectFirst(\"div p\");\n        Element p1 = doc.selectFirst(\"template :containsOwn(Hello)\");\n        assertEquals(\"p\", p.normalName());\n        assertEquals(p, p1);\n    }\n\n    @Test void tableRowFragment() {\n        Document doc = Jsoup.parse(\"<body><table></table></body\");\n        String html = \"<tr><td><img></td></tr>\";\n        Element table = doc.selectFirst(\"table\");\n        table.html(html); // invokes the fragment parser with table as context\n        String want = \"<tbody><tr><td><img></td></tr></tbody>\";\n        assertEquals(want, TextUtil.stripNewlines(table.html()));\n        want = \"<table><tbody><tr><td><img></td></tr></tbody></table>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void templateTableRowFragment() {\n        // https://github.com/jhy/jsoup/issues/1409 (per the fragment <tr> use case)\n        Document doc = Jsoup.parse(\"<body><table><template></template></table></body\");\n        String html = \"<tr><td><img></td></tr>\";\n        Element tmpl = doc.selectFirst(\"template\");\n        tmpl.html(html); // invokes the fragment parser with template as context\n        String want = \"<tr><td><img></td></tr>\";\n        assertEquals(want, TextUtil.stripNewlines(tmpl.html()));\n        tmpl.unwrap();\n\n        want = \"<html><head></head><body><table><tr><td><img></td></tr></table></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void templateNotInTableRowFragment() {\n        // https://github.com/jhy/jsoup/issues/1409 (per the fragment <tr> use case)\n        Document doc = Jsoup.parse(\"<body><template></template></body\");\n        String html = \"<tr><td><img></td></tr>\";\n        Element tmpl = doc.selectFirst(\"template\");\n        tmpl.html(html); // invokes the fragment parser with template as context\n        String want = \"<tr><td><img></td></tr>\";\n        assertEquals(want, TextUtil.stripNewlines(tmpl.html()));\n        tmpl.unwrap();\n\n        want = \"<html><head></head><body><tr><td><img></td></tr></body></html>\";\n        assertEquals(want, TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void templateFragment() {\n        // https://github.com/jhy/jsoup/issues/1315\n        String html = \"<template id=\\\"lorem-ipsum\\\"><tr><td>Lorem</td><td>Ipsum</td></tr></template>\";\n        Document frag = Jsoup.parseBodyFragment(html);\n        String want = \"<template id=\\\"lorem-ipsum\\\"><tr><td>Lorem</td><td>Ipsum</td></tr></template>\";\n        assertEquals(want, TextUtil.stripNewlines(frag.body().html()));\n    }\n\n    @Test void templateInferredForm() {\n        // https://github.com/jhy/jsoup/issues/1637 | https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38987\n        Document doc = Jsoup.parse(\"<template><isindex action>\");\n        assertNotNull(doc);\n        assertEquals(\"<template><isindex action></isindex></template>\",\n            TextUtil.stripNewlines(doc.head().html()));\n    }\n\n    @Test void trimNormalizeElementNamesInBuilder() {\n        // https://github.com/jhy/jsoup/issues/1637 | https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=38983\n        // This is interesting - in TB state, the element name was \"template\\u001E\", so no name checks matched. Then,\n        // when the Element is created, the name got normalized to \"template\" and so looked like there should be a\n        // template on the stack during resetInsertionMode for the select.\n        // The issue was that the normalization in Tag.valueOf did a trim which the Token.Tag did not\n        Document doc = Jsoup.parse(\"<template\\u001E><select><input>\");\n        assertNotNull(doc);\n        assertEquals(\"<template><select></select><input></template>\",\n            TextUtil.stripNewlines(doc.head().html()));\n    }\n\n    @Test void templateInLi() {\n        // https://github.com/jhy/jsoup/issues/2258\n        String html = \"<ul><li>L1</li><li>L2 <template><li>T1</li><li>T2</template></li><li>L3</ul>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<ul><li>L1</li><li>L2<template><li>T1</li><li>T2</li></template></li><li>L3</li></ul>\",\n            TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void templateInButton() {\n        // https://github.com/jhy/jsoup/issues/2271\n        String html = \"<button><template><button></button></template></button>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(html, TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void errorsBeforeHtml() {\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(\"<!doctype html><!doctype something></div>\", parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(2, errors.size());\n        assertEquals(\"<1:36>: Unexpected Doctype token [<!doctype something>] when in state [BeforeHtml]\", errors.get(0).toString());\n        assertEquals(\"<1:42>: Unexpected EndTag token [</div>] when in state [BeforeHtml]\", errors.get(1).toString());\n        assertEquals(\"<!doctype html><html><head></head><body></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void afterHeadReAdds() {\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(\"<head></head><meta charset=UTF8><p>Hello\", parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(1, errors.size());\n        assertEquals(\"<1:33>: Unexpected StartTag token [<meta  charset=\\\"UTF8\\\">] when in state [AfterHead]\", errors.get(0).toString());\n        assertEquals(\"<html><head><meta charset=\\\"UTF8\\\"></head><body><p>Hello</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n        // meta gets added back into head\n    }\n\n    @Test void mergeHtmlAttributesFromBody() {\n        Document doc = Jsoup.parse(\"<html id=1 class=foo><body><html class=bar data=x><p>One\");\n        assertEquals(\"<html id=\\\"1\\\" class=\\\"foo\\\" data=\\\"x\\\"><head></head><body><p>One</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void mergeHtmlNoAttributesFromBody() {\n        Document doc = Jsoup.parse(\"<html id=1 class=foo><body><html><p>One\");\n        assertEquals(\"<html id=\\\"1\\\" class=\\\"foo\\\"><head></head><body><p>One</p></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test void supportsRuby() {\n        String html = \"<ruby><rbc><rb>10</rb><rb>31</rb><rb>2002</rb></rbc><rtc><rt>Month</rt><rt>Day</rt><rt>Year</rt></rtc><rtc><rt>Expiration Date</rt><rp>(*)</rtc></ruby>\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(3, errors.size());\n        Element ruby = doc.expectFirst(\"ruby\");\n        assertEquals(\n            \"<ruby><rbc><rb>10</rb><rb>31</rb><rb>2002</rb></rbc><rtc><rt>Month</rt><rt>Day</rt><rt>Year</rt></rtc><rtc><rt>Expiration Date</rt><rp>(*)</rp></rtc></ruby>\",\n            TextUtil.stripNewlines(ruby.outerHtml()));\n        assertEquals(\"<1:38>: Unexpected StartTag token [<rb>] when in state [InBody]\", errors.get(2).toString()); // 3 errors from rb in rtc as undefined\n    }\n\n    @Test void rubyRpRtImplicitClose() {\n        String html = \"<ruby><rp>(<rt>Hello<rt>Hello<rp>)</ruby>\\n\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(0, parser.getErrors().size());\n        Element ruby = doc.expectFirst(\"ruby\");\n        assertEquals(\n            \"<ruby><rp>(</rp><rt>Hello</rt><rt>Hello</rt><rp>)</rp></ruby>\",\n            TextUtil.stripNewlines(ruby.outerHtml()));\n    }\n\n    @Test void rubyScopeError() {\n        String html = \"<ruby><div><rp>Hello\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(2, errors.size());\n        Element ruby = doc.expectFirst(\"ruby\");\n        assertEquals(\n            \"<ruby><div><rp>Hello</rp></div></ruby>\",\n            TextUtil.stripNewlines(ruby.outerHtml()));\n        assertEquals(\"<1:16>: Unexpected StartTag token [<rp>] when in state [InBody]\", errors.get(0).toString());\n    }\n\n    @Test void errorOnEofIfOpen() {\n        String html = \"<div>\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(1, errors.size());\n        assertEquals(\"Unexpected EOF token [] when in state [InBody]\", errors.get(0).getErrorMessage());\n    }\n\n    @Test void NoErrorOnEofIfBodyOpen() {\n        String html = \"<body>\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(0, errors.size());\n    }\n\n    @Test void htmlClose() {\n        // https://github.com/jhy/jsoup/issues/1851\n        String html = \"<body><div>One</html>Two</div></body>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"OneTwo\", doc.expectFirst(\"body > div\").text());\n    }\n\n    @Test void largeTextareaContents() {\n        // https://github.com/jhy/jsoup/issues/1929\n        StringBuilder sb = new StringBuilder();\n        int num = 2000;\n        for (int i = 0; i <= num; i++) {\n            sb.append(\"\\n<text>foo</text>\\n\");\n        }\n        String textContent = sb.toString();\n        String sourceHtml = \"<textarea>\" + textContent + \"</textarea>\";\n\n        Document doc = Jsoup.parse(sourceHtml);\n        Element textArea = doc.expectFirst(\"textarea\");\n\n        assertEquals(textContent, textArea.wholeText());\n    }\n\n    @Test void svgParseTest() {\n        String html = \"<div><svg viewBox=2><foreignObject><p>One</p></foreignObject></svg></div>\";\n        Document doc = Jsoup.parse(html);\n\n        assertHtmlNamespace(doc);\n        Element div = doc.expectFirst(\"div\");\n        assertHtmlNamespace(div);\n\n        Element svg = doc.expectFirst(\"svg\");\n        assertTrue(svg.attributes().hasKey(\"viewBox\"));\n        assertSvgNamespace(svg);\n        assertSvgNamespace(doc.expectFirst(\"foreignObject\"));\n        assertHtmlNamespace(doc.expectFirst(\"p\"));\n\n        String serialized = div.html();\n        assertEquals(\"<svg viewBox=\\\"2\\\">\\n\" +\n            \" <foreignObject>\\n\" +\n            \"  <p>One</p>\\n\" +\n            \" </foreignObject>\\n\" +\n            \"</svg>\", serialized);\n    }\n\n    @Test void svgForeignObjectInParagraph() {\n        String html = \"<p><svg><foreignObject><div><p>One</p></div></foreignObject></svg></p>\";\n        Document doc = Jsoup.parse(html);\n\n        Element foreignObject = doc.expectFirst(\"foreignObject\");\n        assertSvgNamespace(foreignObject);\n        Element div = foreignObject.selectFirst(\"div\");\n        assertNotNull(div, \"div should stay within foreignObject\");\n        assertHtmlNamespace(div);\n        assertEquals(\"One\", div.expectFirst(\"p\").text());\n    }\n\n    @Test void mathParseText() {\n        String html = \"<div><math><mi><p>One</p><svg><text>Blah</text></svg></mi><ms></ms></div>\";\n        Document doc = Jsoup.parse(html);\n\n        assertHtmlNamespace(doc.expectFirst(\"div\"));\n        assertMathNamespace(doc.expectFirst(\"math\"));\n        assertMathNamespace(doc.expectFirst(\"mi\"));\n        assertHtmlNamespace(doc.expectFirst(\"p\"));\n        assertSvgNamespace(doc.expectFirst(\"svg\"));\n        assertSvgNamespace(doc.expectFirst(\"text\"));\n        assertMathNamespace(doc.expectFirst(\"ms\"));\n\n        String serialized = doc.expectFirst(\"div\").html();\n        assertEquals(\"<math>\\n <mi>\\n  <p>One</p>\\n  <svg>\\n   <text>Blah</text>\\n  </svg>\\n </mi><ms></ms>\\n</math>\", serialized);\n    }\n\n    private static void assertHtmlNamespace(Element el) {\n        assertEquals(Parser.NamespaceHtml, el.tag().namespace());\n    }\n\n    private static void assertSvgNamespace(Element el) {\n        assertEquals(Parser.NamespaceSvg, el.tag().namespace());\n    }\n\n    private static void assertMathNamespace(Element el) {\n        assertEquals(Parser.NamespaceMathml, el.tag().namespace());\n    }\n\n    @Test void mathSvgStyleTest() {\n        String html = \"<style><img></style><math><svg><style><img></img></style></svg></math>\";\n        Document doc = Jsoup.parse(html);\n\n        Element htmlStyle = doc.expectFirst(\"style\");\n        assertHtmlNamespace(htmlStyle);\n        assertEquals(\"<img>\", htmlStyle.data()); // that's not an element, it's data (textish)\n\n        Element svgStyle = doc.expectFirst(\"svg style\");\n        assertMathNamespace(svgStyle); // in inherited math namespace as not an HTML integration point\n        Element styleImg = svgStyle.expectFirst(\"img\");\n        assertHtmlNamespace(styleImg); // this one is an img tag - in foreign to html elements\n\n        assertMathNamespace(doc.expectFirst(\"svg\"));\n        assertMathNamespace(doc.expectFirst(\"math\"));\n    }\n\n    @Test void xmlnsAttributeError() {\n        String html = \"<p><svg></svg></body>\";\n        Parser parser = Parser.htmlParser().setTrackErrors(10);\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(0, doc.parser().getErrors().size());\n\n        String html2 = \"<html xmlns='http://www.w3.org/1999/xhtml'><p xmlns='http://www.w3.org/1999/xhtml'><i xmlns='xhtml'></i></body>\";\n        Document doc2 = Jsoup.parse(html2, parser);\n        assertEquals(1, doc2.parser().getErrors().size());\n        assertEquals(\"Invalid xmlns attribute [xhtml] on tag [i]\", parser.getErrors().get(0).getErrorMessage());\n    }\n\n    @Test void mathAnnotationSvg() {\n        String html = \"<math><svg>\"; // not in annotation, svg will be in math ns\n        Document doc = Jsoup.parse(html);\n        assertMathNamespace(doc.expectFirst(\"math\"));\n        assertMathNamespace(doc.expectFirst(\"svg\"));\n\n        String html2 = \"<math><annotation-xml><svg>\"; // svg will be in svg ns\n        Document doc2 = Jsoup.parse(html2);\n        assertMathNamespace(doc2.expectFirst(\"math\"));\n        assertMathNamespace(doc2.expectFirst(\"annotation-xml\"));\n        assertSvgNamespace(doc2.expectFirst(\"svg\"));\n    }\n\n    @Test void mathHtmlIntegrationPoint() {\n        String html = \"<math><div>Hello\";\n        Document doc = Jsoup.parse(html);\n        assertMathNamespace(doc.expectFirst(\"math\"));\n        assertHtmlNamespace(doc.expectFirst(\"div\"));\n\n        String html2 = \"<math><divv>Hello\";\n        Document doc2 = Jsoup.parse(html2);\n        assertMathNamespace(doc2.expectFirst(\"math\"));\n        assertMathNamespace(doc2.expectFirst(\"divv\"));\n\n        String html3 = \"<math><annotation-xml><divv>Hello\";\n        Document doc3 = Jsoup.parse(html3);\n        assertMathNamespace(doc3.expectFirst(\"math\"));\n        assertMathNamespace(doc3.expectFirst(\"annotation-xml\"));\n        assertMathNamespace(doc3.expectFirst(\"divv\"));\n\n        String html4 = \"<math><annotation-xml encoding=text/html><divv>Hello\";\n        Document doc4 = Jsoup.parse(html4);\n        assertMathNamespace(doc4.expectFirst(\"math\"));\n        assertMathNamespace(doc4.expectFirst(\"annotation-xml\"));\n        assertHtmlNamespace(doc4.expectFirst(\"divv\"));\n    }\n\n    @Test void parseEmojiFromMultipointEncoded() {\n        String html = \"<img multi='&#55357;&#56495;' single='&#128175;' hexsingle='&#x1f4af;'>\";\n        Document document = Jsoup.parse(html);\n        Element img = document.expectFirst(\"img\");\n        assertEquals(\"\\uD83D\\uDCAF\", img.attr(\"multi\"));\n        assertEquals(\"\\uD83D\\uDCAF\", img.attr(\"single\"));\n        assertEquals(\"\\uD83D\\uDCAF\", img.attr(\"hexsingle\"));\n\n        assertEquals(\"<img multi=\\\"\\uD83D\\uDCAF\\\" single=\\\"\\uD83D\\uDCAF\\\" hexsingle=\\\"\\uD83D\\uDCAF\\\">\", img.outerHtml());\n\n        img.ownerDocument().outputSettings().charset(\"ascii\");\n        assertEquals(\"<img multi=\\\"&#x1f4af;\\\" single=\\\"&#x1f4af;\\\" hexsingle=\\\"&#x1f4af;\\\">\", img.outerHtml());\n    }\n\n    @Test void tableInPInQuirksMode() {\n        // https://github.com/jhy/jsoup/issues/2197\n        String html = \"<p><span><table><tbody><tr><td><span>Hello table data</span></td></tr></tbody></table></span></p>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(Document.QuirksMode.quirks, doc.quirksMode());\n        assertEquals(\n            \"<p><span><table><tbody><tr><td><span>Hello table data</span></td></tr></tbody></table></span></p>\", // quirks, allows table in p\n            TextUtil.normalizeSpaces(doc.body().html())\n        );\n\n        // doctype set, no quirks\n        html =\"<!DOCTYPE html><p><span><table><tbody><tr><td><span>Hello table data</span></td></tr></tbody></table></span></p>\";\n        doc = Jsoup.parse(html);\n        assertEquals(Document.QuirksMode.noQuirks, doc.quirksMode());\n        assertEquals(\n            \"<p><span></span></p><table><tbody><tr><td><span>Hello table data</span></td></tr></tbody></table><p></p>\", // no quirks, p gets closed\n            TextUtil.normalizeSpaces(doc.body().html())\n        );\n    }\n\n    @Test void gtAfterTagClose() {\n        // https://github.com/jhy/jsoup/issues/2230\n        String html = \"<div>Div</div<> <a>One<a<b>Hello</b>\";\n        // this gives us an element \"a<b\", which is gross, but to the spec & browsers\n        Document doc = Jsoup.parse(html);\n        Element body = doc.body();\n        assertEquals(\"<div>\\n Div <a>One<a<b>Hello</a<b></a>\\n</div>\", body.html());\n\n        Elements abs = doc.getElementsByTag(\"a<b\");\n        assertEquals(1, abs.size());\n        Element ab = abs.first();\n        assertEquals(\"Hello\", ab.text());\n        assertEquals(\"a<b\", ab.tag().normalName());\n    }\n\n    @Test void ltInAttrStart() {\n        // https://github.com/jhy/jsoup/issues/1483\n        String html = \"<a before='foo' <junk after='bar'>One</a>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<a before=\\\"foo\\\" <junk after=\\\"bar\\\">One</a>\", TextUtil.normalizeSpaces(doc.body().html()));\n\n        Element el = doc.expectFirst(\"a\");\n        Attribute attribute = el.attribute(\"<junk\");\n        assertNotNull(attribute);\n        assertEquals(\"\", attribute.getValue());\n    }\n\n    @Test void pseudoAttributeComment() {\n        // https://github.com/jhy/jsoup/issues/1938\n        String html = \"  <h1>before</h1> <div <!--=\\\"\\\" id=\\\"hidden\\\" --=\\\"\\\"> <h1>within</h1> </div> <h1>after</h1>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"<h1>before</h1><div <!--=\\\"\\\" id=\\\"hidden\\\" --=\\\"\\\"><h1>within</h1></div><h1>after</h1>\", TextUtil.normalizeSpaces(doc.body().html()));\n        Element div = doc.expectFirst(\"div\");\n        assertNotNull(div.attribute(\"<!--\"));\n        assertEquals(\"hidden\", div.attr(\"id\"));\n        assertNotNull(div.attribute(\"--\"));\n    }\n\n    @Test void nullStreamReturnsEmptyDoc() throws IOException {\n        // https://github.com/jhy/jsoup/issues/2252\n        InputStream stream = null;\n        Document doc = Jsoup.parse(stream, null, \"\");\n        // don't want to mark parse(stream) as @Nullable, as it's more useful to show the warning. But support it, for backwards compat\n        assertNotNull(doc);\n        assertEquals(\"\", doc.title());\n    }\n\n    static void assertErrorsContain(String msg, ParseErrorList errors) {\n        assertFalse(errors.isEmpty());\n        for (ParseError error : errors) {\n            if (error.toString().contains(msg)) {\n                return;\n            }\n        }\n        fail(\"Expected to find error message [\" + msg + \"] in \" + errors);\n    }\n\n    static void assertErrorsDoNotContain(String msg, ParseErrorList errors) {\n        for (ParseError error : errors) {\n            if (error.toString().contains(msg)) {\n                fail(\"Did not expect to find error message [\" + msg + \"] in \" + errors);\n            }\n        }\n    }\n\n    @Test void selfClosing() {\n        // in HTML spec by default: void tags can be marked self-closing; foreign elements can self close; other instances are errors and the self-close is ignored\n        // voids are not serialized as self-closing\n        Parser parser = Parser.htmlParser().setTrackErrors(10);\n        String html = \"<div id=1 /><p>Foo\";\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertErrorsContain(\"<1:13>: Tag [div] cannot be self-closing; not a void tag\", errors);\n        assertEquals(\"<div id=\\\"1\\\"><p>Foo</p></div>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // voids are OK to be self close, but we don't emit them\n        html = \"<img /><input />\";\n        doc = Jsoup.parse(html, parser);\n        errors = parser.getErrors();\n        assertErrorsDoNotContain(\"cannot be self-closing\", errors);\n        assertEquals(\"<img><input>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // unknown tags won't be self-closing by default\n        html = \"<unknown />Foo\";\n        doc = Jsoup.parse(html, parser);\n        errors = parser.getErrors();\n        assertErrorsContain(\"Tag [unknown] cannot be self-closing;\", errors);\n        assertEquals(\"<unknown>Foo</unknown>\", TextUtil.stripNewlines(doc.body().html()));\n\n        // foreign elements can self close\n        html = \"<svg /><svg><femerge /><foo /></svg>\"; // femerge is known to tagset, foo is not\n        doc = Jsoup.parse(html, parser);\n        errors = parser.getErrors();\n        assertEquals(0, errors.size());\n        assertEquals(\"<svg /><svg><femerge /><foo /></svg>\", TextUtil.stripNewlines(doc.body().html()));\n        // check namespace of foo\n        Element foo = doc.expectFirst(\"foo\");\n        assertEquals(Parser.NamespaceSvg, foo.tag().namespace());\n    }\n\n    @Test void canControlSelfClosing() {\n        // by supplying a customized tagset, can allow both known and custom tags to self close during parse\n        // to be valid HTML, the emit will not include the self-closing, but user can switch to xml\n        Parser parser = Parser.htmlParser().setTrackErrors(10).tagSet(TagSet.Html());\n        TagSet tags = parser.tagSet();\n        Tag custom = tags.valueOf(\"custom\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Tag div = tags.valueOf(\"div\", Parser.NamespaceHtml).set(Tag.SelfClose);\n\n        String html = \"<div /><custom /><custom>Foo</custom>\";\n        Document doc = Jsoup.parse(html, parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(0, errors.size());\n        assertEquals(\"<div></div><custom></custom><custom>Foo</custom>\", TextUtil.stripNewlines(doc.body().html()));\n\n        assertTrue(custom.is(Tag.SeenSelfClose));\n        assertTrue(div.is(Tag.SeenSelfClose));\n\n        // in xml syntax will allow those self closes (with customized tagset)\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        assertEquals(\"<div /><custom /><custom>Foo</custom>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void customVoidTagsBehaveLikeHtmlVoids() {\n        Parser parser = Parser.htmlParser().setTrackErrors(10).tagSet(TagSet.Html());\n        TagSet tags = parser.tagSet();\n        tags.valueOf(\"voidtag\", Parser.NamespaceHtml).set(Tag.Void);\n\n        String html = \"<p><voidtag>Hello World</p>\";\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(0, parser.getErrors().size());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.html);\n        String emittedHtml = TextUtil.stripNewlines(doc.body().html());\n        assertEquals(\"<p><voidtag>Hello World</p>\", emittedHtml);\n        assertEquals(\"Hello World\", doc.body().text());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        assertEquals(\"<p><voidtag />Hello World</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void customSelfClosingVoidTagsRoundTrip() {\n        Parser parser = Parser.htmlParser().setTrackErrors(10).tagSet(TagSet.Html());\n        TagSet tags = parser.tagSet();\n        tags.valueOf(\"selfclosingvoidtag\", Parser.NamespaceHtml).set(Tag.Void).set(Tag.SelfClose);\n\n        String html = \"<p><selfclosingvoidtag />Hello World</p>\";\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(0, parser.getErrors().size());\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.html);\n        String emittedHtml = TextUtil.stripNewlines(doc.body().html());\n        assertEquals(\"<p><selfclosingvoidtag>Hello World</p>\", emittedHtml);\n\n        Document reparsed = Jsoup.parse(emittedHtml, parser);\n        reparsed.outputSettings().syntax(Document.OutputSettings.Syntax.html);\n        assertEquals(emittedHtml, TextUtil.stripNewlines(reparsed.body().html()));\n\n        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        assertEquals(\"<p><selfclosingvoidtag />Hello World</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void svgScriptParsedAsScriptData() {\n        // https://github.com/jhy/jsoup/issues/2320\n        String html = \"<svg><script>a < b</script></svg>\";\n        Document doc = Jsoup.parse(html);\n        Element script = doc.expectFirst(\"script\");\n        assertEquals(Parser.NamespaceSvg, script.tag().namespace);\n        assertTrue(script.tag().is(Tag.Data));\n\n        DataNode data = (DataNode) script.childNode(0);\n        assertEquals(\"a < b\", data.getWholeData());\n    }\n\n    @Test void allowCustomDataInForeignElements() {\n        Tag dataTag = new Tag(\"data\", Parser.NamespaceSvg);\n        dataTag.set(Tag.Data);\n        TagSet tagSet = TagSet.HtmlTagSet.add(dataTag);\n        String html = \"<svg><data>a < b</data></svg>\";\n        Document doc = Jsoup.parse(html, Parser.htmlParser().tagSet(tagSet));\n        Element data = doc.expectFirst(\"data\");\n        assertEquals(Parser.NamespaceSvg, data.tag().namespace);\n        assertEquals(\"\", data.text());\n        assertEquals(\"a < b\", data.data());\n        assertEquals(\"<data>a < b</data>\", data.outerHtml());\n    }\n\n    @Test void dropsNullsFromBody() {\n        // https://github.com/jhy/jsoup/issues/2395\n        String html = \"<p>\\u0000</p><p>\\u0000\\u0000</p><p>Hi\\u0000</p>\";\n\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(\"<p></p>\\n<p></p>\\n<p>Hi</p>\", doc.body().html());\n        assertEquals(\"Hi\", doc.body().text());\n\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(4, errors.size());\n        assertEquals(\"<1:4>: Unexpected character '\\u0000' in input state [Data]\", errors.get(0).toString());\n        assertEquals(\"<1:12>: Unexpected character '\\u0000' in input state [Data]\", errors.get(1).toString());\n        assertEquals(\"<1:13>: Unexpected character '\\u0000' in input state [Data]\", errors.get(2).toString());\n        assertEquals(\"<1:23>: Unexpected character '\\u0000' in input state [Data]\", errors.get(3).toString());\n        // todo should we replace that null, for convenience?\n    }\n\n    @Test void replacesNullsInForeign() {\n        String html = \"<svg><text>\\u0000</text><text>\\u0000\\u0000</text><text>Hi\\u0000</text></svg>\";\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n\n        Document doc = Jsoup.parse(html, parser);\n        assertEquals(\"<svg>\\n <text>�</text><text>��</text><text>Hi�</text>\\n</svg>\", doc.body().html());\n        assertEquals(\"���Hi�\", doc.body().text());\n\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(4, errors.size());\n        assertEquals(\"<1:12>: Unexpected character '\\u0000' in input state [Data]\", errors.get(0).toString());\n        assertEquals(\"<1:26>: Unexpected character '\\u0000' in input state [Data]\", errors.get(1).toString());\n        assertEquals(\"<1:27>: Unexpected character '\\u0000' in input state [Data]\", errors.get(2).toString());\n        assertEquals(\"<1:43>: Unexpected character '\\u0000' in input state [Data]\", errors.get(3).toString());\n    }\n\n    @Nested class DeepHtmlTrees {\n        private int depth(Element el) {\n            int depth = 0;\n            while ((el = el.parent()) != null) {\n                depth++;\n            }\n            return depth;\n        }\n\n        /**\n         * Parse the HTML code in `contents`, wrapped in enough divs to ensure that the root elements\n         * of contents are at depth `startingDepth`.\n         */\n        private Element parseDeepHtml(int startingDepth, String contents) {\n            StringBuilder html = new StringBuilder();\n            html.append(\"<html><body>\");\n            for (int i = 0; i < startingDepth - 4; i++) {\n                html.append(\"<div>\");\n            }\n            html.append(\"<div id='container'>\");\n            html.append(contents);\n\n            Parser parser = Parser.htmlParser();\n            Document doc = Jsoup.parse(html.toString(), parser);\n            Element container = doc.getElementById(\"container\");\n            assertNotNull(container);\n            assertEquals(startingDepth - 1, depth(container));\n\n            return container;\n        }\n\n        @Test void nestedDivs() {\n            Element container = parseDeepHtml(511, \"<div><div><div>\");\n\n            assertEquals(\"<div>\\n <div></div>\\n <div></div>\\n</div>\", container.html());\n        }\n\n        @Test void closingTagOfTagClosedByDepthLimit() {\n            // The <a></a> tag would be nested too deep, so it first closes the innermost <span>.\n            // This means that the first </span> will close the outer <span>, as it's the only\n            // one that is currently open. The last </span> is then just ignored, as there is no\n            // open <span> left to close.\n            Element container = parseDeepHtml(511, \"<span><span><a></a></span><b></b></span>\");\n\n            assertEquals(\"<span><span></span><a></a></span><b></b>\", container.html());\n        }\n\n        @Test void tableAtDepthLimitWithDirectTd() {\n            Element container = parseDeepHtml(512, \"<table><td>\");\n\n            assertEquals(\"<table></table>\\n<tbody></tbody>\\n<tr></tr>\\n<td></td>\", container.html());\n        }\n\n        @Test void tableRightBeforeDepthLimitWithDirectTd() {\n            Element container = parseDeepHtml(511, \"<table><td>\");\n\n            assertEquals(\"<table>\\n <tbody></tbody>\\n <tr></tr>\\n <td></td>\\n</table>\", container.html());\n        }\n\n        @Test void customDepthLimit() {\n            Parser parser = Parser.htmlParser().setMaxDepth(5);\n            String input = \"<html><body><div><div><div><div><div><div>\";\n\n            Document doc = Jsoup.parse(input, parser);\n            String expected = new StringBuilder()\n                .append(\"<html>\\n\")\n                .append(\" <head></head>\\n\")\n                .append(\" <body>\\n\")\n                .append(\"  <div>\\n\")\n                .append(\"   <div>\\n\")\n                .append(\"    <div></div>\\n\")\n                .append(\"    <div></div>\\n\")\n                .append(\"    <div></div>\\n\")\n                .append(\"    <div></div>\\n\")\n                .append(\"   </div>\\n\")\n                .append(\"  </div>\\n\")\n                .append(\" </body>\\n\")\n                .append(\"</html>\")\n                .toString();\n\n            assertEquals(expected, doc.html());\n        }\n\n        @Test void formControlsDetachWhenFormTrimmed() {\n            Parser parser = Parser.htmlParser().setMaxDepth(3);\n            String input = \"<form id='f'><div><input name='foo'></div></form>\";\n\n            Document doc = Jsoup.parse(input, \"\", parser);\n            Element formEl = doc.getElementById(\"f\");\n            assertNotNull(formEl);\n            assertTrue(formEl instanceof FormElement);\n            FormElement form = (FormElement) formEl;\n            assertEquals(\"\", form.html());\n            assertEquals(0, form.elements().size());\n        }\n\n        @Test void templateModesClearedWhenTrimmed() {\n            Parser parser = Parser.htmlParser().setMaxDepth(3);\n            String input = \"<template id='tmpl'><div><span>One</span></div></template><p>Two</p>\";\n\n            Document doc = Jsoup.parse(input, \"\", parser);\n            Element template = doc.getElementById(\"tmpl\");\n            assertNotNull(template);\n            assertEquals(\"\", template.html());\n            Element paragraph = doc.selectFirst(\"p\");\n            assertNotNull(paragraph);\n            assertEquals(\"Two\", paragraph.text());\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/HtmlTreeBuilderStateTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.parser.HtmlTreeBuilderState.Constants;\nimport org.junit.jupiter.api.Test;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.jsoup.parser.HtmlTreeBuilderState.Constants.InBodyStartInputAttribs;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class HtmlTreeBuilderStateTest {\n    static List<Object[]> findConstantArrays(Class aClass) {\n        ArrayList<Object[]> array = new ArrayList<>();\n        Field[] fields = aClass.getDeclaredFields();\n\n        for (Field field : fields) {\n            int modifiers = field.getModifiers();\n            if (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && field.getType().isArray()) {\n                try {\n                    array.add((Object[]) field.get(null));\n                } catch (IllegalAccessException e) {\n                    throw new IllegalStateException(e);\n                }\n            }\n        }\n\n        return array;\n    }\n\n    static void ensureSorted(List<Object[]> constants) {\n        for (Object[] array : constants) {\n            Object[] copy = Arrays.copyOf(array, array.length);\n            Arrays.sort(array);\n            assertArrayEquals(array, copy);\n        }\n    }\n\n    @Test\n    public void ensureArraysAreSorted() {\n        List<Object[]> constants = findConstantArrays(Constants.class);\n        ensureSorted(constants);\n        assertEquals(39, constants.size());\n    }\n\n    @Test public void ensureTagSearchesAreKnownTags() {\n        List<Object[]> constants = findConstantArrays(Constants.class);\n        for (Object[] constant : constants) {\n            String[] tagNames = (String[]) constant;\n            for (String tagName : tagNames) {\n                if (StringUtil.inSorted(tagName, InBodyStartInputAttribs))\n                    continue; // odd one out in the constant\n                assertTrue(Tag.isKnownTag(tagName), String.format(\"Unknown tag name: %s\", tagName));\n            }\n        }\n    }\n\n\n    @Test\n    public void nestedAnchorElements01() {\n        String html = \"<html>\\n\" +\n            \"  <body>\\n\" +\n            \"    <a href='#1'>\\n\" +\n            \"        <div>\\n\" +\n            \"          <a href='#2'>child</a>\\n\" +\n            \"        </div>\\n\" +\n            \"    </a>\\n\" +\n            \"  </body>\\n\" +\n            \"</html>\";\n        String s = Jsoup.parse(html).toString();\n        assertEquals(\"<html>\\n\" +\n            \" <head></head>\\n\" +\n            \" <body>\\n\" +\n            \"  <a href=\\\"#1\\\"> </a>\\n\" +\n            \"  <div>\\n\" +\n            \"   <a href=\\\"#1\\\"> </a><a href=\\\"#2\\\">child</a>\\n\" +\n            \"  </div>\\n\" +\n            \" </body>\\n\" +\n            \"</html>\", s);\n    }\n\n    @Test\n    public void nestedAnchorElements02() {\n        String html = \"<html>\\n\" +\n            \"  <body>\\n\" +\n            \"    <a href='#1'>\\n\" +\n            \"      <div>\\n\" +\n            \"        <div>\\n\" +\n            \"          <a href='#2'>child</a>\\n\" +\n            \"        </div>\\n\" +\n            \"      </div>\\n\" +\n            \"    </a>\\n\" +\n            \"  </body>\\n\" +\n            \"</html>\";\n        String s = Jsoup.parse(html).toString();\n        assertEquals(\"<html>\\n\" +\n            \" <head></head>\\n\" +\n            \" <body>\\n\" +\n            \"  <a href=\\\"#1\\\"> </a>\\n\" +\n            \"  <div>\\n\" +\n            \"   <a href=\\\"#1\\\"> </a>\\n\" +\n            \"   <div>\\n\" +\n            \"    <a href=\\\"#1\\\"> </a><a href=\\\"#2\\\">child</a>\\n\" +\n            \"   </div>\\n\" +\n            \"  </div>\\n\" +\n            \" </body>\\n\" +\n            \"</html>\", s);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/HtmlTreeBuilderTest.java",
    "content": "package org.jsoup.parser;\n\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jspecify.annotations.NullMarked;\nimport org.junit.jupiter.api.Test;\nimport java.io.Reader;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Method;\nimport java.util.List;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class HtmlTreeBuilderTest {\n    @Test\n    public void ensureSearchArraysAreSorted() {\n        List<Object[]> constants = HtmlTreeBuilderStateTest.findConstantArrays(HtmlTreeBuilder.class);\n        HtmlTreeBuilderStateTest.ensureSorted(constants);\n        assertEquals(14, constants.size());\n    }\n\n    @Test\n    public void nonnull() {\n        assertThrows(IllegalArgumentException.class, () -> {\n                HtmlTreeBuilder treeBuilder = new HtmlTreeBuilder();\n                treeBuilder.parse(null, null, null); // not sure how to test that these visual warnings actually appear! - test below checks for method annotation\n            }\n        ); // I'm not convinced that this lambda is easier to read than the old Junit 4 @Test(expected=IEA.class)...\n    }\n\n    @Test public void nonnullAssertions() throws NoSuchMethodException {\n        Annotation[] declaredAnnotations = TreeBuilder.class.getPackage().getDeclaredAnnotations();\n        boolean seen = false;\n        for (Annotation annotation : declaredAnnotations) {\n            if (annotation.annotationType().isAssignableFrom(NullMarked.class))\n                seen = true;\n        }\n\n        // would need to rework this if/when that annotation moves from the method to the class / package.\n        assertTrue(seen);\n    }\n\n    @Test void isSpecial() {\n        ParseSettings settings = ParseSettings.htmlDefault;\n        Element htmlEl = new Element(Tag.valueOf(\"div\", NamespaceHtml, settings), \"\");\n        assertTrue(HtmlTreeBuilder.isSpecial(htmlEl));\n\n        Element notHtml = new Element(Tag.valueOf(\"not-html\", NamespaceHtml, settings), \"\");\n        assertFalse(HtmlTreeBuilder.isSpecial(notHtml));\n\n        Element mathEl = new Element(Tag.valueOf(\"mi\", Parser.NamespaceMathml, settings), \"\");\n        assertTrue(HtmlTreeBuilder.isSpecial(mathEl));\n\n        Element notMathEl = new Element(Tag.valueOf(\"not-math\", Parser.NamespaceMathml, settings), \"\");\n        assertFalse(HtmlTreeBuilder.isSpecial(notMathEl));\n\n        Element svgEl = new Element(Tag.valueOf(\"title\", Parser.NamespaceSvg, settings), \"\");\n        assertTrue(HtmlTreeBuilder.isSpecial(svgEl));\n\n        Element notSvgEl = new Element(Tag.valueOf(\"not-svg\", Parser.NamespaceSvg, settings), \"\");\n        assertFalse(HtmlTreeBuilder.isSpecial(notSvgEl));\n    }\n\n    @Test void customRcdataTag() {\n        String inner = \"Blah\\nblah\\n<foo>Foo</foo>\\n&quot;\";\n        String innerText = \"Blah\\nblah\\n<foo>Foo</foo>\\n\\\"\";\n        String html = \"<div><x>\" + inner + \"</x></div><div><x id=2></x></div>\";\n        TagSet custom = TagSet.Html();\n        Tag x = custom.valueOf(\"x\", NamespaceHtml);\n        x.set(Tag.RcData);\n\n        Document doc = Jsoup.parse(html, Parser.htmlParser().tagSet(custom));\n        Element xEl = doc.expectFirst(\"x\");\n        assertEquals(x, xEl.tag());\n        assertEquals(innerText, xEl.wholeText()); // <foo> is text no el\n\n        // fragment parse context\n        Element x2 = doc.expectFirst(\"#2\");\n        x2.html(inner); // <foo> will be text not el, via custom fragment context element\n        assertEquals(innerText, x2.wholeText());\n    }\n\n    @Test void customDataTag() {\n        String inner = \"Blah\\nblah\\n<foo>Foo</foo>\\n&quot;\"; // no character refs, will be as-is\n        String html = \"<div><x>\" + inner + \"</x></div><div><x id=2></x></div>\";\n        TagSet custom = TagSet.Html();\n        Tag x = custom.valueOf(\"x\", NamespaceHtml);\n        x.set(Tag.Data);\n\n        Document doc = Jsoup.parse(html, Parser.htmlParser().tagSet(custom));\n        Element xEl = doc.expectFirst(\"x\");\n        assertEquals(x, xEl.tag());\n        assertEquals(inner, xEl.data());\n\n        // fragment parse context\n        Element x2 = doc.expectFirst(\"#2\");\n        x2.html(inner); // <foo> will be text not el, via custom fragment context element\n        assertEquals(inner, xEl.data());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/ParserIT.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n/**\n * Longer running Parser tests.\n */\n\npublic class ParserIT {\n    @Test\n    @Disabled // disabled by default now, as there more specific unconsume tests\n    public void testIssue1251() {\n        // https://github.com/jhy/jsoup/issues/1251\n        StringBuilder str = new StringBuilder(\"<a href=\\\"\\\"ca\");\n        for (int countSpaces = 0; countSpaces < 100000; countSpaces++) {\n            try {\n                Parser.htmlParser().setTrackErrors(1).parseInput(str.toString(), \"\");\n            } catch (Exception e) {\n                throw new AssertionError(\"failed at length \" + str.length(), e);\n            }\n            str.insert(countSpaces, ' ');\n        }\n    }\n\n    @Test\n    public void handlesDeepStack() {\n        // inspired by http://sv.stargate.wikia.com/wiki/M2J and https://github.com/jhy/jsoup/issues/955\n        // I didn't put it in the integration tests, because explorer and intellij kept dieing trying to preview/index it\n\n        // Arrange\n        StringBuilder longBody = new StringBuilder(500000);\n        for (int i = 0; i < 25000; i++) {\n            longBody.append(i).append(\"<dl><dd>\");\n        }\n        for (int i = 0; i < 25000; i++) {\n            longBody.append(i).append(\"</dd></dl>\");\n        }\n\n        // Act\n        long start = System.currentTimeMillis();\n        Document doc = Parser.parseBodyFragment(longBody.toString(), \"\");\n\n        int depth = 1;\n        Element el = doc.body();\n        while (el.childrenSize() > 0) {\n            el = el.child(0);\n            depth++;\n        }\n\n        // Assert\n        assertEquals(1, doc.body().childrenSize());\n        assertEquals(512, depth);\n        assertEquals(25000, doc.select(\"dd\").size());\n        assertTrue(System.currentTimeMillis() - start < 20000); // I get ~ 1.5 seconds, but others have reported slower\n        // was originally much longer, or stack overflow.\n    }\n\n    @Test void parserIsThreadSafe() throws InterruptedException {\n        // tests that a single parser can be called by multiple threads and won't blow up\n        // without the lock, will see many exceptions in parse, and non-equal docs\n        String html = \"<div id=1><div id=2><div id=3>Text.</div></div></div>\";\n        Parser parser = Parser.htmlParser();\n        Document expectDoc = parser.parseInput(html, \"\");\n\n        int numThreads = 10;\n        int numLoops = 20;\n        List<Thread> threads = new ArrayList<>(numThreads);\n        List<Document> toCheck = new ArrayList<>(numThreads * numLoops);\n        for (int i = 0; i < numThreads; i++) {\n            Thread thread = new Thread(() -> {\n                for (int j = 0; j < numLoops; j++) {\n                    Document doc = parser.parseInput(html, \"\");\n                    toCheck.add(doc);\n                }\n            });\n            threads.add(thread);\n            thread.start();\n        }\n\n        for (Thread thread : threads) {\n            thread.join();\n        }\n\n        for (Document doc : toCheck) {\n            assertTrue(doc.hasSameValue(expectDoc));\n        }\n    }\n\n    @Test void parserIsThreadSafeWithCloneAndAppend() throws InterruptedException {\n        // tests that a single parser can be called by multiple threads via Element.clone().append()\n        String html = \"<div id=1><div id=2><div id=3></div></div></div>\";\n        String append = \"<div id=4>Text.</div>\";\n        Parser parser = Parser.htmlParser();\n        Document baseDoc = parser.parseInput(html, \"\");\n        Element baseElement = baseDoc.expectFirst(\"#3\");\n\n        int numThreads = 10;\n        int numLoops = 20;\n        List<Thread> threads = new ArrayList<>(numThreads);\n        List<Element> toCheck = new ArrayList<>(numThreads * numLoops);\n        for (int i = 0; i < numThreads; i++) {\n            Thread thread = new Thread(() -> {\n                for (int j = 0; j < numLoops; j++) {\n                    Element cloned = baseElement.clone();\n                    cloned.append(append); // invokes the parser internally - parseFragment\n                    toCheck.add(cloned);\n                }\n            });\n            threads.add(thread);\n            thread.start();\n        }\n\n        for (Thread thread : threads) {\n            thread.join();\n        }\n\n        baseElement.append(append);\n        for (Element element : toCheck) {\n            assertTrue(element.hasSameValue(baseElement));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/ParserSettingsTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.MultiLocaleExtension.MultiLocaleTest;\nimport org.jsoup.nodes.Attributes;\n\nimport java.util.Locale;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class ParserSettingsTest {\n    @MultiLocaleTest\n    public void caseSupport(Locale locale) {\n        Locale.setDefault(locale);\n\n        ParseSettings bothOn = new ParseSettings(true, true);\n        ParseSettings bothOff = new ParseSettings(false, false);\n        ParseSettings tagOn = new ParseSettings(true, false);\n        ParseSettings attrOn = new ParseSettings(false, true);\n\n        assertEquals(\"IMG\", bothOn.normalizeTag(\"IMG\"));\n        assertEquals(\"ID\", bothOn.normalizeAttribute(\"ID\"));\n\n        assertEquals(\"img\", bothOff.normalizeTag(\"IMG\"));\n        assertEquals(\"id\", bothOff.normalizeAttribute(\"ID\"));\n\n        assertEquals(\"IMG\", tagOn.normalizeTag(\"IMG\"));\n        assertEquals(\"id\", tagOn.normalizeAttribute(\"ID\"));\n\n        assertEquals(\"img\", attrOn.normalizeTag(\"IMG\"));\n        assertEquals(\"ID\", attrOn.normalizeAttribute(\"ID\"));\n    }\n\n    @MultiLocaleTest\n    public void attributeCaseNormalization(Locale locale) {\n        Locale.setDefault(locale);\n\n        ParseSettings parseSettings = new ParseSettings(false, false);\n        String normalizedAttribute = parseSettings.normalizeAttribute(\"HIDDEN\");\n\n        assertEquals(\"hidden\", normalizedAttribute);\n    }\n\n    @MultiLocaleTest\n    public void attributesCaseNormalization(Locale locale) {\n        Locale.setDefault(locale);\n\n        ParseSettings parseSettings = new ParseSettings(false, false);\n        Attributes attributes = new Attributes();\n        attributes.put(\"ITEM\", \"1\");\n\n        parseSettings.normalizeAttributes(attributes);\n        assertEquals(\"item\", attributes.asList().get(0).getKey());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/ParserTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNotSame;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class ParserTest {\n\n    @Test\n    public void unescapeEntities() {\n        String s = Parser.unescapeEntities(\"One &amp; Two\", false);\n        assertEquals(\"One & Two\", s);\n    }\n\n    @Test\n    public void unescapeEntitiesHandlesLargeInput() {\n        StringBuilder longBody = new StringBuilder(500000);\n        do {\n            longBody.append(\"SomeNonEncodedInput\");\n        } while (longBody.length() < 64 * 1024);\n\n        String body = longBody.toString();\n        assertEquals(body, Parser.unescapeEntities(body, false));\n    }\n\n    @Test public void unescapeTracksErrors() {\n        Parser parser = Parser.htmlParser();\n        parser.setTrackErrors(10);\n\n        String s = parser.unescape(\"One &bogus; &amp; &gt Two\", false);\n        assertEquals(\"One &bogus; & > Two\", s);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(2, errors.size());\n        assertEquals(\"<1:6>: Invalid character reference: invalid named reference [bogus]\", errors.get(0).toString());\n        assertEquals(\"<1:22>: Invalid character reference: missing semicolon on [&gt]\", errors.get(1).toString());\n\n        // can reuse parser; errors will be reset\n        s = parser.unescape(\"One &amp; &bogus; Two\", false);\n        assertEquals(\"One & &bogus; Two\", s);\n        assertEquals(1, parser.getErrors().size());\n        assertEquals(\"<1:12>: Invalid character reference: invalid named reference [bogus]\", parser.getErrors().get(0).toString());\n    }\n\n    @Test\n    public void testUtf8() throws IOException {\n        // testcase for https://github.com/jhy/jsoup/issues/1557. no repro.\n        Document parsed = Jsoup.parse(new ByteArrayInputStream(\"<p>H\\u00E9llo, w\\u00F6rld!\".getBytes(StandardCharsets.UTF_8)), null, \"\");\n        String text = parsed.selectFirst(\"p\").wholeText();\n        assertEquals(text, \"H\\u00E9llo, w\\u00F6rld!\");\n    }\n\n    @Test\n    public void testClone() {\n        // Test HTML parser cloning\n        Parser htmlParser = Parser.htmlParser();\n        Parser htmlClone = htmlParser.clone();\n        assertNotSame(htmlParser, htmlClone);\n        // Ensure the tree builder instances are different\n        assertNotSame(htmlParser.getTreeBuilder(), htmlClone.getTreeBuilder());\n        // Check that settings are cloned properly (for example, tag case settings)\n        assertEquals(htmlParser.settings().preserveTagCase(), htmlClone.settings().preserveTagCase());\n        assertEquals(htmlParser.settings().preserveAttributeCase(), htmlClone.settings().preserveAttributeCase());\n\n        // Test XML parser cloning\n        Parser xmlParser = Parser.xmlParser();\n        Parser xmlClone = xmlParser.clone();\n        assertNotSame(xmlParser, xmlClone);\n        assertNotSame(xmlParser.getTreeBuilder(), xmlClone.getTreeBuilder());\n        assertEquals(xmlParser.settings().preserveTagCase(), xmlClone.settings().preserveTagCase());\n        assertEquals(xmlParser.settings().preserveAttributeCase(), xmlClone.settings().preserveAttributeCase());\n    }\n\n    @Test\n    public void testCloneCopyTagSet() {\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().add(new Tag(\"foo\"));\n        parser.tagSet().onNewTag(tag -> tag.set(Tag.SelfClose));\n        Parser clone = parser.clone();\n\n        // Ensure the tagsets are different instances\n        assertNotSame(clone.tagSet(), parser.tagSet());\n        // Check that cloned tagset contains same tag\n        assertNotNull(clone.tagSet().get(\"foo\", Parser.NamespaceHtml));\n        // Ensure onNewTag customizers are retained\n        Tag custom = clone.tagSet().valueOf(\"qux\", Parser.NamespaceHtml);\n        assertTrue(custom.isSelfClosing());\n        // Check that cloned tagset does not observe modifications made to the original\n        assertNull(clone.tagSet().get(\"bar\", Parser.NamespaceHtml));\n        parser.tagSet().add(new Tag(\"bar\"));\n        assertNull(clone.tagSet().get(\"bar\", Parser.NamespaceHtml));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/PositionTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.internal.Normalizer;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.CDataNode;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.DocumentType;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.LeafNode;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.Range;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.nodes.XmlDeclaration;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Functional tests for the Position tracking behavior (across nodes, treebuilder, etc.)\n */\nclass PositionTest {\n    static Parser TrackingHtmlParser = Parser.htmlParser().setTrackPosition(true);\n    static Parser TrackingXmlParser = Parser.xmlParser().setTrackPosition(true);\n\n    @Test void parserTrackDefaults() {\n        Parser htmlParser = Parser.htmlParser();\n        assertFalse(htmlParser.isTrackPosition());\n        htmlParser.setTrackPosition(true);\n        assertTrue(htmlParser.isTrackPosition());\n\n        Parser xmlParser = Parser.xmlParser();\n        assertFalse(xmlParser.isTrackPosition());\n        xmlParser.setTrackPosition(true);\n        assertTrue(xmlParser.isTrackPosition());\n    }\n\n    @Test void tracksPosition() {\n        String content = \"<p id=1\\n class=foo>\\n<span>Hello\\n &reg;\\n there &copy.</span> now.\\n <!-- comment --> \";\n        Document doc = Jsoup.parse(content, TrackingHtmlParser);\n\n        Element html = doc.expectFirst(\"html\");\n        Element body = doc.expectFirst(\"body\");\n        Element p = doc.expectFirst(\"p\");\n        Element span = doc.expectFirst(\"span\");\n        TextNode text = (TextNode) span.firstChild();\n        assertNotNull(text);\n        TextNode now = (TextNode) span.nextSibling();\n        assertNotNull(now);\n        Comment comment = (Comment) now.nextSibling();\n        assertNotNull(comment);\n\n        // implicit\n        assertTrue(body.sourceRange().isTracked());\n        assertTrue(body.endSourceRange().isTracked());\n        assertTrue(body.sourceRange().isImplicit());\n        assertTrue(body.endSourceRange().isImplicit());\n        Range htmlRange = html.sourceRange();\n        assertEquals(\"1,1:0-1,1:0\", htmlRange.toString());\n        assertEquals(htmlRange, body.sourceRange());\n        assertEquals(html.endSourceRange(), body.endSourceRange());\n\n\n        Range pRange = p.sourceRange();\n        assertEquals(\"1,1:0-2,12:19\", pRange.toString());\n        assertFalse(pRange.isImplicit());\n        assertTrue(p.endSourceRange().isImplicit());\n        assertEquals(\"6,19:83-6,19:83\", p.endSourceRange().toString());\n        assertEquals(p.endSourceRange(), html.endSourceRange());\n\n        // no explicit P closer\n        Range pEndRange = p.endSourceRange();\n        assertTrue(pEndRange.isTracked());\n        assertTrue(pEndRange.isImplicit());\n\n        Range.Position pStart = pRange.start();\n        assertTrue(pStart.isTracked());\n        assertEquals(0, pStart.pos());\n        assertEquals(1, pStart.columnNumber());\n        assertEquals(1, pStart.lineNumber());\n        assertEquals(\"1,1:0\", pStart.toString());\n\n        Range.Position pEnd = pRange.end();\n        assertTrue(pStart.isTracked());\n        assertEquals(19, pEnd.pos());\n        assertEquals(12, pEnd.columnNumber());\n        assertEquals(2, pEnd.lineNumber());\n        assertEquals(\"2,12:19\", pEnd.toString());\n\n        assertEquals(\"3,1:20\", span.sourceRange().start().toString());\n        assertEquals(\"3,7:26\", span.sourceRange().end().toString());\n\n        // span end tag\n        Range spanEnd = span.endSourceRange();\n        assertTrue(spanEnd.isTracked());\n        assertEquals(\"5,14:52-5,21:59\", spanEnd.toString());\n\n        String wholeText = text.getWholeText();\n        assertEquals(\"Hello\\n ®\\n there ©.\", wholeText);\n        String textOrig = \"Hello\\n &reg;\\n there &copy.\";\n        Range textRange = text.sourceRange();\n        assertEquals(textRange.end().pos() -  textRange.start().pos(), textOrig.length());\n        assertEquals(\"3,7:26\", textRange.start().toString());\n        assertEquals(\"5,14:52\", textRange.end().toString());\n\n        assertEquals(\"6,2:66\", comment.sourceRange().start().toString());\n        assertEquals(\"6,18:82\", comment.sourceRange().end().toString());\n    }\n\n    @Test void tracksExpectedPoppedElements() {\n        // When TreeBuilder hits a direct .pop(), vs popToClose(..)\n        String html = \"<html><head><meta></head><body><img><p>One</p><p>Two</p></body></html>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        StringBuilder track = new StringBuilder();\n        doc.expectFirst(\"html\").stream().forEach(el -> {\n            accumulatePositions(el, track);\n            assertTrue(el.sourceRange().isTracked(), el.tagName());\n            assertTrue(el.endSourceRange().isTracked(), el.tagName());\n            assertFalse(el.sourceRange().isImplicit(), el.tagName());\n            assertFalse(el.endSourceRange().isImplicit(), el.tagName());\n        });\n        assertEquals(\"html:0-6~63-70; head:6-12~18-25; meta:12-18~12-18; body:25-31~56-63; img:31-36~31-36; p:36-39~42-46; p:46-49~52-56; \", track.toString());\n\n        StringBuilder textTrack = new StringBuilder();\n        doc.nodeStream(TextNode.class).forEach(text -> accumulatePositions(text, textTrack));\n        assertEquals(\"#text:39-42; #text:49-52; \", textTrack.toString());\n    }\n\n    static void accumulatePositions(Node node, StringBuilder sb) {\n        sb\n            .append(node.nodeName())\n            .append(':')\n            .append(node.sourceRange().startPos())\n            .append('-')\n            .append(node.sourceRange().endPos());\n\n        if (node instanceof Element) {\n            Element el = (Element) node;\n            sb\n                .append(\"~\")\n                .append(el.endSourceRange().startPos())\n                .append('-')\n                .append(el.endSourceRange().endPos());\n        }\n        sb.append(\"; \");\n    }\n\n    @Test void tracksImplicitPoppedElements() {\n        // When TreeBuilder hits a direct .pop(), vs popToClose(..)\n        String html = \"<meta><img><p>One<p>Two<p>Three\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        StringBuilder track = new StringBuilder();\n        doc.expectFirst(\"html\").stream().forEach(el -> {\n            assertTrue(el.sourceRange().isTracked());\n            assertTrue(el.endSourceRange().isTracked());\n            accumulatePositions(el, track);\n        });\n\n        assertTrue(doc.expectFirst(\"p\").endSourceRange().isImplicit());\n        assertFalse(doc.expectFirst(\"meta\").endSourceRange().isImplicit());\n        assertEquals(\"html:0-0~31-31; head:0-0~6-6; meta:0-6~0-6; body:6-6~31-31; img:6-11~6-11; p:11-14~17-17; p:17-20~23-23; p:23-26~31-31; \", track.toString());\n    }\n    private void printRange(Node node) {\n        if (node instanceof Element) {\n            Element el = (Element) node;\n            System.out.println(el.tagName() + \"\\t\"\n                + el.sourceRange().start().pos() + \"-\" + el.sourceRange().end().pos()\n                + \"\\t... \"\n                + el.endSourceRange().start().pos() + \"-\" + el.endSourceRange().end().pos()\n            );\n        } else {\n            System.out.println(node.nodeName() + \"\\t\"\n                + node.sourceRange().start().pos() + \"-\" + node.sourceRange().end().pos()\n            );\n        }\n    }\n\n    @Test void tracksMarkup() {\n        String html = \"<!doctype\\nhtml>\\n<title>jsoup &copy;\\n2022</title><body>\\n<![CDATA[\\n<jsoup>\\n]]>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        DocumentType doctype = doc.documentType();\n        assertNotNull(doctype);\n        assertEquals(\"html\", doctype.name());\n        assertEquals(\"1,1:0-2,6:15\", doctype.sourceRange().toString());\n\n        Element title = doc.expectFirst(\"title\");\n        TextNode titleText = (TextNode) title.firstChild();\n        assertNotNull(titleText);\n        assertEquals(\"jsoup ©\\n2022\", title.text());\n        assertEquals(titleText.getWholeText(), title.text());\n        assertEquals(\"3,1:16-3,8:23\", title.sourceRange().toString());\n        assertEquals(\"3,8:23-4,5:40\", titleText.sourceRange().toString());\n\n        CDataNode cdata = (CDataNode) doc.body().childNode(1);\n        assertEquals(\"\\n<jsoup>\\n\", cdata.text());\n        assertEquals(\"5,1:55-7,4:76\", cdata.sourceRange().toString());\n    }\n\n    @Test void tracksDataNodes() {\n        String html = \"<head>\\n<script>foo;\\nbar()\\n5 <= 4;</script>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        Element script = doc.expectFirst(\"script\");\n        assertNotNull(script);\n        assertEquals(\"2,1:7-2,9:15\", script.sourceRange().toString());\n        DataNode data = (DataNode) script.firstChild();\n        assertNotNull(data);\n        assertEquals(\"2,9:15-4,8:33\", data.sourceRange().toString());\n    }\n\n    @Test void tracksExplicitAndImplicitBodyHtml() {\n        // Tests that </body></html> tokens are tracked when present\n        String htmlSans = \"<body><a>Link</a>\";\n        String htmlWith = \"<html><head></head><body><a>Link</a></body></html>\";\n\n        Document docSans = Jsoup.parse(htmlSans, TrackingHtmlParser);\n        Document docWith = Jsoup.parse(htmlWith, TrackingHtmlParser);\n\n        StringBuilder trackSans = new StringBuilder();\n        StringBuilder trackWith = new StringBuilder();\n        docSans.forEachNode(node -> accumulatePositions(node, trackSans));\n        docWith.forEachNode(node -> accumulatePositions(node, trackWith));\n\n        assertEquals(\"#document:0-0~17-17; html:0-0~17-17; head:0-0~0-0; body:0-6~17-17; a:6-9~13-17; #text:9-13; \", trackSans.toString());\n        assertEquals(\"#document:0-0~50-50; html:0-6~43-50; head:6-12~12-19; body:19-25~36-43; a:25-28~32-36; #text:28-32; \", trackWith.toString());\n    }\n\n    @Test void tracksXml() {\n        String xml = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\\n<!doctype html>\\n<rss url=foo>\\nXML\\n</rss>\\n<!-- comment -->\";\n        Document doc = Jsoup.parse(xml, TrackingXmlParser);\n\n        XmlDeclaration decl = (XmlDeclaration) doc.childNode(0);\n        assertEquals(\"1,1:0-1,39:38\", decl.sourceRange().toString());\n\n        DocumentType doctype = (DocumentType) doc.childNode(2);\n        assertEquals(\"2,1:39-2,16:54\", doctype.sourceRange().toString());\n\n        Element rss = doc.firstElementChild();\n        assertNotNull(rss);\n        assertEquals(\"3,1:55-3,14:68\", rss.sourceRange().toString());\n        assertEquals(\"5,1:73-5,7:79\", rss.endSourceRange().toString());\n\n        TextNode text = (TextNode) rss.firstChild();\n        assertNotNull(text);\n        assertEquals(\"3,14:68-5,1:73\", text.sourceRange().toString());\n\n        Comment comment = (Comment) rss.nextSibling().nextSibling();\n        assertEquals(\"6,1:80-6,17:96\", comment.sourceRange().toString());\n    }\n\n    @Test void tracksFromFetch() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n        Document doc = Jsoup.connect(url).parser(TrackingHtmlParser).get();\n\n        Element firstP = doc.expectFirst(\"p\");\n        assertNotNull(firstP);\n        assertEquals(\"4,1:53-4,4:56\", firstP.sourceRange().toString());\n\n        Element p = doc.expectFirst(\"#xy\");\n        assertNotNull(p);\n        assertEquals(\"1000,1:279646-1000,10:279655\", p.sourceRange().toString());\n        assertEquals(\"1000,567:280212-1000,571:280216\", p.endSourceRange().toString());\n\n        TextNode text = (TextNode) p.firstChild();\n        assertNotNull(text);\n        assertEquals(\"1000,10:279655-1000,357:280002\", text.sourceRange().toString());\n    }\n\n    @Test void tracksFromXmlFetch() throws IOException {\n        String url = FileServlet.urlTo(\"/htmltests/test-rss.xml\");\n        Document doc = Jsoup.connect(url).parser(TrackingXmlParser).get();\n\n        Element item = doc.expectFirst(\"item + item\");\n        assertNotNull(item);\n        assertEquals(\"13,5:496-13,11:502\", item.sourceRange().toString());\n        assertEquals(\"17,5:779-17,12:786\", item.endSourceRange().toString());\n    }\n\n    @Test void tracksTableMovedText() {\n        String html = \"<table>foo<tr>bar<td>baz</td>qux</tr>coo</table>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        StringBuilder track = new StringBuilder();\n        List<TextNode> textNodes = doc.nodeStream(TextNode.class)\n            .peek(node -> accumulatePositions(node, track))\n            .collect(Collectors.toList());\n\n        assertEquals(5, textNodes.size());\n        assertEquals(\"foo\", textNodes.get(0).text());\n        assertEquals(\"bar\", textNodes.get(1).text());\n        assertEquals(\"baz\", textNodes.get(2).text());\n        assertEquals(\"qux\", textNodes.get(3).text());\n        assertEquals(\"coo\", textNodes.get(4).text());\n\n        assertEquals(\"#text:7-10; #text:14-17; #text:21-24; #text:29-32; #text:37-40; \", track.toString());\n    }\n\n    @Test void tracksClosingHtmlTagsInXml() {\n        // verifies https://github.com/jhy/jsoup/issues/1935\n        String xml = \"<p>One</p><title>Two</title><data>Three</data>\";\n        Document doc = Jsoup.parse(xml, TrackingXmlParser);\n        Elements els = doc.children();\n        for (Element el : els) {\n            assertTrue(el.sourceRange().isTracked());\n            assertTrue(el.endSourceRange().isTracked());\n        }\n    }\n\n    @Test void tracksClosingHeadingTags() {\n        // https://github.com/jhy/jsoup/issues/1987\n        String html = \"<h1>One</h1><h2>Two</h2><h10>Ten</h10>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        Elements els = doc.body().children();\n        for (Element el : els) {\n            assertTrue(el.sourceRange().isTracked());\n            assertTrue(el.endSourceRange().isTracked());\n        }\n\n        Element h2 = doc.expectFirst(\"h2\");\n        assertEquals(\"1,13:12-1,17:16\", h2.sourceRange().toString());\n        assertEquals(\"1,20:19-1,25:24\", h2.endSourceRange().toString());\n    }\n\n    @Test void tracksAttributes() {\n        String html = \"<div one=\\\"Hello there\\\" id=1 class=foo attr1 = \\\"bar &amp; qux\\\" attr2='val &gt x' attr3=\\\"\\\" attr4 attr5>Text\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        Element div = doc.expectFirst(\"div\");\n\n        StringBuilder track = new StringBuilder();\n        for (Attribute attr : div.attributes()) {\n\n            Range.AttributeRange attrRange = attr.sourceRange();\n            assertTrue(attrRange.nameRange().isTracked());\n            assertTrue(attrRange.valueRange().isTracked());\n            assertSame(attrRange, div.attributes().sourceRange(attr.getKey()));\n\n            assertFalse(attrRange.nameRange().isImplicit());\n            if (attr.getValue().isEmpty())\n                assertTrue(attrRange.valueRange().isImplicit());\n            else\n                assertFalse(attrRange.valueRange().isImplicit());\n\n            accumulatePositions(attr, track);\n        }\n\n        assertEquals(\"one:5-8=10-21; id:23-25=26-27; class:28-33=34-37; attr1:38-43=47-60; attr2:62-67=69-78; attr3:80-85=85-85; attr4:89-94=94-94; attr5:95-100=100-100; \", track.toString());\n    }\n\n    @Test void tracksAttributesAcrossLines() {\n        String html = \"<div one=\\\"Hello\\nthere\\\" \\nid=1 \\nclass=\\nfoo\\nattr5>Text\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n\n        Element div = doc.expectFirst(\"div\");\n\n        StringBuilder track = new StringBuilder();\n        for (Attribute attr : div.attributes()) {\n            Range.AttributeRange attrRange = attr.sourceRange();\n            assertTrue(attrRange.nameRange().isTracked());\n            assertTrue(attrRange.valueRange().isTracked());\n            assertSame(attrRange, div.attributes().sourceRange(attr.getKey()));\n            assertFalse(attrRange.nameRange().isImplicit());\n            if (attr.getValue().isEmpty())\n                assertTrue(attrRange.valueRange().isImplicit());\n            else\n                assertFalse(attrRange.valueRange().isImplicit());\n            accumulatePositions(attr, track);\n        }\n\n        String value = div.attributes().get(\"class\");\n        assertEquals(\"foo\", value);\n        Range.AttributeRange foo = div.attributes().sourceRange(\"class\");\n        assertEquals(\"4,1:30-4,6:35=5,1:37-5,4:40\", foo.toString());\n\n        assertEquals(\"one:5-8=10-21; id:24-26=27-28; class:30-35=37-40; attr5:41-46=46-46; \", track.toString());\n    }\n\n    @Test void trackAttributePositionInFirstElement() {\n        String html = \"<html lang=en class=dark><p hidden></p></html>\";\n\n        Document htmlDoc = Jsoup.parse(html, TrackingHtmlParser);\n        StringBuilder htmlPos = new StringBuilder();\n        htmlDoc.expectFirst(\"html\").nodeStream().forEach(node -> {\n            accumulatePositions(node, htmlPos);\n            accumulateAttributePositions(node, htmlPos);\n        });\n\n        assertEquals(\"html:0-25~39-46; lang:6-10=11-13; class:14-19=20-24; head:25-25~25-25; body:25-25~46-46; p:25-35~35-39; hidden:28-34=34-34; \", htmlPos.toString());\n\n        Document xmlDoc = Jsoup.parse(html, TrackingXmlParser);\n        StringBuilder xmlPos = new StringBuilder();\n        xmlDoc.expectFirst(\"html\").nodeStream().forEach(node -> {\n            accumulatePositions(node, xmlPos);\n            accumulateAttributePositions(node, xmlPos);\n        });\n\n        assertEquals(\"html:0-25~39-46; lang:6-10=11-13; class:14-19=20-24; p:25-35~35-39; hidden:28-34=34-34; \", xmlPos.toString());\n    }\n\n    @Test void trackAttributePositionWithCase() {\n        String pomXml = \"<project xmlns=\\\"http://maven.apache.org/POM/4.0.0\\\" xmlns:xsi=\\\"http://www.w3.org/2001/XMLSchema-instance\\\" xsi:schemaLocation=\\\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\\\">\\n\" +\n            \"    <modelVersion>4.0.0</modelVersion>\";\n\n        Document htmlDoc = Jsoup.parse(pomXml, TrackingHtmlParser);\n        StringBuilder htmlPos = new StringBuilder();\n        htmlDoc.expectFirst(\"html\").nodeStream().forEach(node -> {\n            accumulatePositions(node, htmlPos);\n            accumulateAttributePositions(node, htmlPos);\n        });\n\n        assertEquals(\"html:0-0~243-243; head:0-0~0-0; body:0-0~243-243; project:0-204~243-243; xmlns:9-14=16-49; xmlns:xsi:51-60=62-103; xsi:schemalocation:105-123=125-202; #text:204-209; modelversion:209-223~228-243; #text:223-228; \", htmlPos.toString());\n\n        Document xmlDoc = Jsoup.parse(pomXml, TrackingXmlParser);\n        StringBuilder xmlPos = new StringBuilder();\n        xmlDoc.expectFirst(\"project\").nodeStream().forEach(node -> {\n            accumulatePositions(node, xmlPos);\n            accumulateAttributePositions(node, xmlPos);\n        });\n\n        assertEquals(\"project:0-204~243-243; xmlns:9-14=16-49; xmlns:xsi:51-60=62-103; xsi:schemaLocation:105-123=125-202; #text:204-209; modelVersion:209-223~228-243; #text:223-228; \", xmlPos.toString());\n\n        Document xmlDocLc = Jsoup.parse(pomXml, Parser.xmlParser().setTrackPosition(true).settings(new ParseSettings(false, false)));\n        StringBuilder xmlPosLc = new StringBuilder();\n        xmlDocLc.expectFirst(\"project\").nodeStream().forEach(node -> {\n            accumulatePositions(node, xmlPosLc);\n            accumulateAttributePositions(node, xmlPosLc);\n        });\n\n        assertEquals(\"project:0-204~243-243; xmlns:9-14=16-49; xmlns:xsi:51-60=62-103; xsi:schemalocation:105-123=125-202; #text:204-209; modelversion:209-223~228-243; #text:223-228; \", xmlPosLc.toString());\n    }\n\n\n    @Test void trackAttributesPositionsDedupes() {\n        String html = \"<p id=1 id=2 Id=3 Id=4 id=5 Id=6>\";\n        Document      htmlDoc   = Jsoup.parse(html, TrackingHtmlParser);\n        Document      htmlDocUc = Jsoup.parse(html, Parser.htmlParser().setTrackPosition(true).settings(new ParseSettings(true, true)));\n        Document      xmlDoc    = Jsoup.parse(html, TrackingXmlParser);\n        Document      xmlDocLc  = Jsoup.parse(html, Parser.xmlParser().setTrackPosition(true).settings(new ParseSettings(false, false)));\n\n        StringBuilder htmlPos   = new StringBuilder();\n        StringBuilder htmlUcPos = new StringBuilder();\n        StringBuilder xmlPos    = new StringBuilder();\n        StringBuilder xmlLcPos  = new StringBuilder();\n\n        accumulateAttributePositions(htmlDoc   .expectFirst(\"p\"), htmlPos);\n        accumulateAttributePositions(htmlDocUc .expectFirst(\"p\"), htmlUcPos);\n        accumulateAttributePositions(xmlDoc    .expectFirst(\"p\"), xmlPos);\n        accumulateAttributePositions(xmlDocLc  .expectFirst(\"p\"), xmlLcPos);\n\n        assertEquals(\"id:3-5=6-7; \", htmlPos   .toString());\n        assertEquals(\"id:3-5=6-7; Id:13-15=16-17; \", htmlUcPos .toString());\n        assertEquals(\"id:3-5=6-7; Id:13-15=16-17; \", xmlPos    .toString());\n        assertEquals(\"id:3-5=6-7; \", xmlLcPos .toString());\n    }\n\n    @Test void trackAttributesPositionsDirectionalDedupes() {\n        String html = \"<p Id=1 id=2 Id=3 Id=4 id=5 Id=6>\";\n        Document      htmlDoc   = Jsoup.parse(html, TrackingHtmlParser);\n        Document      htmlDocUc = Jsoup.parse(html, Parser.htmlParser().setTrackPosition(true).settings(new ParseSettings(true, true)));\n        Document      xmlDoc    = Jsoup.parse(html, TrackingXmlParser);\n        Document      xmlDocLc  = Jsoup.parse(html, Parser.xmlParser().setTrackPosition(true).settings(new ParseSettings(false, false)));\n\n        StringBuilder htmlPos   = new StringBuilder();\n        StringBuilder htmlUcPos = new StringBuilder();\n        StringBuilder xmlPos    = new StringBuilder();\n        StringBuilder xmlLcPos  = new StringBuilder();\n\n        accumulateAttributePositions(htmlDoc   .expectFirst(\"p\"), htmlPos);\n        accumulateAttributePositions(htmlDocUc .expectFirst(\"p\"), htmlUcPos);\n        accumulateAttributePositions(xmlDoc    .expectFirst(\"p\"), xmlPos);\n        accumulateAttributePositions(xmlDocLc  .expectFirst(\"p\"), xmlLcPos);\n\n        assertEquals(\"id:3-5=6-7; \", htmlPos   .toString());\n        assertEquals(\"Id:3-5=6-7; id:8-10=11-12; \", htmlUcPos .toString());\n        assertEquals(\"Id:3-5=6-7; id:8-10=11-12; \", xmlPos    .toString());\n        assertEquals(\"id:3-5=6-7; \", xmlLcPos .toString());\n    }\n\n    @Test void tracksFrag() {\n        // https://github.com/jhy/jsoup/issues/2068\n        String html = \"<h1 id=1>One</h1>\\n<h2 id=2>Two</h2><h10>Ten</h10>\";\n        Document shellDoc = Document.createShell(\"\");\n\n        List<Node> nodes = TrackingHtmlParser.parseFragmentInput(html, shellDoc.body(), shellDoc.baseUri());\n        StringBuilder track = new StringBuilder();\n\n        // nodes is the top level nodes - want to descend to check all tracked OK\n        nodes.forEach(node -> node.nodeStream().forEach(descend -> {\n            accumulatePositions(descend, track);\n            accumulateAttributePositions(descend, track);\n        }));\n\n        assertEquals(\"h1:0-9~12-17; id:4-6=7-8; #text:9-12; #text:17-18; h2:18-27~30-35; id:22-24=25-26; #text:27-30; h10:35-40~43-49; #text:40-43; \", track.toString());\n    }\n\n    @Test void tracksAfterPSelfClose() {\n        // https://github.com/jhy/jsoup/issues/2175\n        String html = \"foo<p/>bar &amp; 2\";\n\n        // force allow self-closing p\n        Parser parser = TrackingHtmlParser.clone();\n        parser.tagSet().valueOf(\"p\", Parser.NamespaceHtml).set(Tag.SelfClose);\n        Document doc = Jsoup.parse(html, parser);\n        StringBuilder track = new StringBuilder();\n        doc.body().forEachNode(node -> accumulatePositions(node, track));\n        assertEquals(\"body:0-0~18-18; #text:0-3; p:3-7~3-7; #text:7-18; \", track.toString());\n    }\n\n    @Test void tracksFirstTextnode() {\n        // https://github.com/jhy/jsoup/issues/2106\n        String html = \"foo<p></p>bar<p></p><div><b>baz</b></div>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n        StringBuilder track = new StringBuilder();\n        doc.body().forEachNode(node -> accumulatePositions(node, track));\n        assertEquals(\"body:0-0~41-41; #text:0-3; p:3-6~6-10; #text:10-13; p:13-16~16-20; div:20-25~35-41; b:25-28~31-35; #text:28-31; \", track.toString());\n    }\n\n    @Test void updateKeyMaintainsRangeLc() {\n        String html = \"<p xsi:CLASS=On>One</p>\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n        Element p = doc.expectFirst(\"p\");\n        Attribute attr = p.attribute(\"xsi:class\");\n        assertNotNull(attr);\n\n        String expectedRange = \"1,4:3-1,13:12=1,14:13-1,16:15\";\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        attr.setKey(\"class\");\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        assertEquals(\"class=\\\"On\\\"\", attr.html());\n    }\n\n    @Test void tracksDocument() {\n        String html = \"<!doctype html><title>Foo</title><p>Bar.\";\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n        StringBuilder track = new StringBuilder();\n        doc.forEachNode(node -> accumulatePositions(node, track));\n        assertEquals(\"#document:0-0~40-40; #doctype:0-15; html:15-15~40-40; head:15-15~33-33; title:15-22~15-33; #text:22-25; body:33-33~40-40; p:33-36~40-40; #text:36-40; \", track.toString());\n    }\n\n    @Test void tracksDocumentXml() {\n        String html = \"<!doctype html><title>Foo</title><p>Bar.\";\n        Document doc = Jsoup.parse(html, TrackingXmlParser);\n        StringBuilder track = new StringBuilder();\n        doc.forEachNode(node -> accumulatePositions(node, track));\n        assertEquals(\"#document:0-0~40-40; #doctype:0-15; title:15-22~25-33; #text:22-25; p:33-36~40-40; #text:36-40; \", track.toString());\n    }\n\n    @Test void updateKeyMaintainsRangeUc() {\n        String html = \"<p xsi:CLASS=On>One</p>\";\n        Document doc = Jsoup.parse(html, TrackingXmlParser);\n        Element p = doc.expectFirst(\"p\");\n        Attribute attr = p.attribute(\"xsi:CLASS\");\n        assertNotNull(attr);\n\n        String expectedRange = \"1,4:3-1,13:12=1,14:13-1,16:15\";\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        attr.setKey(\"class\");\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        assertEquals(\"class=\\\"On\\\"\", attr.html());\n\n        attr.setKey(\"CLASSY\");\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        assertEquals(\"CLASSY=\\\"On\\\"\", attr.html());\n\n        attr.setValue(\"To\");\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        assertEquals(\"CLASSY=\\\"To\\\"\", attr.html());\n\n        assertEquals(\"<p CLASSY=\\\"To\\\">One</p>\", p.outerHtml());\n\n        p.attr(\"CLASSY\", \"Tree\");\n        assertEquals(expectedRange, attr.sourceRange().toString());\n        assertEquals(\"CLASSY=\\\"To\\\"\", attr.html()); // changes in this direction do not get to the attribute as it's not connected that way\n\n        Attribute attr2 = p.attribute(\"CLASSY\");\n        assertEquals(\"CLASSY=\\\"Tree\\\"\", attr2.html());\n        assertEquals(expectedRange, attr2.sourceRange().toString());\n    }\n\n    @Test void movedAttributesHaveRange() {\n        // https://github.com/jhy/jsoup/issues/2204\n        String html = \"<span id=1>One</span><html attr=foo><body class=2>Two</body><head title=3><body class=ok data=bar>\";\n        // note that the attributes of the head el are not copied into the implicit head created by the span, per spec. html and body els are.\n        Document doc = Jsoup.parse(html, TrackingHtmlParser);\n        StringBuilder elTrack = new StringBuilder();\n        doc.forEachNode(node -> accumulatePositions(node, elTrack));\n\n        StringBuilder atTrack = new StringBuilder();\n        doc.forEachNode(node -> accumulateAttributePositions(node, atTrack));\n\n        assertEquals(\"#document:0-0~98-98; html:0-0~98-98; head:0-0~0-0; body:0-0~53-60; span:0-11~14-21; #text:11-14; #text:50-53; \", elTrack.toString());\n        assertEquals(\"attr:27-31=32-35; class:42-47=48-49; data:89-93=94-97; id:6-8=9-10; \", atTrack.toString());\n\n        assertEquals(\"<html attr=\\\"foo\\\"><head></head><body class=\\\"2\\\" data=\\\"bar\\\"><span id=\\\"1\\\">One</span>Two </body></html>\", TextUtil.normalizeSpaces(doc.html()));\n    }\n\n    static void accumulateAttributePositions(Node node, StringBuilder sb) {\n        if (node instanceof LeafNode) return; // leafnode pseudo attributes are not tracked\n        for (Attribute attribute : node.attributes()) {\n            accumulatePositions(attribute, sb);\n        }\n    }\n\n    static void accumulatePositions(Attribute attr, StringBuilder sb) {\n        Range.AttributeRange range = attr.sourceRange();\n\n        sb\n            .append(attr.getKey())\n            .append(':')\n            .append(range.nameRange().startPos())\n            .append('-')\n            .append(range.nameRange().endPos())\n\n            .append('=')\n            .append(range.valueRange().startPos())\n            .append('-')\n            .append(range.valueRange().endPos());\n\n        sb.append(\"; \");\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/StreamParserTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Connection;\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.DataUtil;\nimport org.jsoup.integration.ParseTest;\nimport org.jsoup.integration.servlets.FileServlet;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.NoSuchElementException;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests for the StreamParser. There are also some tests in {@link org.jsoup.integration.ConnectTest}.\n */\nclass StreamParserTest {\n\n    @Test\n    void canStream() {\n        String html = \"<title>Test</title></head><div id=1>D1</div><div id=2>D2<p id=3><span>P One</p><p id=4>P Two</p></div><div id=5>D3<p id=6>P three</p>\";\n        try (StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\")) {\n            StringBuilder seen;\n            seen = new StringBuilder();\n            parser.stream().forEachOrdered(el -> trackSeen(el, seen));\n            assertEquals(\"title[Test];head+;div#1[D1]+;span[P One];p#3+;p#4[P Two];div#2[D2]+;p#6[P three];div#5[D3];body;html;#root;\", seen.toString());\n            // checks expected order, and the + indicates that element had a next sibling at time of emission\n        }\n    }\n\n    @Test\n    void canStreamXml() {\n        String html = \"<outmost><DIV id=1>D1</DIV><div id=2>D2<p id=3><span>P One</p><p id=4>P Two</p></div><div id=5>D3<p id=6>P three</p>\";\n        try (StreamParser parser = new StreamParser(Parser.xmlParser()).parse(html, \"\")) {\n            StringBuilder seen;\n            seen = new StringBuilder();\n            parser.stream().forEachOrdered(el -> trackSeen(el, seen));\n            assertEquals(\"DIV#1[D1]+;span[P One];p#3+;p#4[P Two];div#2[D2]+;p#6[P three];div#5[D3];outmost;#root;\", seen.toString());\n            // checks expected order, and the + indicates that element had a next sibling at time of emission\n        }\n    }\n\n    @Test void canIterate() {\n        // same as stream, just a different interface\n        String html = \"<title>Test</title></head><div id=1>D1</div><div id=2>D2<p id=3><span>P One</p><p id=4>P Two</p></div><div id=5>D3<p id=6>P three</p>\";\n        StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\");\n        StringBuilder seen = new StringBuilder();\n\n        Iterator<Element> it = parser.iterator();\n        while (it.hasNext()) {\n            trackSeen(it.next(), seen);\n        }\n\n        assertEquals(\"title[Test];head+;div#1[D1]+;span[P One];p#3+;p#4[P Two];div#2[D2]+;p#6[P three];div#5[D3];body;html;#root;\", seen.toString());\n        // checks expected order, and the + indicates that element had a next sibling at time of emission\n    }\n\n    @Test void canReuse() {\n        StreamParser parser = new StreamParser(Parser.htmlParser());\n        String html1 = \"<p>One<p>Two\";\n        parser.parse(html1, \"\");\n\n        StringBuilder seen = new StringBuilder();\n        parser.stream().forEach(el -> trackSeen(el, seen));\n        assertEquals(\"head+;p[One]+;p[Two];body;html;#root;\", seen.toString());\n\n        String html2 = \"<div>Three<div>Four</div></div>\";\n        StringBuilder seen2 = new StringBuilder();\n        parser.parse(html2, \"\");\n        parser.stream().forEach(el -> trackSeen(el, seen2));\n        assertEquals(\"head+;div[Four];div[Three];body;html;#root;\", seen2.toString());\n\n        // re-run without a new parse should be empty\n        StringBuilder seen3 = new StringBuilder();\n        parser.stream().forEach(el -> trackSeen(el, seen3));\n        assertEquals(\"\", seen3.toString());\n    }\n\n    @Test void canStopAndCompleteAndReuse() throws IOException {\n        StreamParser parser = new StreamParser(Parser.htmlParser());\n        String html1 = \"<p>One<p>Two\";\n        parser.parse(html1, \"\");\n\n        Element p = parser.expectFirst(\"p\");\n        assertEquals(\"One\", p.text());\n        parser.stop();\n\n        Iterator<Element> it = parser.iterator();\n        assertFalse(it.hasNext());\n        assertThrows(NoSuchElementException.class, it::next);\n\n        Element p2 = parser.selectNext(\"p\");\n        assertNull(p2);\n\n        Document completed = parser.complete();\n        Elements ps = completed.select(\"p\");\n        assertEquals(2, ps.size());\n        assertEquals(\"One\", ps.get(0).text());\n        assertEquals(\"Two\", ps.get(1).text());\n\n        // can reuse\n        parser.parse(\"<div>DIV\", \"\");\n        Element div = parser.expectFirst(\"div\");\n        assertEquals(\"DIV\", div.text());\n    }\n\n    static void trackSeen(Element el, StringBuilder actual) {\n        actual.append(el.tagName());\n        if (el.hasAttr(\"id\"))\n            actual.append(\"#\").append(el.id());\n        if (!el.ownText().isEmpty())\n            actual.append(\"[\").append(el.ownText()).append(\"]\");\n        if (el.nextElementSibling() != null)\n            actual.append(\"+\");\n\n        actual.append(\";\");\n    }\n\n    @Test void select() throws IOException {\n        String html = \"<title>One</title><p id=1>P One</p><p id=2>P Two</p>\";\n        StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\");\n\n        Element title = parser.expectFirst(\"title\");\n        assertEquals(\"One\", title.text());\n\n        Document partialDoc = title.ownerDocument();\n        assertNotNull(partialDoc);\n        // at this point, we should have one P with no text - as title was emitted on P head\n        Elements ps = partialDoc.select(\"p\");\n        assertEquals(1, ps.size());\n        assertEquals(\"\", ps.get(0).text());\n        assertSame(partialDoc, parser.document());\n\n        Element title2 = parser.selectFirst(\"title\");\n        assertSame(title2, title);\n\n        Element p1 = parser.expectNext(\"p\");\n        assertEquals(\"P One\", p1.text());\n\n        Element p2 = parser.expectNext(\"p\");\n        assertEquals(\"P Two\", p2.text());\n\n        Element pNone = parser.selectNext(\"p\");\n        assertNull(pNone);\n    }\n\n    @Test void canRemoveFromDom() {\n        String html = \"<div>One</div><div>DESTROY</div><div>Two</div>\";\n        StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\");\n        parser.parse(html, \"\");\n\n        parser.stream().forEach(\n            el -> {\n                if (el.ownText().equals(\"DESTROY\"))\n                    el.remove();\n            });\n\n        Document doc = parser.document();\n        Elements divs = doc.select(\"div\");\n        assertEquals(2, divs.size());\n        assertEquals(\"One Two\", divs.text());\n    }\n\n    @Test void canRemoveWithIterator() {\n        String html = \"<div>One</div><div>DESTROY</div><div>Two</div>\";\n        StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\");\n        parser.parse(html, \"\");\n\n        Iterator<Element> it = parser.iterator();\n        while (it.hasNext()) {\n            Element el = it.next();\n            if (el.ownText().equals(\"DESTROY\"))\n                it.remove(); // we know el.remove() works, from above test\n        }\n\n        Document doc = parser.document();\n        Elements divs = doc.select(\"div\");\n        assertEquals(2, divs.size());\n        assertEquals(\"One Two\", divs.text());\n    }\n\n    @Test void canSelectWithHas() throws IOException {\n        StreamParser parser = basic();\n\n        Element el = parser.expectNext(\"div:has(p)\");\n        assertEquals(\"Two\", el.text());\n    }\n\n    @Test void canSelectWithSibling() throws IOException {\n        StreamParser parser = basic();\n\n        Element el = parser.expectNext(\"div:first-of-type\");\n        assertEquals(\"One\", el.text());\n\n        Element el2 = parser.selectNext(\"div:first-of-type\");\n        assertNull(el2);\n    }\n\n    @Test void canLoopOnSelectNext() throws IOException {\n        StreamParser streamer = new StreamParser(Parser.htmlParser()).parse(\"<div><p>One<p>Two<p>Thr</div>\", \"\");\n\n        int count = 0;\n        Element e;\n        while ((e = streamer.selectNext(\"p\")) != null) {\n            assertEquals(3, e.text().length()); // has a body\n            e.remove();\n            count++;\n        }\n\n        assertEquals(3, count);\n        assertEquals(0, streamer.document().select(\"p\").size()); // removed all during iter\n\n        assertTrue(isClosed(streamer)); // read to the end\n    }\n\n    @Test void worksWithXmlParser() throws IOException {\n        StreamParser streamer = new StreamParser(Parser.xmlParser()).parse(\"<div><p>One</p><p>Two</p><p>Thr</p></div>\", \"\");\n\n        int count = 0;\n        Element e;\n        while ((e = streamer.selectNext(\"p\")) != null) {\n            assertEquals(3, e.text().length()); // has a body\n            e.remove();\n            count++;\n        }\n\n        assertEquals(3, count);\n        assertEquals(0, streamer.document().select(\"p\").size()); // removed all during iter\n\n        assertTrue(isClosed(streamer)); // read to the end\n    }\n\n    @Test void closedOnStreamDrained() {\n        StreamParser streamer = basic();\n        assertFalse(isClosed(streamer));\n        long count = streamer.stream().count();\n        assertEquals(7, count);\n\n        assertTrue(isClosed(streamer));\n    }\n\n    @Test void closedOnIteratorDrained() {\n        StreamParser streamer = basic();\n\n        int count = 0;\n        Iterator<Element> it = streamer.iterator();\n        while (it.hasNext()) {\n            it.next();\n            count++;\n        }\n        assertEquals(7, count);\n        assertTrue(isClosed(streamer));\n    }\n\n    @Test void closedOnComplete() throws IOException {\n        StreamParser streamer = basic();\n        Document doc = streamer.complete();\n        assertTrue(isClosed(streamer));\n    }\n\n    @Test void closedOnTryWithResources() {\n        StreamParser copy;\n        try(StreamParser streamer = basic()) {\n            copy = streamer;\n            assertFalse(isClosed(copy));\n        }\n        assertTrue(isClosed(copy));\n    }\n\n    static StreamParser basic() {\n        String html = \"<div>One</div><div><p>Two</div>\";\n        StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\");\n        parser.parse(html, \"\");\n        return parser;\n    }\n\n    static boolean isClosed(StreamParser streamer) {\n        // a bit of a back door in!\n        return getReader(streamer) == null;\n    }\n\n     private static CharacterReader getReader(StreamParser streamer) {\n        return streamer.document().parser().getTreeBuilder().reader;\n    }\n\n    @Test void doesNotReadPastParse() throws IOException {\n        StreamParser streamer = basic();\n        Element div = streamer.expectFirst(\"div\");\n\n        // we should have read the sibling div, but not yet its children p\n        Element sib = div.nextElementSibling();\n        assertNotNull(sib);\n        assertEquals(\"div\", sib.tagName());\n        assertEquals(0, sib.childNodeSize());\n\n        // the Reader should be at \"<p>\" because we haven't consumed it\n        assertTrue(getReader(streamer).matches(\"<p>Two\"));\n    }\n\n    @Test void canParseFileReader() throws IOException {\n        File file = ParseTest.getFile(\"/htmltests/large.html\");\n\n        // can't use FileReader from Java 11 here\n        InputStreamReader input = new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8);\n        BufferedReader reader = new BufferedReader(input);\n        StreamParser streamer = new StreamParser(Parser.htmlParser()).parse(reader, file.getAbsolutePath());\n\n        Element last = null, e;\n        while ((e = streamer.selectNext(\"p\")) != null) {\n            last = e;\n        }\n        assertTrue(last.text().startsWith(\"VESTIBULUM\"));\n\n        // the reader should be closed as streamer is closed on completion of read\n        assertTrue(isClosed(streamer));\n\n        assertThrows(IOException.class, reader::ready); // ready() checks isOpen and throws\n    }\n\n    @Test void canParseFile() throws IOException {\n        File file = ParseTest.getFile(\"/htmltests/large.html\");\n        StreamParser streamer = DataUtil.streamParser(file.toPath(), StandardCharsets.UTF_8, \"\", Parser.htmlParser());\n\n        Element last = null, e;\n        while ((e = streamer.selectNext(\"p\")) != null) {\n            last = e;\n        }\n        assertTrue(last.text().startsWith(\"VESTIBULUM\"));\n\n        // the reader should be closed as streamer is closed on completion of read\n        assertTrue(isClosed(streamer));\n    }\n\n    @Test void canCleanlyConsumePortionOfUrl() throws IOException {\n        // test that we can get just the head section of large.html, and only read the minimum required from the URL\n        String url = FileServlet.urlTo(\"/htmltests/large.html\"); // 280 K\n\n        AtomicReference<Float> seenPercent = new AtomicReference<>(0.0f);\n        StreamParser parserRef;\n\n        Connection con = Jsoup.connect(url)\n            .onResponseProgress((processed, total, percent, response) -> {\n                //System.out.println(\"Processed: \" + processed + \" Total: \" + total + \" Percent: \" + percent);\n                seenPercent.set(percent);\n            });\n\n        Connection.Response response = con.execute();\n        try (StreamParser parser = response.streamParser()) {\n            parserRef = parser;\n            // get the head section\n            Element head = parser.selectFirst(\"head\");\n            Element title = head.expectFirst(\"title\");\n            assertEquals(\"Large HTML\", title.text());\n        }\n        // now that we've left the try, the stream parser and the response bodystream should be closed\n        assertTrue(isClosed(parserRef));\n\n        // test that we didn't read all of the stream\n        assertTrue(seenPercent.get() > 0.0f);\n        assertTrue(seenPercent.get() < 100.0f);\n        // not sure of a good way to assert the bufferedInputReader buf (as held by ConstrainableInputStream in Response.BodyStream) is null. But it is via StreamParser.close.\n    }\n\n    // Fragments\n\n    @Test\n    void canStreamFragment() {\n        String html = \"<tr id=1><td>One</td><tr id=2><td>Two</td></tr><tr id=3><td>Three</td></tr>\";\n        Element context = new Element(\"table\");\n\n        try (StreamParser parser = new StreamParser(Parser.htmlParser()).parseFragment(html, context, \"\")) {\n            StringBuilder seen = new StringBuilder();\n            parser.stream().forEachOrdered(el -> trackSeen(el, seen));\n            assertEquals(\"td[One];tr#1+;td[Two];tr#2+;td[Three];tr#3;tbody;table;#root;\", seen.toString());\n            // checks expected order, and the + indicates that element had a next sibling at time of emission\n            // note that we don't get a full doc, just the fragment (and the context at the end of the stack)\n\n            assertTrue(isClosed(parser)); // as read to completion\n        }\n    }\n\n    @Test void canIterateFragment() {\n        // same as stream, just a different interface\n        String html = \"<tr id=1><td>One</td><tr id=2><td>Two</td></tr><tr id=3><td>Three</td></tr>\"; // missing </tr>, following <tr> infers it\n        Element context = new Element(\"table\");\n\n        try(StreamParser parser = new StreamParser(Parser.htmlParser()).parseFragment(html, context, \"\")) {\n            StringBuilder seen = new StringBuilder();\n\n            Iterator<Element> it = parser.iterator();\n            while (it.hasNext()) {\n                trackSeen(it.next(), seen);\n            }\n\n            assertEquals(\"td[One];tr#1+;td[Two];tr#2+;td[Three];tr#3;tbody;table;#root;\", seen.toString());\n            // checks expected order, and the + indicates that element had a next sibling at time of emission\n            // note that we don't get a full doc, just the fragment (and the context at the end of the stack)\n\n            assertTrue(isClosed(parser)); // as read to completion\n        }\n    }\n\n    @Test\n    void canSelectAndCompleteFragment() throws IOException {\n        String html = \"<tr id=1><td>One</td><tr id=2><td>Two</td></tr><tr id=3><td>Three</td></tr>\";\n        Element context = new Element(\"table\");\n\n        try (StreamParser parser = new StreamParser(Parser.htmlParser()).parseFragment(html, context, \"\")) {\n            Element first = parser.expectNext(\"td\");\n            assertEquals(\"One\", first.ownText());\n\n            Element el = parser.expectNext(\"td\");\n            assertEquals(\"Two\", el.ownText());\n\n            el = parser.expectNext(\"td\");\n            assertEquals(\"Three\", el.ownText());\n\n            el = parser.selectNext(\"td\");\n            assertNull(el);\n\n            List<Node> nodes = parser.completeFragment();\n            assertEquals(1, nodes.size()); // should be the inferred tbody\n            Node tbody = nodes.get(0);\n            assertEquals(\"tbody\", tbody.nodeName());\n            List<Node> trs = tbody.childNodes();\n            assertEquals(3, trs.size()); // should be the three TRs\n            assertSame(trs.get(0).childNode(0), first); // tr -> td\n\n            assertSame(parser.document(), first.ownerDocument()); // the shell document for this fragment\n        }\n    }\n\n    @Test\n    void canStreamFragmentXml() throws IOException {\n        String html = \"<tr id=1><td>One</td></tr><tr id=2><td>Two</td></tr><tr id=3><td>Three</td></tr>\";\n        Element context = new Element(\"Other\");\n\n        try (StreamParser parser = new StreamParser(Parser.xmlParser()).parseFragment(html, context, \"\")) {\n            StringBuilder seen = new StringBuilder();\n            parser.stream().forEachOrdered(el -> trackSeen(el, seen));\n            assertEquals(\"td[One];tr#1+;td[Two];tr#2+;td[Three];tr#3;#root;\", seen.toString());\n            // checks expected order, and the + indicates that element had a next sibling at time of emission\n            // note that we don't get a full doc, just the fragment\n\n            assertTrue(isClosed(parser)); // as read to completion\n\n            List<Node> nodes = parser.completeFragment();\n            assertEquals(3, nodes.size());\n            assertEquals(\"tr\", nodes.get(0).nodeName());\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\n        \"<html><body><a>Link</a></body></html>\",\n        \"<html><body><a>Link</a>\",\n        \"<a>Link</a></body></html>\",\n        \"<a>Link</a>\",\n        \"<a>Link\",\n        \"<a>Link</body>\",\n    })\n    void emitsOnlyOnce(String html) {\n        try (StreamParser parser = new StreamParser(Parser.htmlParser()).parse(html, \"\")) {\n            // https://github.com/jhy/jsoup/issues/2295\n            // When there was a /body or /html, those were being emitted twice, due to firing a fake onNodeClosed to track their source positions\n            StringBuilder seen = new StringBuilder();\n            parser.stream().forEach(el -> trackSeen(el, seen));\n            assertEquals(\"head+;a[Link];body;html;#root;\", seen.toString());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/TagSetTest.java",
    "content": "package org.jsoup.parser;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.junit.jupiter.api.Test;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class TagSetTest {\n    @Test void canRetrieveNewTagsSensitive() {\n        Document doc = Jsoup.parse(\"<div><p>One</p></div>\", \"\", Parser.htmlParser().settings(ParseSettings.preserveCase));\n        TagSet tags = doc.parser().tagSet();\n        // should be the full html set\n        Tag meta = tags.get(\"meta\", NamespaceHtml);\n        assertNotNull(meta);\n        assertTrue(meta.isKnownTag());\n\n        Element p = doc.expectFirst(\"p\");\n        assertTrue(p.tag().isKnownTag());\n\n        assertNull(tags.get(\"FOO\", NamespaceHtml));\n        p.tagName(\"FOO\");\n        Tag foo = p.tag();\n        assertEquals(\"FOO\", foo.name());\n        assertEquals(\"foo\", foo.normalName());\n        assertEquals(NamespaceHtml, foo.namespace());\n        assertFalse(foo.isKnownTag());\n\n        assertSame(foo, tags.get(\"FOO\", NamespaceHtml));\n        assertSame(foo, tags.valueOf(\"FOO\", NamespaceHtml));\n        assertNull(tags.get(\"FOO\", \"SomeOtherNamespace\"));\n    }\n\n    @Test void canRetrieveNewTagsInsensitive() {\n        Document doc = Jsoup.parse(\"<div><p>One</p></div>\");\n        TagSet tags = doc.parser().tagSet();\n        // should be the full html set\n        Tag meta = tags.get(\"meta\", NamespaceHtml);\n        assertNotNull(meta);\n        assertTrue(meta.isKnownTag());\n\n        Element p = doc.expectFirst(\"p\");\n        assertTrue(p.tag().isKnownTag());\n\n        assertNull(tags.get(\"FOO\", NamespaceHtml));\n        p.tagName(\"FOO\");\n        Tag foo = p.tag();\n        assertEquals(\"foo\", foo.name());\n        assertEquals(\"foo\", foo.normalName());\n        assertEquals(NamespaceHtml, foo.namespace());\n        assertFalse(foo.isKnownTag());\n\n        assertSame(foo, tags.get(\"foo\", NamespaceHtml));\n        assertSame(foo, tags.valueOf(\"FOO\", NamespaceHtml, doc.parser().settings()));\n        assertNull(tags.get(\"foo\", \"SomeOtherNamespace\"));\n    }\n\n    @Test void supplyCustomTagSet() {\n        TagSet tags = TagSet.Html();\n        tags.valueOf(\"custom\", NamespaceHtml).set(Tag.PreserveWhitespace).set(Tag.Block);\n        Parser parser = Parser.htmlParser().tagSet(tags);\n\n        Document doc = Jsoup.parse(\"<body><custom>\\n\\nFoo\\n Bar</custom></body>\", parser);\n        Element custom = doc.expectFirst(\"custom\");\n        assertTrue(custom.tag().preserveWhitespace());\n        assertTrue(custom.tag().isBlock());\n        assertEquals(\"<custom>\\n\" +\n            \"\\n\" +\n            \"Foo\\n\" +\n            \" Bar\" +\n            \"</custom>\", custom.outerHtml());\n    }\n\n    @Test void knownTags() {\n        // tests that tags explicitly inserted via .add are 'known'; those that come implicitly via valueOf are not\n        TagSet tags = TagSet.Html();\n        Tag custom = new Tag(\"custom\");\n        assertEquals(\"custom\", custom.name());\n        assertEquals(NamespaceHtml, custom.namespace());\n        assertFalse(custom.isKnownTag()); // not yet\n\n        Tag br = tags.get(\"br\", NamespaceHtml);\n        assertNotNull(br);\n        assertTrue(br.isKnownTag());\n        assertSame(br, tags.valueOf(\"br\", NamespaceHtml));\n\n        Tag foo = tags.valueOf(\"foo\", NamespaceHtml);\n        assertFalse(foo.isKnownTag());\n\n        tags.add(custom);\n        assertTrue(custom.isKnownTag());\n        assertSame(custom, tags.get(\"custom\", NamespaceHtml));\n        assertSame(custom, tags.valueOf(\"custom\", NamespaceHtml));\n        Tag capCustom = tags.valueOf(\"Custom\", NamespaceHtml);\n        assertTrue(capCustom.isKnownTag()); // cloned from a known tag, so is still known\n\n        // known if set or clear called\n        Tag c1 = new Tag(\"bar\");\n        assertFalse(c1.isKnownTag());\n        c1.set(Tag.Block);\n        assertTrue(c1.isKnownTag());\n        c1.clear(Tag.Block);\n        assertTrue(c1.isKnownTag());\n        c1.clear(Tag.Known);\n        assertFalse(c1.isKnownTag());\n    }\n\n    @Test void canCustomizeAll() {\n        TagSet tags = TagSet.Html();\n        tags.onNewTag(tag -> tag.set(Tag.SelfClose));\n        assertTrue(tags.get(\"script\", NamespaceHtml).is(Tag.SelfClose));\n        assertTrue(tags.valueOf(\"SCRIPT\", NamespaceHtml).is(Tag.SelfClose));\n        assertTrue(tags.valueOf(\"custom\", NamespaceHtml).is(Tag.SelfClose));\n\n        Tag foo = new Tag(\"foo\", NamespaceHtml);\n        assertFalse(foo.is(Tag.SelfClose));\n        tags.add(foo);\n        assertTrue(foo.is(Tag.SelfClose));\n    }\n\n    @Test void canCustomizeSome() {\n        TagSet tags = TagSet.Html();\n        tags.onNewTag(tag -> {\n            if (!tag.isKnownTag()) {\n                tag.set(Tag.SelfClose);\n            }\n        });\n        assertFalse(tags.valueOf(\"script\", NamespaceHtml).is(Tag.SelfClose));\n        assertFalse(tags.valueOf(\"SCRIPT\", NamespaceHtml).is(Tag.SelfClose));\n        assertTrue(tags.valueOf(\"custom-tag\", NamespaceHtml).is(Tag.SelfClose));\n    }\n\n    @Test void canParseWithCustomization() {\n        // really would use tag.valueOf(\"script\"); just a test example here\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().onNewTag(tag -> {\n            if (tag.normalName().equals(\"script\"))\n                tag.set(Tag.SelfClose);\n        });\n\n        Document doc = Jsoup.parse(\"<script />Text\", parser);\n        assertEquals(\"<html>\\n <head>\\n  <script></script>\\n </head>\\n <body>Text</body>\\n</html>\", doc.html());\n        // self closing bit still produces valid HTML\n    }\n\n    @Test void canParseWithGeneralCustomization() {\n        Parser parser = Parser.htmlParser();\n        parser.tagSet().onNewTag(tag -> {\n            if (!tag.isKnownTag())\n                tag.set(Tag.SelfClose);\n        });\n\n        Document doc = Jsoup.parse(\"<custom-data />Bar <script />Text\", parser);\n        assertEquals(\"<custom-data></custom-data>Bar\\n<script>Text</script>\", doc.body().html());\n    }\n\n    @Test void supportsMultipleCustomizers() {\n        TagSet tags = TagSet.Html();\n        tags.onNewTag(tag -> {\n            if (tag.normalName().equals(\"script\"))\n                tag.set(Tag.SelfClose);\n        });\n        tags.onNewTag(tag -> {\n            if (!tag.isKnownTag())\n                tag.set(Tag.RcData);\n        });\n\n        assertTrue(tags.valueOf(\"script\", NamespaceHtml).is(Tag.SelfClose));\n        assertFalse(tags.valueOf(\"script\", NamespaceHtml).is(Tag.RcData));\n        assertTrue(tags.valueOf(\"custom-tag\", NamespaceHtml).is(Tag.RcData));\n    }\n\n    @Test void customizersArePreservedInSource() {\n        TagSet source = TagSet.Html();\n        source.onNewTag(tag -> tag.set(Tag.RcData));\n        TagSet copy = new TagSet(source);\n        assertTrue(copy.valueOf(\"script\", NamespaceHtml).is(Tag.RcData));\n        assertTrue(source.valueOf(\"script\", NamespaceHtml).is(Tag.RcData));\n\n        copy.onNewTag(tag -> tag.set(Tag.Void));\n        assertTrue(copy.valueOf(\"custom-tag\", NamespaceHtml).is(Tag.Void));\n        assertFalse(source.valueOf(\"custom-tag\", NamespaceHtml).is(Tag.Void));\n    }\n\n    @Test void copyPullThroughDoesNotMutateSource() {\n        TagSet source = TagSet.Html();\n        TagSet copy = new TagSet(source);\n\n        int sourceNamespacesBefore = tagSetNamespaceCount(source);\n        assertNotNull(copy.get(\"div\", NamespaceHtml));\n        int sourceNamespacesAfter = tagSetNamespaceCount(source);\n        assertEquals(sourceNamespacesBefore, sourceNamespacesAfter);\n    }\n\n    @Test void copyPullWithCustomizerThroughDoesNotMutateSource() {\n        TagSet source = TagSet.Html();\n        TagSet copy = new TagSet(source);\n\n        AtomicInteger sourceAdds = new AtomicInteger();\n        source.onNewTag(tag -> sourceAdds.incrementAndGet());\n\n        assertNotNull(copy.get(\"div\", NamespaceHtml));\n        assertEquals(0, sourceAdds.get());\n    }\n\n    private static int tagSetNamespaceCount(TagSet tagSet) {\n        try {\n            Field tagsField = TagSet.class.getDeclaredField(\"tags\");\n            tagsField.setAccessible(true);\n            Map<?, ?> tags = (Map<?, ?>) tagsField.get(tagSet);\n            return tags.size();\n        } catch (ReflectiveOperationException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/TagTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.MultiLocaleExtension.MultiLocaleTest;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Locale;\n\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.jsoup.parser.Parser.NamespaceSvg;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tag tests.\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class TagTest {\n    @Test public void isCaseSensitive() {\n        Tag p1 = Tag.valueOf(\"P\");\n        Tag p2 = Tag.valueOf(\"p\");\n        assertNotEquals(p1, p2);\n    }\n\n    @MultiLocaleTest\n    public void canBeInsensitive(Locale locale) {\n        Locale.setDefault(locale);\n\n        Tag script1 = Tag.valueOf(\"script\", NamespaceHtml, ParseSettings.htmlDefault);\n        Tag script2 = Tag.valueOf(\"SCRIPT\", NamespaceHtml, ParseSettings.htmlDefault);\n        assertEquals(script1, script2);\n\n        TagSet htmlTags = TagSet.Html();\n        Tag script3 = htmlTags.valueOf(\"script\", NamespaceHtml, ParseSettings.htmlDefault);\n        Tag script4 = htmlTags.valueOf(\"SCRIPT\", NamespaceHtml, ParseSettings.htmlDefault);\n        assertSame(script3, script4);\n    }\n\n    @Test public void trims() {\n        Tag p1 = Tag.valueOf(\"p\");\n        Tag p2 = Tag.valueOf(\" p \");\n        assertEquals(p1, p2);\n    }\n\n    @Test public void equality() {\n        Tag p1 = Tag.valueOf(\"p\");\n        Tag p2 = Tag.valueOf(\"p\");\n        assertEquals(p1, p2);\n        assertNotSame(p1, p2); // not same because Tag.valueOf creates new clone of the TagSet.Html, so changes don't clobber all\n\n        TagSet html1 = TagSet.Html();\n        TagSet html2 = TagSet.Html();\n        assertEquals(html1, html2);\n        assertNotSame(html1, html2);\n\n        Tag p3 = html1.valueOf(\"p\", NamespaceHtml);\n        Tag p4 = html1.valueOf(\"p\", NamespaceHtml);\n        Tag p5 = html2.valueOf(\"p\", NamespaceHtml);\n        Tag p6 = html2.valueOf(\"p\", NamespaceHtml);\n        assertEquals(p1, p3);\n        assertEquals(p3, p4);\n        assertEquals(p4, p5);\n        assertSame(p3, p4);\n        assertSame(p5, p6);\n        assertNotSame(p3, p5);\n    }\n\n    @Test public void divSemantics() {\n        Tag div = Tag.valueOf(\"div\");\n\n        assertTrue(div.isBlock());\n        assertFalse(div.isInline());\n        assertTrue(div.isKnownTag());\n    }\n\n    @Test public void pSemantics() {\n        Tag p = Tag.valueOf(\"p\");\n        assertTrue(p.isKnownTag());\n        assertTrue(p.isBlock());\n        assertFalse(p.isInline());\n    }\n\n    @Test public void brSemantics() {\n        Tag br = Tag.valueOf(\"br\");\n        assertTrue(br.isInline());\n        assertFalse(br.isBlock());\n    }\n\n    @Test public void imgSemantics() {\n        Tag img = Tag.valueOf(\"img\");\n        assertTrue(img.isInline());\n        assertTrue(img.isSelfClosing());\n        assertFalse(img.isBlock());\n    }\n\n    @Test public void defaultSemantics() {\n        Tag foo = Tag.valueOf(\"FOO\"); // not defined\n        Tag foo2 = Tag.valueOf(\"FOO\");\n\n        assertEquals(foo, foo2);\n        assertFalse(foo.isKnownTag());\n        assertTrue(foo.isInline());\n        assertFalse(foo.isBlock());\n        assertFalse(foo.is(Tag.InlineContainer));\n        assertFalse(foo.preserveWhitespace());\n    }\n\n    @Test public void valueOfChecksNotNull() {\n        assertThrows(IllegalArgumentException.class, () -> Tag.valueOf(null));\n    }\n\n    @Test public void valueOfChecksNotEmpty() {\n        assertThrows(IllegalArgumentException.class, () -> Tag.valueOf(\" \"));\n    }\n\n    @Test public void knownTags() {\n        assertTrue(Tag.isKnownTag(\"div\"));\n        assertFalse(Tag.isKnownTag(\"explain\"));\n    }\n\n    @Test public void knownSvgNamespace() {\n        Tag svgHtml = Tag.valueOf(\"svg\"); // no namespace specified, defaults to html, so not the known tag\n        Tag svg = Tag.valueOf(\"svg\", Parser.NamespaceSvg, ParseSettings.htmlDefault);\n\n        assertEquals(NamespaceHtml, svgHtml.namespace());\n        assertEquals(Parser.NamespaceSvg, svg.namespace());\n\n        assertFalse(svgHtml.isKnownTag()); // generated\n        assertTrue(svg.isKnownTag()); // known\n    }\n\n    @Test public void unknownTagNamespace() {\n        Tag fooHtml = Tag.valueOf(\"foo\"); // no namespace specified, defaults to html\n        Tag foo = Tag.valueOf(\"foo\", Parser.NamespaceSvg, ParseSettings.htmlDefault);\n\n        assertEquals(NamespaceHtml, fooHtml.namespace());\n        assertEquals(Parser.NamespaceSvg, foo.namespace());\n\n        assertFalse(fooHtml.isKnownTag()); // generated\n        assertFalse(foo.isKnownTag()); // generated\n    }\n\n    @Test void canSetOptions() {\n        Tag tag = new Tag(\"foo\", NamespaceHtml);\n        assertFalse(tag.isKnownTag());\n        assertFalse(tag.isEmpty());\n        tag.set(Tag.Void);\n        assertTrue(tag.isEmpty());\n        assertTrue(tag.isKnownTag());\n    }\n\n    @Test void updateNameAndNamespace() {\n        Tag tag = new Tag(\"foo\", NamespaceHtml);\n        tag.name(\"bar\").namespace(NamespaceSvg);\n        tag.set(Tag.Block);\n        assertEquals(\"bar\", tag.name());\n        assertEquals(NamespaceSvg, tag.namespace());\n        assertTrue(tag.isBlock()); // properties are unchanged\n\n        // test in a doc\n        Document doc = Jsoup.parse(\"<foo>One</foo><foo>Two</foo>\");\n        Tag foo = doc.expectFirst(\"foo\").tag();\n        foo.name(\"BAR\");\n        assertEquals(\"<BAR>One</BAR><BAR>Two</BAR>\", doc.body().html()); // is case-sensitive\n    }\n\n    @Test void formSubmittable() {\n        // https://github.com/jhy/jsoup/issues/2323\n        Tag img = Tag.valueOf(\"img\");\n        Tag input = Tag.valueOf(\"input\");\n        int imgOpts = img.options;\n        int inputOpts = input.options;\n        assertFalse(img.isFormSubmittable());\n        assertTrue(input.isFormSubmittable());\n        assertEquals(imgOpts, img.options);\n        assertEquals(inputOpts, input.options);\n    }\n\n    @Test void stableHashcode() {\n        // tests that the hashcode is stable and suitable as a key\n        HashSet<Tag> tags = new HashSet<>();\n        Tag img = Tag.valueOf(\"img\");\n        Tag IMG = Tag.valueOf(\"IMG\");\n        Tag imgS = Tag.valueOf(\"img\", NamespaceSvg, ParseSettings.htmlDefault);\n\n        assertEquals(-2074969810, img.hashCode());\n        assertEquals(-2075954866, IMG.hashCode());\n        assertEquals(-292873947, imgS.hashCode());\n\n        tags.add(img);\n        tags.add(IMG);\n        tags.add(imgS);\n\n        imgS.set(Tag.Block);\n        assertEquals(-292873947, imgS.hashCode());\n\n        assertTrue(tags.contains(img));\n        assertTrue(tags.contains(IMG));\n        assertTrue(tags.contains(imgS));\n    }\n\n    @Test void prefix() {\n        Tag img = Tag.valueOf(\"img\");\n        Tag book = Tag.valueOf(\"bk:book\");\n\n        assertEquals(\"\", img.prefix());\n        assertEquals(\"bk\", book.prefix());\n    }\n\n    @Test void localname() {\n        Tag img = Tag.valueOf(\"img\");\n        Tag book = Tag.valueOf(\"bk:book\");\n\n        assertEquals(\"img\", img.localName());\n        assertEquals(\"book\", book.localName());\n    }\n\n    @Test void valueOfWithSettings() {\n        Tag img1 = Tag.valueOf(\"img\", ParseSettings.htmlDefault);\n        Tag img2 = Tag.valueOf(\"IMG\", ParseSettings.htmlDefault);\n        Tag img3 = Tag.valueOf(\"IMG\", ParseSettings.preserveCase);\n\n        assertNotSame(img1, img2); // because we are creating new TagSets with html()\n        assertNotSame(img1, img3);\n        assertEquals(\"IMG\", img3.toString());\n        assertEquals(\"img\", img1.toString());\n\n        TagSet tagSet = TagSet.Html();\n        assertSame(\n            tagSet.valueOf(\"img\", NamespaceHtml, ParseSettings.htmlDefault),\n            tagSet.valueOf(\"IMG\", NamespaceHtml, ParseSettings.htmlDefault)\n        );\n\n        assertNotSame(\n            tagSet.valueOf(\"img\", NamespaceHtml),\n            tagSet.valueOf(\"IMG\", NamespaceHtml)\n        );\n    }\n\n    @Test void equals() {\n        TagSet tags = TagSet.Html();\n        Tag p1 = tags.get(\"p\", NamespaceHtml);\n        Tag p2 = p1.clone();\n        assertEquals(p1, p2);\n        assertNotEquals(p1, tags);\n\n        p2.namespace = \"Other\";\n        assertNotEquals(p1, p2);\n        p2.namespace = p1.namespace;\n\n        p2.tagName = \"P\";\n        assertNotEquals(p1, p2);\n        p2.tagName = p1.tagName;\n\n        p2.normalName = \"pp\";\n        assertNotEquals(p1, p2);\n        p2.normalName = p1.normalName;\n\n        p2.options = 0;\n        assertNotEquals(p1, p2);\n        p2.options = p1.options;\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/TokenQueueTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Token queue tests.\n */\npublic class TokenQueueTest {\n    @Test public void chompBalanced() {\n        TokenQueue tq = new TokenQueue(\":contains(one (two) three) four\");\n        String pre = tq.consumeTo(\"(\");\n        String guts = tq.chompBalanced('(', ')');\n        String remainder = tq.remainder();\n\n        assertEquals(\":contains\", pre);\n        assertEquals(\"one (two) three\", guts);\n        assertEquals(\" four\", remainder);\n    }\n\n    @Test public void chompEscapedBalanced() {\n        TokenQueue tq = new TokenQueue(\":contains(one (two) \\\\( \\\\) \\\\) three) four\");\n        String pre = tq.consumeTo(\"(\");\n        String guts = tq.chompBalanced('(', ')');\n        String remainder = tq.remainder();\n\n        assertEquals(\":contains\", pre);\n        assertEquals(\"one (two) \\\\( \\\\) \\\\) three\", guts);\n        assertEquals(\"one (two) ( ) ) three\", TokenQueue.unescape(guts));\n        assertEquals(\" four\", remainder);\n    }\n\n    @Test public void chompBalancedMatchesAsMuchAsPossible() {\n        TokenQueue tq = new TokenQueue(\"unbalanced(something(or another)) else\");\n        tq.consumeTo(\"(\");\n        String match = tq.chompBalanced('(', ')');\n        assertEquals(\"something(or another)\", match);\n    }\n\n    @Test public void unescape() {\n        assertEquals(\"one ( ) \\\\\", TokenQueue.unescape(\"one \\\\( \\\\) \\\\\\\\\"));\n    }\n\n    @Test public void unescape_2() {\n        assertEquals(\"\\\\&\", TokenQueue.unescape(\"\\\\\\\\\\\\&\"));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"escapeCssIdentifier_WebPlatformTestParameters\")\n    @MethodSource(\"escapeCssIdentifier_additionalParameters\")\n    public void escapeCssIdentifier(String expected, String input) {\n        assertEquals(expected, TokenQueue.escapeCssIdentifier(input));\n    }\n\n    // https://github.com/web-platform-tests/wpt/blob/328fa1c67bf5dfa6f24571d4c41dd10224b6d247/css/cssom/escape.html\n    private static Stream<Arguments> escapeCssIdentifier_WebPlatformTestParameters() {\n        return Stream.of(\n            Arguments.of(\"\", \"\"),\n\n            // Null bytes\n            Arguments.of(\"\\uFFFD\", \"\\0\"),\n            Arguments.of(\"a\\uFFFD\", \"a\\0\"),\n            Arguments.of(\"\\uFFFDb\", \"\\0b\"),\n            Arguments.of(\"a\\uFFFDb\", \"a\\0b\"),\n\n            // Replacement character\n            Arguments.of(\"\\uFFFD\", \"\\uFFFD\"),\n            Arguments.of(\"a\\uFFFD\", \"a\\uFFFD\"),\n            Arguments.of(\"\\uFFFDb\", \"\\uFFFDb\"),\n            Arguments.of(\"a\\uFFFDb\", \"a\\uFFFDb\"),\n\n            // Number prefix\n            Arguments.of(\"\\\\30 a\", \"0a\"),\n            Arguments.of(\"\\\\31 a\", \"1a\"),\n            Arguments.of(\"\\\\32 a\", \"2a\"),\n            Arguments.of(\"\\\\33 a\", \"3a\"),\n            Arguments.of(\"\\\\34 a\", \"4a\"),\n            Arguments.of(\"\\\\35 a\", \"5a\"),\n            Arguments.of(\"\\\\36 a\", \"6a\"),\n            Arguments.of(\"\\\\37 a\", \"7a\"),\n            Arguments.of(\"\\\\38 a\", \"8a\"),\n            Arguments.of(\"\\\\39 a\", \"9a\"),\n\n            // Letter number prefix\n            Arguments.of(\"a0b\", \"a0b\"),\n            Arguments.of(\"a1b\", \"a1b\"),\n            Arguments.of(\"a2b\", \"a2b\"),\n            Arguments.of(\"a3b\", \"a3b\"),\n            Arguments.of(\"a4b\", \"a4b\"),\n            Arguments.of(\"a5b\", \"a5b\"),\n            Arguments.of(\"a6b\", \"a6b\"),\n            Arguments.of(\"a7b\", \"a7b\"),\n            Arguments.of(\"a8b\", \"a8b\"),\n            Arguments.of(\"a9b\", \"a9b\"),\n\n            // Dash number prefix\n            Arguments.of(\"-\\\\30 a\", \"-0a\"),\n            Arguments.of(\"-\\\\31 a\", \"-1a\"),\n            Arguments.of(\"-\\\\32 a\", \"-2a\"),\n            Arguments.of(\"-\\\\33 a\", \"-3a\"),\n            Arguments.of(\"-\\\\34 a\", \"-4a\"),\n            Arguments.of(\"-\\\\35 a\", \"-5a\"),\n            Arguments.of(\"-\\\\36 a\", \"-6a\"),\n            Arguments.of(\"-\\\\37 a\", \"-7a\"),\n            Arguments.of(\"-\\\\38 a\", \"-8a\"),\n            Arguments.of(\"-\\\\39 a\", \"-9a\"),\n\n            // Double dash prefix\n            Arguments.of(\"--a\", \"--a\"),\n\n            // Various tests\n            Arguments.of(\"\\\\1 \\\\2 \\\\1e \\\\1f \", \"\\u0001\\u0002\\u001E\\u001F\"),\n            Arguments.of(\"\\u0080\\u002D\\u005F\\u00A9\", \"\\u0080\\u002D\\u005F\\u00A9\"),\n            Arguments.of(\"\\\\7f \\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\", \"\\u007F\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\"),\n            Arguments.of(\"\\u00A0\\u00A1\\u00A2\", \"\\u00A0\\u00A1\\u00A2\"),\n            Arguments.of(\"a0123456789b\", \"a0123456789b\"),\n            Arguments.of(\"abcdefghijklmnopqrstuvwxyz\", \"abcdefghijklmnopqrstuvwxyz\"),\n            Arguments.of(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"),\n\n            Arguments.of(\"hello\\\\\\\\world\", \"hello\\\\world\"), // Backslashes get backslash-escaped\n            Arguments.of(\"hello\\u1234world\", \"hello\\u1234world\"), // Code points greater than U+0080 are preserved\n            Arguments.of(\"\\\\-\", \"-\"), // CSS.escape: Single dash escaped\n\n            Arguments.of(\"\\\\ \\\\!xy\", \"\\u0020\\u0021\\u0078\\u0079\"),\n\n            // astral symbol (U+1D306 TETRAGRAM FOR CENTRE)\n            Arguments.of(\"\\uD834\\uDF06\", \"\\uD834\\uDF06\"),\n\n            // lone surrogates\n            Arguments.of(\"\\uDF06\", \"\\uDF06\"),\n            Arguments.of(\"\\uD834\", \"\\uD834\")\n        );\n    }\n\n    private static Stream<Arguments> escapeCssIdentifier_additionalParameters() {\n        return Stream.of(\n            Arguments.of(\"one\\\\#two\\\\.three\\\\/four\\\\\\\\five\", \"one#two.three/four\\\\five\"),\n            Arguments.of(\"-a\", \"-a\"),\n            Arguments.of(\"--\", \"--\")\n        );\n    }\n\n    @Test public void testNestedQuotes() {\n        validateNestedQuotes(\"<html><body><a id=\\\"identifier\\\" onclick=\\\"func('arg')\\\" /></body></html>\", \"a[onclick*=\\\"('arg\\\"]\");\n        validateNestedQuotes(\"<html><body><a id=\\\"identifier\\\" onclick=func('arg') /></body></html>\", \"a[onclick*=\\\"('arg\\\"]\");\n        validateNestedQuotes(\"<html><body><a id=\\\"identifier\\\" onclick='func(\\\"arg\\\")' /></body></html>\", \"a[onclick*='(\\\"arg']\");\n        validateNestedQuotes(\"<html><body><a id=\\\"identifier\\\" onclick=func(\\\"arg\\\") /></body></html>\", \"a[onclick*='(\\\"arg']\");\n    }\n\n    private static void validateNestedQuotes(String html, String selector) {\n        assertEquals(\"#identifier\", Jsoup.parse(html).select(selector).first().cssSelector());\n    }\n\n    @Test\n    public void chompBalancedThrowIllegalArgumentException() {\n        try {\n            TokenQueue tq = new TokenQueue(\"unbalanced(something(or another)) else\");\n            tq.consumeTo(\"(\");\n            tq.chompBalanced('(', '+');\n            fail(\"should have thrown IllegalArgumentException\");\n        } catch (IllegalArgumentException expected) {\n            assertEquals(\"Did not find balanced marker at 'something(or another)) else'\", expected.getMessage());\n        }\n    }\n\n    @Test\n    public void testQuotedPattern() {\n        final Document doc = Jsoup.parse(\"<div>\\\\) foo1</div><div>( foo2</div><div>1) foo3</div>\");\n        assertEquals(\"\\\\) foo1\",doc.select(\"div:matches(\" + Pattern.quote(\"\\\\)\") + \")\").get(0).childNode(0).toString());\n        assertEquals(\"( foo2\",doc.select(\"div:matches(\" + Pattern.quote(\"(\") + \")\").get(0).childNode(0).toString());\n        assertEquals(\"1) foo3\",doc.select(\"div:matches(\" + Pattern.quote(\"1)\") + \")\").get(0).childNode(0).toString());\n    }\n\n    @Test public void consumeEscapedTag() {\n        TokenQueue q = new TokenQueue(\"p\\\\\\\\p p\\\\.p p\\\\:p p\\\\!p\");\n\n        assertEquals(\"p\\\\p\", q.consumeElementSelector());\n        assertTrue(q.consumeWhitespace());\n\n        assertEquals(\"p.p\", q.consumeElementSelector());\n        assertTrue(q.consumeWhitespace());\n\n        assertEquals(\"p:p\", q.consumeElementSelector());\n        assertTrue(q.consumeWhitespace());\n\n        assertEquals(\"p!p\", q.consumeElementSelector());\n        assertTrue(q.isEmpty());\n    }\n\n    @Test public void consumeEscapedId() {\n        TokenQueue q = new TokenQueue(\"i\\\\.d i\\\\\\\\d\");\n\n        assertEquals(\"i.d\", q.consumeCssIdentifier());\n        assertTrue(q.consumeWhitespace());\n\n        assertEquals(\"i\\\\d\", q.consumeCssIdentifier());\n        assertTrue(q.isEmpty());\n    }\n\n    @Test void escapeAtEof() {\n        TokenQueue q = new TokenQueue(\"Foo\\\\\");\n        String s = q.consumeElementSelector();\n        assertEquals(\"Foo\", s); // no escape, no eof. Just straight up Foo.\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"cssIdentifiers\")\n    @MethodSource(\"cssAdditionalIdentifiers\")\n    void consumeCssIdentifier_WebPlatformTests(String expected, String cssIdentifier) {\n        assertParsedCssIdentifierEquals(expected, cssIdentifier);\n    }\n\n    private static Stream<Arguments> cssIdentifiers() {\n        return Stream.of(\n            // https://github.com/web-platform-tests/wpt/blob/36036fb5212a3fc15fc5750cecb1923ba4071668/dom/nodes/ParentNode-querySelector-escapes.html\n            // - escape hex digit\n            Arguments.of(\"0nextIsWhiteSpace\", \"\\\\30 nextIsWhiteSpace\"),\n            Arguments.of(\"0nextIsNotHexLetters\", \"\\\\30nextIsNotHexLetters\"),\n            Arguments.of(\"0connectHexMoreThan6Hex\", \"\\\\000030connectHexMoreThan6Hex\"),\n            Arguments.of(\"0spaceMoreThan6Hex\", \"\\\\000030 spaceMoreThan6Hex\"),\n\n            // - hex digit special replacement\n            // 1. zero points\n            Arguments.of(\"zero\\uFFFD\", \"zero\\\\0\"),\n            Arguments.of(\"zero\\uFFFD\", \"zero\\\\000000\"),\n            // 2. surrogate points\n            Arguments.of(\"\\uFFFDsurrogateFirst\", \"\\\\d83d surrogateFirst\"),\n            Arguments.of(\"surrogateSecond\\uFFFd\", \"surrogateSecond\\\\dd11\"),\n            Arguments.of(\"surrogatePair\\uFFFD\\uFFFD\", \"surrogatePair\\\\d83d\\\\dd11\"),\n            // 3. out of range points\n            Arguments.of(\"outOfRange\\uFFFD\", \"outOfRange\\\\110000\"),\n            Arguments.of(\"outOfRange\\uFFFD\", \"outOfRange\\\\110030\"),\n            Arguments.of(\"outOfRange\\uFFFD\", \"outOfRange\\\\555555\"),\n            Arguments.of(\"outOfRange\\uFFFD\", \"outOfRange\\\\ffffff\"),\n\n            // - escape anything else\n            Arguments.of(\".comma\", \"\\\\.comma\"),\n            Arguments.of(\"-minus\", \"\\\\-minus\"),\n            Arguments.of(\"g\", \"\\\\g\"),\n\n            // non edge cases\n            Arguments.of(\"aBMPRegular\", \"\\\\61 BMPRegular\"),\n            Arguments.of(\"\\uD83D\\uDD11nonBMP\", \"\\\\1f511 nonBMP\"),\n            Arguments.of(\"00continueEscapes\", \"\\\\30\\\\30 continueEscapes\"),\n            Arguments.of(\"00continueEscapes\", \"\\\\30 \\\\30 continueEscapes\"),\n            Arguments.of(\"continueEscapes00\", \"continueEscapes\\\\30 \\\\30 \"),\n            Arguments.of(\"continueEscapes00\", \"continueEscapes\\\\30 \\\\30\"),\n            Arguments.of(\"continueEscapes00\", \"continueEscapes\\\\30\\\\30 \"),\n            Arguments.of(\"continueEscapes00\", \"continueEscapes\\\\30\\\\30\"),\n\n            // ident tests case from CSS tests of chromium source: https://goo.gl/3Cxdov\n            Arguments.of(\"hello\", \"hel\\\\6Co\"),\n            Arguments.of(\"&B\", \"\\\\26 B\"),\n            Arguments.of(\"hello\", \"hel\\\\6C o\"),\n            Arguments.of(\"spaces\", \"spac\\\\65\\r\\ns\"),\n            Arguments.of(\"spaces\", \"sp\\\\61\\tc\\\\65\\fs\"),\n            Arguments.of(\"test\\uD799\", \"test\\\\D799\"),\n            Arguments.of(\"\\uE000\", \"\\\\E000\"),\n            Arguments.of(\"test\", \"te\\\\s\\\\t\"),\n            Arguments.of(\"spaces in\\tident\", \"spaces\\\\ in\\\\\\tident\"),\n            Arguments.of(\".,:!\", \"\\\\.\\\\,\\\\:\\\\!\"),\n            Arguments.of(\"null\\uFFFD\", \"null\\\\0\"),\n            Arguments.of(\"null\\uFFFD\", \"null\\\\0000\"),\n            Arguments.of(\"large\\uFFFD\", \"large\\\\110000\"),\n            Arguments.of(\"large\\uFFFD\", \"large\\\\23456a\"),\n            Arguments.of(\"surrogate\\uFFFD\", \"surrogate\\\\D800\"),\n            Arguments.of(\"surrogate\\uFFFD\", \"surrogate\\\\0DBAC\"),\n            Arguments.of(\"\\uFFFDsurrogate\", \"\\\\00DFFFsurrogate\"),\n            Arguments.of(\"\\uDBFF\\uDFFF\", \"\\\\10fFfF\"),\n            Arguments.of(\"\\uDBFF\\uDFFF0\", \"\\\\10fFfF0\"),\n            Arguments.of(\"\\uDBC0\\uDC0000\", \"\\\\10000000\"),\n            Arguments.of(\"eof\\uFFFD\", \"eof\\\\\"),\n\n            Arguments.of(\"simple-ident\", \"simple-ident\"),\n            Arguments.of(\"testing123\", \"testing123\"),\n            Arguments.of(\"_underscore\", \"_underscore\"),\n            Arguments.of(\"-text\", \"-text\"),\n            Arguments.of(\"-m\", \"-\\\\6d\"),\n            Arguments.of(\"--abc\", \"--abc\"),\n            Arguments.of(\"--\", \"--\"),\n            Arguments.of(\"--11\", \"--11\"),\n            Arguments.of(\"---\", \"---\"),\n            Arguments.of(\"\\u2003\", \"\\u2003\"),\n            Arguments.of(\"\\u00A0\", \"\\u00A0\"),\n            Arguments.of(\"\\u1234\", \"\\u1234\"),\n            Arguments.of(\"\\uD808\\uDF45\", \"\\uD808\\uDF45\"),\n            Arguments.of(\"\\uFFFD\", \"\\u0000\"),\n            Arguments.of(\"ab\\uFFFDc\", \"ab\\u0000c\")\n        );\n    }\n\n    private static Stream<Arguments> cssAdditionalIdentifiers() {\n        return Stream.of(\n            Arguments.of(\"1st\", \"\\\\31\\r\\nst\"),\n            Arguments.of(\"1\", \"\\\\31\\r\"),\n            Arguments.of(\"1a\", \"\\\\31\\ra\"),\n            Arguments.of(\"1\", \"\\\\031\"),\n            Arguments.of(\"1\", \"\\\\0031\"),\n            Arguments.of(\"1\", \"\\\\00031\"),\n            Arguments.of(\"1\", \"\\\\000031\"),\n            Arguments.of(\"1\", \"\\\\000031\"),\n            Arguments.of(\"a\", \"a\\\\\\nb\")\n        );\n    }\n\n    @Test void consumeCssIdentifierWithEmptyInput() {\n        TokenQueue emptyQueue = new TokenQueue(\"\");\n        Exception exception = assertThrows(IllegalArgumentException.class, emptyQueue::consumeCssIdentifier);\n        assertEquals(\"CSS identifier expected, but end of input found\", exception.getMessage());\n    }\n\n    // Some of jsoup's tests depend on this behavior\n    @Test public void consumeCssIdentifier_invalidButSupportedForBackwardsCompatibility() {\n        assertParsedCssIdentifierEquals(\"1\", \"1\");\n        assertParsedCssIdentifierEquals(\"-\", \"-\");\n        assertParsedCssIdentifierEquals(\"-1\", \"-1\");\n    }\n\n    private static String parseCssIdentifier(String text) {\n        TokenQueue q = new TokenQueue(text);\n        return q.consumeCssIdentifier();\n    }\n\n    private void assertParsedCssIdentifierEquals(String expected, String cssIdentifier) {\n        assertEquals(expected, parseCssIdentifier(cssIdentifier));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/TokeniserStateTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class TokeniserStateTest {\n\n    final char[] whiteSpace = { '\\t', '\\n', '\\r', '\\f', ' ' };\n    final char[] quote = { '\\'', '\"' };\n\n    @Test\n    public void ensureSearchArraysAreSorted() {\n        char[][] arrays = {\n            TokeniserState.attributeNameCharsSorted,\n            TokeniserState.attributeValueUnquoted\n        };\n\n        for (char[] array : arrays) {\n            char[] copy = Arrays.copyOf(array, array.length);\n            Arrays.sort(array);\n            assertArrayEquals(array, copy);\n        }\n    }\n\n    @Test\n    public void testCharacterReferenceInRcdata() {\n        String body = \"<textarea>You&I</textarea>\";\n        Document doc = Jsoup.parse(body);\n        Elements els = doc.select(\"textarea\");\n        assertEquals(\"You&I\", els.text());\n    }\n\n    @Test\n    public void testBeforeTagName() {\n        for (char c : whiteSpace) {\n            String body = String.format(\"<div%c>test</div>\", c);\n            Document doc = Jsoup.parse(body);\n            Elements els = doc.select(\"div\");\n            assertEquals(\"test\", els.text());\n        }\n    }\n\n    @Test\n    public void testEndTagOpen() {\n        String body;\n        Document doc;\n        Elements els;\n\n        body = \"<div>hello world</\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"div\");\n        assertEquals(\"hello world</\", els.text());\n\n        body = \"<div>hello world</div>\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"div\");\n        assertEquals(\"hello world\", els.text());\n\n        body = \"<div>fake</></div>\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"div\");\n        assertEquals(\"fake\", els.text());\n\n        body = \"<div>fake</?</div>\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"div\");\n        assertEquals(\"fake\", els.text());\n    }\n\n    @Test\n    public void testRcdataLessthanSign() {\n        String body;\n        Document doc;\n        Elements els;\n\n        body = \"<textarea><fake></textarea>\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"textarea\");\n        assertEquals(\"<fake>\", els.text());\n\n        body = \"<textarea><open\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"textarea\");\n        assertEquals(\"\", els.text());\n\n        body = \"<textarea>hello world</?fake</textarea>\";\n        doc = Jsoup.parse(body);\n        els = doc.select(\"textarea\");\n        assertEquals(\"hello world</?fake\", els.text());\n    }\n\n    @Test\n    public void testRCDATAEndTagName() {\n        for (char c : whiteSpace) {\n            String body = String.format(\"<textarea>data</textarea%c>\", c);\n            Document doc = Jsoup.parse(body);\n            Elements els = doc.select(\"textarea\");\n            assertEquals(\"data\", els.text());\n        }\n    }\n\n    @Test\n    public void testCommentEndCoverage() {\n        String html = \"<html><head></head><body><img src=foo><!-- <table><tr><td></table> --! --- --><p>Hello</p></body></html>\";\n        Document doc = Jsoup.parse(html);\n\n        Element body = doc.body();\n        Comment comment = (Comment) body.childNode(1);\n        assertEquals(\" <table><tr><td></table> --! --- \", comment.getData());\n        Element p = body.child(1);\n        TextNode text = (TextNode) p.childNode(0);\n        assertEquals(\"Hello\", text.getWholeText());\n    }\n\n    @Test\n    public void testCommentEndBangCoverage() {\n        String html = \"<html><head></head><body><img src=foo><!-- <table><tr><td></table> --!---!>--><p>Hello</p></body></html>\";\n        Document doc = Jsoup.parse(html);\n\n        Element body = doc.body();\n        Comment comment = (Comment) body.childNode(1);\n        assertEquals(\" <table><tr><td></table> --!-\", comment.getData());\n        Element p = body.child(1);\n        TextNode text = (TextNode) p.childNode(0);\n        assertEquals(\"Hello\", text.getWholeText());\n    }\n\n    @Test\n    public void testPublicIdentifiersWithWhitespace() {\n        String expectedOutput = \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD HTML 4.0//EN\\\">\";\n        for (char q : quote) {\n            for (char ws : whiteSpace) {\n                String[] htmls = {\n                        String.format(\"<!DOCTYPE html%cPUBLIC %c-//W3C//DTD HTML 4.0//EN%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html %cPUBLIC %c-//W3C//DTD HTML 4.0//EN%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html PUBLIC%c%c-//W3C//DTD HTML 4.0//EN%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html PUBLIC %c%c-//W3C//DTD HTML 4.0//EN%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html PUBLIC %c-//W3C//DTD HTML 4.0//EN%c%c>\", q, q, ws),\n                        String.format(\"<!DOCTYPE html PUBLIC%c-//W3C//DTD HTML 4.0//EN%c%c>\", q, q, ws)\n                    };\n                for (String html : htmls) {\n                    Document doc = Jsoup.parse(html);\n                    assertEquals(expectedOutput, doc.childNode(0).outerHtml());\n                }\n            }\n        }\n    }\n\n    @Test\n    public void testSystemIdentifiersWithWhitespace() {\n        String expectedOutput = \"<!DOCTYPE html SYSTEM \\\"http://www.w3.org/TR/REC-html40/strict.dtd\\\">\";\n        for (char q : quote) {\n            for (char ws : whiteSpace) {\n                String[] htmls = {\n                        String.format(\"<!DOCTYPE html%cSYSTEM %chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html %cSYSTEM %chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html SYSTEM%c%chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html SYSTEM %c%chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", ws, q, q),\n                        String.format(\"<!DOCTYPE html SYSTEM %chttp://www.w3.org/TR/REC-html40/strict.dtd%c%c>\", q, q, ws),\n                        String.format(\"<!DOCTYPE html SYSTEM%chttp://www.w3.org/TR/REC-html40/strict.dtd%c%c>\", q, q, ws)\n                    };\n                for (String html : htmls) {\n                    Document doc = Jsoup.parse(html);\n                    assertEquals(expectedOutput, doc.childNode(0).outerHtml());\n                }\n            }\n        }\n    }\n\n    @Test\n    public void testPublicAndSystemIdentifiersWithWhitespace() {\n        String expectedOutput = \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD HTML 4.0//EN\\\"\"\n                + \" \\\"http://www.w3.org/TR/REC-html40/strict.dtd\\\">\";\n    \tfor (char q : quote) {\n            for (char ws : whiteSpace) {\n                String[] htmls = {\n                        String.format(\"<!DOCTYPE html PUBLIC %c-//W3C//DTD HTML 4.0//EN%c\"\n                                + \"%c%chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", q, q, ws, q, q),\n                        String.format(\"<!DOCTYPE html PUBLIC %c-//W3C//DTD HTML 4.0//EN%c\"\n                                + \"%chttp://www.w3.org/TR/REC-html40/strict.dtd%c>\", q, q, q, q)\n                    };\n                for (String html : htmls) {\n                    Document doc = Jsoup.parse(html);\n                    assertEquals(expectedOutput, doc.childNode(0).outerHtml());\n                }\n            }\n        }\n    }\n\n    @Test\n    public void testUnconsumeAtBufferBoundary() {\n        String triggeringSnippet = \"<a href=\\\"\\\"foo\";\n        char[] padding = new char[CharacterReader.RefillPoint - triggeringSnippet.length() + 2]; // The \"foo\" part must be just at the limit.\n        Arrays.fill(padding, ' ');\n        String paddedSnippet = String.valueOf(padding) + triggeringSnippet;\n        ParseErrorList errorList = ParseErrorList.tracking(1);\n\n        Parser.parseFragment(paddedSnippet, null, \"\", errorList);\n\n        assertEquals(CharacterReader.RefillPoint - 1, errorList.get(0).getPosition());\n    }\n\n    @Test\n    public void testUnconsumeAfterBufferUp() {\n        // test for after consume() a bufferUp occurs (look-forward) but then attempts to unconsume. Would throw a \"No buffer left to unconsume\"\n        String triggeringSnippet = \"<title>One <span>Two\";\n        char[] padding = new char[CharacterReader.RefillPoint - triggeringSnippet.length() + 8]; // The \"<span\" part must be just at the limit. The \"containsIgnoreCase\" scan does a bufferUp, losing the unconsume\n        Arrays.fill(padding, ' ');\n        String paddedSnippet = String.valueOf(padding) + triggeringSnippet;\n        ParseErrorList errorList = ParseErrorList.tracking(1);\n        Parser.parseFragment(paddedSnippet, null, \"\", errorList);\n        // just asserting we don't get a WTF on unconsume\n    }\n\n    @Test\n    public void testOpeningAngleBracketInsteadOfAttribute() {\n        String triggeringSnippet = \"<html <\";\n        ParseErrorList errorList = ParseErrorList.tracking(1);\n\n        Parser.parseFragment(triggeringSnippet, null, \"\", errorList);\n\n        assertEquals(7, errorList.get(0).getPosition());\n    }\n\n    @Test\n    public void testMalformedSelfClosingTag() {\n        String triggeringSnippet = \"<html /ouch\";\n        ParseErrorList errorList = ParseErrorList.tracking(1);\n\n        Parser.parseFragment(triggeringSnippet, null, \"\", errorList);\n\n        assertEquals(7, errorList.get(0).getPosition());\n    }\n\n    @Test\n    public void rcData() {\n        Document doc = Jsoup.parse(\"<title>One \\0Two</title>\");\n        assertEquals(\"One �Two\", doc.title());\n    }\n\n    @Test\n    public void plaintext() {\n        Document doc = Jsoup.parse(\"<div>One<plaintext><div>Two</plaintext>\\0no < Return\");\n        assertEquals(\"<html><head></head><body><div>One<plaintext>&lt;div&gt;Two&lt;/plaintext&gt;�no &lt; Return</plaintext></div></body></html>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void nullInTag() {\n        Document doc = Jsoup.parse(\"<di\\0v>One</di\\0v>Two\");\n        assertEquals(\"<di�v>One</di�v>Two\", doc.body().html());\n    }\n\n    @Test\n    public void attributeValUnquoted() {\n        Document doc = Jsoup.parse(\"<p name=foo&lt;bar>\");\n        Element p = doc.selectFirst(\"p\");\n        assertEquals(\"foo<bar\", p.attr(\"name\"));\n\n        doc = Jsoup.parse(\"<p foo=\");\n        assertEquals(\"<p foo></p>\", doc.body().html());\n    }\n\n    @Test void customDataTagWithHyphen() {\n        // https://github.com/jhy/jsoup/issues/2332\n\n        TagSet tagSet = TagSet.Html();\n        tagSet.valueOf(\"custom-data\", Parser.NamespaceHtml).set(Tag.Data);\n        tagSet.valueOf(\"custom-rcdata\", Parser.NamespaceHtml).set(Tag.RcData);\n\n        String html = \"<body><custom-data>a < > b</custom-data><p>One</p><custom-rcdata>a < > b</custom-rcdata><p>Two</p>\";\n        Document doc = Jsoup.parse(html, Parser.htmlParser().tagSet(tagSet));\n        assertEquals(\n            \"<custom-data>a < > b</custom-data><p>One</p><custom-rcdata>a &lt; &gt; b</custom-rcdata><p>Two</p>\",\n            TextUtil.normalizeSpaces(doc.body().html()));\n    }\n\n    @Test void customDataTagWithHyphenXml() {\n        String xml = \"<custom-data>a < > b</custom-data><p>One</p><custom-rcdata>a < > b</custom-rcdata><p>Two</p>\";\n        Parser parser = Parser.xmlParser();\n        TagSet tagSet = parser.tagSet();\n        tagSet.valueOf(\"custom-data\", Parser.NamespaceXml).set(Tag.Data);\n        tagSet.valueOf(\"custom-rcdata\", Parser.NamespaceXml).set(Tag.RcData);\n\n        Document doc = Jsoup.parse(xml, parser);\n        assertEquals(\n            \"<custom-data><![CDATA[a < > b]]></custom-data><p>One</p><custom-rcdata>a &lt; &gt; b</custom-rcdata><p>Two</p>\",\n            TextUtil.normalizeSpaces(doc.html()));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/TokeniserTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.*;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\n\nimport static org.jsoup.parser.CharacterReader.BufferSize;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class TokeniserTest {\n    @Test\n    public void bufferUpInAttributeVal() {\n        // https://github.com/jhy/jsoup/issues/967\n\n        // check each double, singlem, unquoted impls\n        String[] quotes = {\"\\\"\", \"'\", \"\"};\n        for (String quote : quotes) {\n            String preamble = \"<img src=\" + quote;\n            String tail = \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\";\n            StringBuilder sb = new StringBuilder(preamble);\n\n            final int charsToFillBuffer = BufferSize - preamble.length();\n            for (int i = 0; i < charsToFillBuffer; i++) {\n                sb.append('a');\n            }\n\n            sb.append('X'); // First character to cross character buffer boundary\n            sb.append(tail).append(quote).append(\">\\n\");\n\n            String html = sb.toString();\n            Document doc = Jsoup.parse(html);\n            String src = doc.select(\"img\").attr(\"src\");\n\n            assertTrue(src.contains(\"X\"), \"Handles for quote \" + quote);\n            assertTrue(src.contains(tail));\n        }\n    }\n\n    @Test public void handleSuperLargeTagNames() {\n        // unlikely, but valid. so who knows.\n\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"LargeTagName\");\n        } while (sb.length() < BufferSize);\n        String tag = sb.toString();\n        String html = \"<\" + tag + \">One</\" + tag + \">\";\n\n        Document doc = Parser.htmlParser().settings(ParseSettings.preserveCase).parseInput(html, \"\");\n        Elements els = doc.select(tag);\n        assertEquals(1, els.size());\n        Element el = els.first();\n        assertNotNull(el);\n        assertEquals(\"One\", el.text());\n        assertEquals(tag, el.tagName());\n    }\n\n    @Test public void handleSuperLargeAttributeName() {\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"LargAttributeName\");\n        } while (sb.length() < BufferSize);\n        String attrName = sb.toString();\n        String html = \"<p \" + attrName + \"=foo>One</p>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.getElementsByAttribute(attrName);\n        assertEquals(1, els.size());\n        Element el = els.first();\n        assertNotNull(el);\n        assertEquals(\"One\", el.text());\n        Attribute attribute = el.attributes().asList().get(0);\n        assertEquals(attrName.toLowerCase(), attribute.getKey());\n        assertEquals(\"foo\", attribute.getValue());\n    }\n\n    @Test public void handleLargeText() {\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"A Large Amount of Text\");\n        } while (sb.length() < BufferSize);\n        String text = sb.toString();\n        String html = \"<p>\" + text + \"</p>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"p\");\n        assertEquals(1, els.size());\n        Element el = els.first();\n\n        assertNotNull(el);\n        assertEquals(text, el.text());\n    }\n\n    @Test public void handleLargeComment() {\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"Quite a comment \");\n        } while (sb.length() < BufferSize);\n        String comment = sb.toString();\n        String html = \"<p><!-- \" + comment + \" --></p>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"p\");\n        assertEquals(1, els.size());\n        Element el = els.first();\n\n        assertNotNull(el);\n        Comment child = (Comment) el.childNode(0);\n        assertEquals(\" \" + comment + \" \", child.getData());\n    }\n\n    @Test public void handleLargeCdata() {\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"Quite a lot of CDATA <><><><>\");\n        } while (sb.length() < BufferSize);\n        String cdata = sb.toString();\n        String html = \"<p><![CDATA[\" + cdata + \"]]></p>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"p\");\n        assertEquals(1, els.size());\n        Element el = els.first();\n\n        assertNotNull(el);\n        TextNode child = (TextNode) el.childNode(0);\n        assertEquals(cdata, el.text());\n        assertEquals(cdata, child.getWholeText());\n    }\n\n    @Test public void handleLargeTitle() {\n        StringBuilder sb = new StringBuilder(BufferSize);\n        do {\n            sb.append(\"Quite a long title\");\n        } while (sb.length() < BufferSize);\n        String title = sb.toString();\n        String html = \"<title>\" + title + \"</title>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"title\");\n        assertEquals(1, els.size());\n        Element el = els.first();\n\n        assertNotNull(el);\n        TextNode child = (TextNode) el.childNode(0);\n        assertEquals(title, el.text());\n        assertEquals(title, child.getWholeText());\n        assertEquals(title, doc.title());\n    }\n\n    @Test public void cp1252Entities() {\n        assertEquals(\"\\u20ac\", Jsoup.parse(\"&#0128;\").text());\n        assertEquals(\"\\u201a\", Jsoup.parse(\"&#0130;\").text());\n        assertEquals(\"\\u20ac\", Jsoup.parse(\"&#x80;\").text());\n    }\n\n    @Test public void cp1252EntitiesProduceError() {\n        Parser parser = new Parser(new HtmlTreeBuilder());\n        parser.setTrackErrors(10);\n        assertEquals(\"\\u20ac\", parser.parseInput(\"<html><body>&#0128;</body></html>\", \"\").text());\n        assertEquals(1, parser.getErrors().size());\n    }\n\n    @Test public void cp1252SubstitutionTable() {\n        for (int i = 0; i < Tokeniser.win1252Extensions.length; i++) {\n            String s = new String(new byte[]{ (byte) (i + Tokeniser.win1252ExtensionsStart) }, Charset.forName(\"Windows-1252\"));\n            assertEquals(1, s.length());\n\n            // some of these characters are illegal\n            if (s.charAt(0) == '\\ufffd') { continue; }\n\n            assertEquals(s.charAt(0), Tokeniser.win1252Extensions[i], \"At: \" + i);\n        }\n    }\n\n    @Test public void canParseVeryLongBogusComment() {\n        StringBuilder commentData = new StringBuilder(BufferSize);\n        do {\n            commentData.append(\"blah blah blah blah \");\n        } while (commentData.length() < BufferSize);\n        String expectedCommentData = commentData.toString();\n        String testMarkup = \"<html><body><!\" + expectedCommentData + \"></body></html>\";\n        Parser parser = new Parser(new HtmlTreeBuilder());\n\n        Document doc = parser.parseInput(testMarkup, \"\");\n\n        Node commentNode = doc.body().childNode(0);\n        assertTrue(commentNode instanceof Comment, \"Expected comment node\");\n        assertEquals(expectedCommentData, ((Comment)commentNode).getData());\n    }\n\n    @Test public void canParseCdataEndingAtEdgeOfBuffer() {\n        String cdataStart = \"<![CDATA[\";\n        String cdataEnd = \"]]>\";\n        int bufLen = BufferSize - cdataStart.length() - 1;    // also breaks with -2, but not with -3 or 0\n        char[] cdataContentsArray = new char[bufLen];\n        Arrays.fill(cdataContentsArray, 'x');\n        String cdataContents = new String(cdataContentsArray);\n        String testMarkup = cdataStart + cdataContents + cdataEnd;\n        Parser parser = new Parser(new HtmlTreeBuilder());\n\n        Document doc = parser.parseInput(testMarkup, \"\");\n\n        Node cdataNode = doc.body().childNode(0);\n        assertTrue(cdataNode instanceof CDataNode, \"Expected CDATA node\");\n        assertEquals(cdataContents, ((CDataNode)cdataNode).text());\n    }\n\n    @Test void tokenDataToString() {\n        TokenData data = new TokenData();\n        assertEquals(\"\", data.toString());\n        data.set(\"abc\");\n        assertEquals(\"abc\", data.toString());\n        data.append(\"def\");\n        assertEquals(\"abcdef\", data.toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/parser/XmlTreeBuilderTest.java",
    "content": "package org.jsoup.parser;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.*;\nimport org.jsoup.select.Elements;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\nimport static org.jsoup.nodes.Document.OutputSettings.Syntax;\nimport static org.jsoup.parser.Parser.NamespaceHtml;\nimport static org.jsoup.parser.Parser.NamespaceXml;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests XmlTreeBuilder.\n *\n * @author Jonathan Hedley\n */\npublic class XmlTreeBuilderTest {\n    @Test\n    public void testSimpleXmlParse() {\n        String xml = \"<doc id=2 href='/bar'>Foo <br /><link>One</link><link>Two</link></doc>\";\n        XmlTreeBuilder tb = new XmlTreeBuilder();\n        Document doc = tb.parse(xml, \"http://foo.com/\");\n        assertEquals(\"<doc id=\\\"2\\\" href=\\\"/bar\\\">Foo <br /><link>One</link><link>Two</link></doc>\",\n                TextUtil.stripNewlines(doc.html()));\n        assertEquals(doc.getElementById(\"2\").absUrl(\"href\"), \"http://foo.com/bar\");\n    }\n\n    @Test\n    public void testPopToClose() {\n        // test: </val> closes Two, </bar> ignored\n        String xml = \"<doc><val>One<val>Two</val></bar>Three</doc>\";\n        XmlTreeBuilder tb = new XmlTreeBuilder();\n        Document doc = tb.parse(xml, \"http://foo.com/\");\n        assertEquals(\"<doc><val>One<val>Two</val>Three</val></doc>\",\n                TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void testCommentAndDocType() {\n        String xml = \"<!DOCTYPE HTML><!-- a comment -->One <qux />Two\";\n        XmlTreeBuilder tb = new XmlTreeBuilder();\n        Document doc = tb.parse(xml, \"http://foo.com/\");\n        assertEquals(\"<!DOCTYPE HTML><!-- a comment -->One <qux />Two\",\n                TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void testSupplyParserToJsoupClass() {\n        String xml = \"<doc><val>One<val>Two</val></bar>Three</doc>\";\n        Document doc = Jsoup.parse(xml, \"http://foo.com/\", Parser.xmlParser());\n        assertEquals(\"<doc><val>One<val>Two</val>Three</val></doc>\",\n                TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void testSupplyParserToDataStream() throws IOException, URISyntaxException {\n        File xmlFile = new File(XmlTreeBuilder.class.getResource(\"/htmltests/xml-test.xml\").toURI());\n        InputStream inStream = new FileInputStream(xmlFile);\n        Document doc = Jsoup.parse(inStream, null, \"http://foo.com\", Parser.xmlParser());\n        assertEquals(\"<doc><val>One<val>Two</val>Three</val></doc>\",\n                TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void testDoesNotForceSelfClosingKnownTags() {\n        // html will force \"<br>one</br>\" to logically \"<br />One<br />\". XML should be stay \"<br>one</br> -- don't recognise tag.\n        Document htmlDoc = Jsoup.parse(\"<br>one</br>\");\n        assertEquals(\"<br>\\none\\n<br>\", htmlDoc.body().html());\n\n        Document xmlDoc = Jsoup.parse(\"<br>one</br>\", \"\", Parser.xmlParser());\n        assertEquals(\"<br>one</br>\", xmlDoc.html());\n    }\n\n    @Test public void handlesXmlDeclarationAsDeclaration() {\n        String html = \"<?xml encoding='UTF-8' ?><body>One</body><!-- comment -->\";\n        Document doc = Jsoup.parse(html, \"\", Parser.xmlParser());\n        assertEquals(\"<?xml encoding=\\\"UTF-8\\\"?><body>One</body><!-- comment -->\",doc.outerHtml());\n        assertEquals(\"#declaration\", doc.childNode(0).nodeName());\n        assertEquals(\"#comment\", doc.childNode(2).nodeName());\n    }\n\n    @Test public void xmlFragment() {\n        String xml = \"<one src='/foo/' />Two<three><four /></three>\";\n        List<Node> nodes = Parser.parseXmlFragment(xml, \"http://example.com/\");\n        assertEquals(3, nodes.size());\n\n        assertEquals(\"http://example.com/foo/\", nodes.get(0).absUrl(\"src\"));\n        assertEquals(\"one\", nodes.get(0).nodeName());\n        assertEquals(\"Two\", ((TextNode)nodes.get(1)).text());\n    }\n\n    @Test public void xmlParseDefaultsToHtmlOutputSyntax() {\n        Document doc = Jsoup.parse(\"x\", \"\", Parser.xmlParser());\n        assertEquals(Syntax.xml, doc.outputSettings().syntax());\n    }\n\n    @Test\n    public void testDoesHandleEOFInTag() {\n        String html = \"<img src=asdf onerror=\\\"alert(1)\\\" x=\";\n        Document xmlDoc = Jsoup.parse(html, \"\", Parser.xmlParser());\n        assertEquals(\"<img src=\\\"asdf\\\" onerror=\\\"alert(1)\\\" x=\\\"\\\"></img>\", xmlDoc.html());\n    }\n\n    @Test\n    public void testDetectCharsetEncodingDeclaration() throws IOException, URISyntaxException {\n        File xmlFile = new File(XmlTreeBuilder.class.getResource(\"/htmltests/xml-charset.xml\").toURI());\n        InputStream inStream = new FileInputStream(xmlFile);\n        Document doc = Jsoup.parse(inStream, null, \"http://example.com/\", Parser.xmlParser());\n        assertEquals(\"ISO-8859-1\", doc.charset().name());\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"ISO-8859-1\\\"?><data>äöåéü</data>\",\n            TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void testParseDeclarationAttributes() {\n        String xml = \"<?xml version='1' encoding='UTF-8' something='else'?><val>One</val>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        XmlDeclaration decl = (XmlDeclaration) doc.childNode(0);\n        assertEquals(\"1\", decl.attr(\"version\"));\n        assertEquals(\"UTF-8\", decl.attr(\"encoding\"));\n        assertEquals(\"else\", decl.attr(\"something\"));\n        assertEquals(\"version=\\\"1\\\" encoding=\\\"UTF-8\\\" something=\\\"else\\\"\", decl.getWholeDeclaration());\n        assertEquals(\"<?xml version=\\\"1\\\" encoding=\\\"UTF-8\\\" something=\\\"else\\\"?>\", decl.outerHtml());\n    }\n\n    @Test\n    public void testParseDeclarationWithoutAttributes() {\n        String xml = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\\n<?myProcessingInstruction My Processing instruction.?>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        XmlDeclaration decl = (XmlDeclaration) doc.childNode(2);\n        assertEquals(\"myProcessingInstruction\", decl.name());\n        assertTrue(decl.hasAttr(\"My\"));\n        assertEquals(\"<?myProcessingInstruction My Processing instruction.?>\", decl.outerHtml());\n    }\n\n    @Test\n    public void caseSensitiveDeclaration() {\n        String xml = \"<?XML version='1' encoding='UTF-8' something='else'?>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        assertEquals(\"<?XML version=\\\"1\\\" encoding=\\\"UTF-8\\\" something=\\\"else\\\"?>\", doc.outerHtml());\n    }\n\n    @Test\n    public void testCreatesValidProlog() {\n        Document document = Document.createShell(\"\");\n        document.outputSettings().syntax(Syntax.xml);\n        document.charset(StandardCharsets.UTF_8);\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\" +\n            \"<html>\\n\" +\n            \" <head></head>\\n\" +\n            \" <body></body>\\n\" +\n            \"</html>\", document.outerHtml());\n    }\n\n    @Test\n    public void preservesCaseByDefault() {\n        String xml = \"<CHECK>One</CHECK><TEST ID=1>Check</TEST>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        assertEquals(\"<CHECK>One</CHECK><TEST ID=\\\"1\\\">Check</TEST>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void appendPreservesCaseByDefault() {\n        String xml = \"<One>One</One>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        Elements one = doc.select(\"One\");\n        one.append(\"<Two ID=2>Two</Two>\");\n        assertEquals(\"<One>One<Two ID=\\\"2\\\">Two</Two></One>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test\n    public void disablesPrettyPrintingByDefault() {\n        String xml = \"\\n\\n<div><one>One</one><one>\\n Two</one>\\n</div>\\n \";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        assertEquals(xml, doc.html());\n    }\n\n    @Test\n    public void canNormalizeCase() {\n        String xml = \"<TEST ID=1>Check</TEST>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser().settings(ParseSettings.htmlDefault));\n        assertEquals(\"<test id=\\\"1\\\">Check</test>\", TextUtil.stripNewlines(doc.html()));\n    }\n\n    @Test public void normalizesDiscordantTags() {\n        Parser parser = Parser.xmlParser().settings(ParseSettings.htmlDefault);\n        Document document = Jsoup.parse(\"<div>test</DIV><p></p>\", \"\", parser);\n        assertEquals(\"<div>test</div><p></p>\", document.html());\n        // was failing -> toString() = \"<div>\\n test\\n <p></p>\\n</div>\"\n    }\n\n    @Test public void roundTripsCdata() {\n        String xml = \"<div id=1><![CDATA[\\n<html>\\n <foo><&amp;]]></div>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n\n        Element div = doc.getElementById(\"1\");\n        assertEquals(\"<html>\\n <foo><&amp;\", div.text());\n        assertEquals(0, div.children().size());\n        assertEquals(1, div.childNodeSize()); // no elements, one text node\n\n        assertEquals(\"<div id=\\\"1\\\"><![CDATA[\\n<html>\\n <foo><&amp;]]></div>\", div.outerHtml());\n\n        CDataNode cdata = (CDataNode) div.textNodes().get(0);\n        assertEquals(\"\\n<html>\\n <foo><&amp;\", cdata.text());\n    }\n\n    @Test public void cdataPreservesWhiteSpace() {\n        String xml = \"<script type=\\\"text/javascript\\\">//<![CDATA[\\n\\n  foo();\\n//]]></script>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        assertEquals(xml, doc.outerHtml());\n\n        assertEquals(\"//\\n\\n  foo();\\n//\", doc.selectFirst(\"script\").text());\n    }\n\n    @Test\n    public void handlesDodgyXmlDecl() {\n        String xml = \"<?xml version='1.0'><val>One</val>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n        assertEquals(\"One\", doc.select(\"val\").text());\n    }\n\n    @Test\n    public void handlesLTinScript() {\n        // https://github.com/jhy/jsoup/issues/1139\n        String html = \"<script> var a=\\\"<?\\\"; var b=\\\"?>\\\"; </script>\";\n        Document doc = Jsoup.parse(html, \"\", Parser.xmlParser());\n        assertEquals(\"<script> var a=\\\"<!--?\\\"; var b=\\\"?-->\\\"; </script>\", doc.html()); // converted from pseudo xmldecl to comment\n    }\n\n    @Test public void dropsDuplicateAttributes() {\n        // case sensitive, so should drop Four and Five\n        String html = \"<p One=One ONE=Two one=Three One=Four ONE=Five two=Six two=Seven Two=Eight>Text</p>\";\n        Parser parser = Parser.xmlParser().setTrackErrors(10);\n        Document doc = parser.parseInput(html, \"\");\n\n        assertEquals(\"<p One=\\\"One\\\" ONE=\\\"Two\\\" one=\\\"Three\\\" two=\\\"Six\\\" Two=\\\"Eight\\\">Text</p>\", doc.selectFirst(\"p\").outerHtml());\n    }\n\n    @Test public void readerClosedAfterParse() {\n        Document doc = Jsoup.parse(\"Hello\", \"\", Parser.xmlParser());\n        TreeBuilder treeBuilder = doc.parser().getTreeBuilder();\n        assertNull(treeBuilder.reader);\n        assertNull(treeBuilder.tokeniser);\n    }\n\n    @Test public void xmlParserEnablesXmlOutputAndEscapes() {\n        // Test that when using the XML parser, the output mode and escape mode default to XHTML entities\n        Document doc = Jsoup.parse(\"<root/>\", \"\", Parser.xmlParser());\n        assertEquals(doc.outputSettings().syntax(), Syntax.xml);\n        assertEquals(doc.outputSettings().escapeMode(), Entities.EscapeMode.xhtml);\n    }\n\n    @Test public void xmlSyntaxAlwaysEscapesLtAndGtInAttributeValues() {\n        // https://github.com/jhy/jsoup/issues/2337\n        Document doc = Jsoup.parse(\"<p one='&lt;two&gt;'>Three</p>\", \"\", Parser.xmlParser());\n        doc.outputSettings().escapeMode(Entities.EscapeMode.extended);\n        assertEquals(doc.outputSettings().syntax(), Syntax.xml);\n        assertEquals(\"<p one=\\\"&lt;two&gt;\\\">Three</p>\", doc.html());\n    }\n\n    @Test void xmlOutputCorrectsInvalidAttributeNames() {\n        String xml = \"<body style=\\\"color: red\\\" \\\" name\\\"><div =\\\"\\\"></div></body>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        assertEquals(Syntax.xml, doc.outputSettings().syntax());\n\n        String out = doc.html();\n        assertEquals(\"<body style=\\\"color: red\\\" _=\\\"\\\" name_=\\\"\\\"><div _=\\\"\\\"></div></body>\", out);\n    }\n\n    @Test void xmlValidAttributes() {\n        String xml = \"<a bB1-_:.=foo _9!=bar xmlns:p1=qux>One</a>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        assertEquals(Syntax.xml, doc.outputSettings().syntax());\n\n        String out = doc.html();\n        assertEquals(\"<a bB1-_:.=\\\"foo\\\" _9_=\\\"bar\\\" xmlns:p1=\\\"qux\\\">One</a>\", out); // first is same, second coerced\n    }\n\n    @Test void customTagsAreFlyweights() {\n        String xml = \"<foo>Foo</foo><foo>Foo</foo><FOO>FOO</FOO><FOO>FOO</FOO>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        Elements els = doc.children();\n\n        Tag t1 = els.get(0).tag();\n        Tag t2 = els.get(1).tag();\n        Tag t3 = els.get(2).tag();\n        Tag t4 = els.get(3).tag();\n        assertEquals(\"foo\", t1.getName());\n        assertEquals(\"FOO\", t3.getName());\n        assertSame(t1, t2);\n        assertSame(t3, t4);\n    }\n\n    @Test void rootHasXmlSettings() {\n        Document doc = Jsoup.parse(\"<foo>\", Parser.xmlParser());\n        ParseSettings settings = doc.parser().settings();\n        assertTrue(settings.preserveTagCase());\n        assertTrue(settings.preserveAttributeCase());\n        assertEquals(NamespaceXml, doc.parser().defaultNamespace());\n    }\n\n    @Test void xmlNamespace() {\n        String xml = \"<foo><bar><div><svg><math>Qux</bar></foo>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        assertXmlNamespace(doc);\n        Elements els = doc.select(\"*\");\n        for (Element el : els) {\n            assertXmlNamespace(el);\n        }\n\n        Document clone = doc.clone();\n        assertXmlNamespace(clone);\n        assertXmlNamespace(clone.expectFirst(\"bar\"));\n\n        Document shallow = doc.shallowClone();\n        assertXmlNamespace(shallow);\n    }\n\n    private static void assertXmlNamespace(Element el) {\n        assertEquals(NamespaceXml, el.tag().namespace(), String.format(\"Element %s not in XML namespace\", el.tagName()));\n    }\n\n    @Test void declarations() {\n        String xml = \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?><!DOCTYPE html\\n\" +\n            \"  PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\"\\n\" +\n            \"  \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\" +\n            \"<!ELEMENT footnote (#PCDATA|a)*>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        XmlDeclaration proc = (XmlDeclaration) doc.childNode(0);\n        DocumentType doctype = (DocumentType) doc.childNode(1);\n        XmlDeclaration decl = (XmlDeclaration) doc.childNode(2);\n\n        assertEquals(\"xml\", proc.name());\n        assertEquals(\"1.0\", proc.attr(\"version\"));\n        assertEquals(\"utf-8\", proc.attr(\"encoding\"));\n        assertEquals(\"version=\\\"1.0\\\" encoding=\\\"utf-8\\\"\", proc.getWholeDeclaration());\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\", proc.outerHtml());\n\n        assertEquals(\"html\", doctype.name());\n        assertEquals(\"-//W3C//DTD XHTML 1.0 Transitional//EN\", doctype.attr(\"publicId\"));\n        assertEquals(\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\", doctype.attr(\"systemId\"));\n        assertEquals(\"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\", doctype.outerHtml());\n\n        assertEquals(\"ELEMENT\", decl.name());\n        assertEquals(\"footnote (#PCDATA|a)*\", decl.getWholeDeclaration());\n        assertTrue(decl.hasAttr(\"footNote\"));\n        assertFalse(decl.hasAttr(\"ELEMENT\"));\n        assertEquals(\"<!ELEMENT footnote (#PCDATA|a)*>\", decl.outerHtml());\n\n        assertEquals(\"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\" +\n            \"<!DOCTYPE html PUBLIC \\\"-//W3C//DTD XHTML 1.0 Transitional//EN\\\" \\\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\\\">\" +\n            \"<!ELEMENT footnote (#PCDATA|a)*>\", doc.outerHtml());\n    }\n\n    @Test void declarationWithGt() {\n        // https://github.com/jhy/jsoup/issues/1947\n        String xml = \"<x><?xmlDeclaration att1=\\\"value1\\\" att2=\\\"&lt;val2>\\\"?></x>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        XmlDeclaration decl = (XmlDeclaration) doc.expectFirst(\"x\").childNode(0);\n        assertEquals(\"<val2>\", decl.attr(\"att2\"));\n    }\n\n    @Test void xmlHeaderIsValid() {\n        // https://github.com/jhy/jsoup/issues/2298\n        String xml = \"<?xml version=\\\"1.0\\\"?>\\n<root></root>\";\n        String expect = xml;\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser().setTrackErrors(10));\n        assertEquals(0, doc.parser().getErrors().size());\n        assertEquals(expect, doc.html());\n\n        xml =  \"<?xml version=\\\"1.0\\\" ?>\\n<root></root>\";\n        doc = Jsoup.parse(xml, Parser.xmlParser().setTrackErrors(10));\n        assertEquals(0, doc.parser().getErrors().size());\n        assertEquals(expect, doc.html());\n    }\n\n    @Test void canSetCustomRcdataTag() {\n        String inner = \"Blah\\nblah\\n<foo></foo>&quot;\";\n        String innerText = \"Blah\\nblah\\n<foo></foo>\\\"\";\n\n        String xml = \"<x><y><z>\" + inner + \"</z></y></x><x><z id=2></z>\";\n        TagSet custom = new TagSet();\n        Tag z = custom.valueOf(\"z\", NamespaceXml, ParseSettings.preserveCase);\n        z.set(Tag.RcData);\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser().tagSet(custom));\n        Element zEl = doc.expectFirst(\"z\");\n        assertNotSame(z, zEl.tag()); // not same because we copy the tagset\n        assertEquals(z, zEl.tag());\n\n        assertEquals(1, zEl.childNodeSize());\n        Node child = zEl.childNode(0);\n        assertTrue(child instanceof TextNode);\n        assertEquals(innerText, ((TextNode) child).getWholeText());\n\n        // test fragment context parse - should parse <foo> as text\n        Element z2 = doc.expectFirst(\"#2\");\n        z2.html(inner);\n        assertEquals(innerText, z2.wholeText());\n    }\n\n    @Test void canSetCustomDataTag() {\n        String inner = \"Blah\\nblah\\n<foo></foo>&quot;\"; // no character refs, will be as-is\n\n        String xml = \"<x><y><z>\" + inner + \"</z></y></x><x><z id=2></z>\";\n        TagSet custom = new TagSet();\n        Tag z = custom.valueOf(\"z\", NamespaceXml, ParseSettings.preserveCase);\n        z.set(Tag.Data);\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser().tagSet(custom));\n        Element zEl = doc.expectFirst(\"z\");\n        assertNotSame(z, zEl.tag()); // not same because we copy the tagset\n        assertEquals(z, zEl.tag());\n\n        assertEquals(1, zEl.childNodeSize());\n        Node child = zEl.childNode(0);\n        assertTrue(child instanceof DataNode);\n        assertEquals(inner, ((DataNode) child).getWholeData());\n        assertEquals(inner, zEl.data());\n\n        // test fragment context parse - should parse <foo> as data\n        Element z2 = doc.expectFirst(\"#2\");\n        z2.html(inner);\n        assertEquals(inner, ((DataNode) child).getWholeData());\n        assertEquals(inner, zEl.data());\n    }\n\n    @Test void canSetCustomVoid() {\n        String ns = \"custom\";\n        String xml = \"<x xmlns=custom><foo><link><meta>\";\n        TagSet custom = new TagSet();\n        custom.valueOf(\"link\", ns).set(Tag.Void);\n        custom.valueOf(\"meta\", ns).set(Tag.Void);\n        custom.valueOf(\"foo\", \"other\").set(Tag.Void); // ns doesn't match, won't impact\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser().tagSet(custom));\n        String expect = \"<x xmlns=\\\"custom\\\"><foo><link /><meta /></foo></x>\";\n        assertEquals(expect, doc.html());\n    }\n\n    @Test void canSupplyWithHtmlTagSet() {\n        // use the properties of html tag set but without HtmlTreeBuilder rules\n        String xml = \"<html xmlns=\" + NamespaceHtml + \"><div><script>a<b</script><img><p>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser().tagSet(TagSet.Html()));\n        doc.outputSettings().prettyPrint(true);\n        String expect = \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n\" +\n            \" <div>\\n\" +\n            \"  <script>//<![CDATA[\\n\" +\n            \"a<b\\n\" +\n            \"//]]></script>\\n\" +\n            \"  <img />\\n\" +\n            \"  <p></p>\\n\" +\n            \" </div>\\n\" +\n            \"</html>\";\n        assertEquals(expect, doc.html());\n\n        doc.outputSettings().syntax(Syntax.html);\n        expect = \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n\" +\n            \" <div>\\n\" +\n            \"  <script>a<b</script>\\n\" +\n            \"  <img>\\n\" +\n            \"  <p></p>\\n\" +\n            \" </div>\\n\" +\n            \"</html>\";\n        assertEquals(expect, doc.html());\n    }\n\n    @Test void prettyFormatsTextInline() {\n        // https://github.com/jhy/jsoup/issues/2141\n        String xml = \"<package><metadata xmlns:dc=\\\"http://purl.org/dc/elements/1.1/\\\">\\n\" +\n            \"<dc:identifier id=\\\"pub-id\\\">id</dc:identifier>\\n\" +\n            \"<dc:title>title</dc:title>\\n\" +\n            \"<dc:language>ja</dc:language>\\n\" +\n            \"<dc:description>desc</dc:description>\\n\" +\n            \"</metadata></package>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        doc.outputSettings().prettyPrint(true);\n        assertEquals(\"<package>\\n\" +\n            \" <metadata xmlns:dc=\\\"http://purl.org/dc/elements/1.1/\\\">\\n\" +\n            \"  <dc:identifier id=\\\"pub-id\\\">id</dc:identifier> <dc:title>title</dc:title> <dc:language>ja</dc:language> <dc:description>desc</dc:description>\\n\" +\n            \" </metadata>\\n\" +\n            \"</package>\", doc.html());\n\n        // can customize\n        Element meta = doc.expectFirst(\"metadata\");\n        Tag metaTag = meta.tag();\n        metaTag.set(Tag.Block);\n        // set all the inner els of meta to be blocks\n        for (Element inner : meta) inner.tag().set(Tag.Block);\n\n        assertEquals(\"<package>\\n\" +\n            \" <metadata xmlns:dc=\\\"http://purl.org/dc/elements/1.1/\\\">\\n\" +\n            \"  <dc:identifier id=\\\"pub-id\\\">id</dc:identifier>\\n\" +\n            \"  <dc:title>title</dc:title>\\n\" +\n            \"  <dc:language>ja</dc:language>\\n\" +\n            \"  <dc:description>desc</dc:description>\\n\" +\n            \" </metadata>\\n\" +\n            \"</package>\", doc.html());\n    }\n\n    // namespace tests\n    @Test void xmlns() {\n        // example from the xml namespace spec https://www.w3.org/TR/xml-names/\n        String xml = \"<?xml version=\\\"1.0\\\"?>\\n\" +\n            \"<!-- both namespace prefixes are available throughout -->\\n\" +\n            \"<bk:book xmlns:bk=\\\"urn:loc.gov:books\\\" xmlns:isbn=\\\"urn:ISBN:0-395-36341-6\\\">\\n\" +\n            \"    <bk:title>Cheaper by the Dozen</bk:title>\\n\" +\n            \"    <isbn:number>1568491379</isbn:number>\\n\" +\n            \"</bk:book>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        Element book = doc.expectFirst(\"bk|book\");\n        assertEquals(\"bk:book\", book.tag().name());\n        assertEquals(\"bk\", book.tag().prefix());\n        assertEquals(\"book\", book.tag().localName());\n        assertEquals(\"urn:loc.gov:books\", book.tag().namespace());\n\n        Element title = doc.expectFirst(\"bk|title\");\n        assertEquals(\"bk:title\", title.tag().name());\n        assertEquals(\"urn:loc.gov:books\", title.tag().namespace());\n\n        Element number = doc.expectFirst(\"isbn|number\");\n        assertEquals(\"isbn:number\", number.tag().name());\n        assertEquals(\"urn:ISBN:0-395-36341-6\", number.tag().namespace());\n\n        // and we didn't modify the dom\n        assertEquals(xml, doc.html());\n    }\n\n    @Test void unprefixedDefaults() {\n        String xml = \"<?xml version=\\\"1.0\\\"?>\\n\" +\n            \"<!-- elements are in the HTML namespace, in this case by default -->\\n\" +\n            \"<html xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n\" +\n            \"  <head><title>Frobnostication</title></head>\\n\" +\n            \"  <body><p>Moved to \\n\" +\n            \"    <a href=\\\"http://frob.example.com\\\">here</a>.</p></body>\\n\" +\n            \"</html>\";\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        Element html = doc.expectFirst(\"html\");\n        assertEquals(NamespaceHtml, html.tag().namespace());\n        Element a = doc.expectFirst(\"a\");\n        assertEquals(NamespaceHtml, a.tag().namespace());\n    }\n\n    @Test void emptyDefault() {\n        String xml = \"<?xml version='1.0'?>\\n\" +\n            \"<Beers>\\n\" +\n            \"  <!-- the default namespace inside tables is that of HTML -->\\n\" +\n            \"  <table xmlns='http://www.w3.org/1999/xhtml'>\\n\" +\n            \"   <th><td>Name</td><td>Origin</td><td>Description</td></th>\\n\" +\n            \"   <tr> \\n\" +\n            \"     <!-- no default namespace inside table cells -->\\n\" +\n            \"     <td><brandName xmlns=\\\"\\\">Huntsman</brandName></td>\\n\" +\n            \"     <td><origin xmlns=\\\"\\\">Bath, UK</origin></td>\\n\" +\n            \"     <td>\\n\" +\n            \"       <details xmlns=\\\"\\\"><class>Bitter</class><hop>Fuggles</hop>\\n\" +\n            \"         <pro>Wonderful hop, light alcohol, good summer beer</pro>\\n\" +\n            \"         <con>Fragile; excessive variance pub to pub</con>\\n\" +\n            \"         </details>\\n\" +\n            \"        </td>\\n\" +\n            \"      </tr>\\n\" +\n            \"    </table>\\n\" +\n            \"  </Beers>\";\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        Element beers = doc.expectFirst(\"Beers\");\n        assertEquals(NamespaceXml, beers.tag().namespace());\n        Element td = doc.expectFirst(\"td\");\n        assertEquals(NamespaceHtml, td.tag().namespace());\n        Element origin = doc.expectFirst(\"origin\");\n        assertEquals(\"\", origin.tag().namespace());\n        Element pro = doc.expectFirst(\"pro\");\n        assertEquals(\"\", pro.tag().namespace());\n    }\n\n    @Test void namespacedAttribute() {\n        String xml = \"<x xmlns:edi='http://ecommerce.example.org/schema'>\\n\" +\n            \"  <!-- the 'taxClass' attribute's namespace is http://ecommerce.example.org/schema -->\\n\" +\n            \"  <lineItem edi:taxClass=\\\"exempt\\\" other=foo>Baby food</lineItem>\\n\" +\n            \"</x>\";\n\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n        Element lineItem = doc.expectFirst(\"lineItem\");\n\n        Attribute taxClass = lineItem.attribute(\"edi:taxClass\");\n        assertNotNull(taxClass);\n        assertEquals(\"edi\", taxClass.prefix());\n        assertEquals(\"taxClass\", taxClass.localName());\n        assertEquals(\"http://ecommerce.example.org/schema\", taxClass.namespace());\n\n        Attribute other = lineItem.attribute(\"other\");\n        assertNotNull(other);\n        assertEquals(\"foo\", other.getValue());\n        assertEquals(\"\", other.prefix());\n        assertEquals(\"other\", other.localName());\n        assertEquals(\"\", other.namespace());\n    }\n\n    @Test void elementsViaAppendHtmlAreNamespaced() {\n        // tests that when elements / attributes are added via a fragment parse, they inherit the namespace stack, and can still override\n        String xml = \"<out xmlns='/out'><bk:book xmlns:bk='/books' xmlns:edi='/edi'><bk:title>Test</bk:title><li edi:foo='bar'></bk:book></out>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        // insert some parsed xml, inherit bk and edi, and with an inner node override bk\n        Element book = doc.expectFirst(\"bk|book\");\n        book.append(\"<bk:content edi:foo=qux>Content</bk:content>\");\n\n        Element out = doc.expectFirst(\"out\");\n        assertEquals(\"/out\", out.tag().namespace());\n\n        Element content = book.expectFirst(\"bk|content\");\n        assertEquals(\"bk:content\", content.tag().name());\n        assertEquals(\"/books\", content.tag().namespace());\n        assertEquals(\"/edi\", content.attribute(\"edi:foo\").namespace());\n\n        content.append(\"<data>Data</data><html xmlns='/html' xmlns:bk='/update'><p>Foo</p><bk:news>News</bk:news></html>\");\n        // p should be in /html, news in /update\n        Element p = content.expectFirst(\"p\");\n        assertEquals(\"/html\", p.tag().namespace());\n        Element news = content.expectFirst(\"bk|news\");\n        assertEquals(\"/update\", news.tag().namespace());\n        Element data = content.expectFirst(\"data\");\n        assertEquals(\"/out\", data.tag().namespace());\n    }\n\n    @Test void selfClosingOK() {\n        // In XML, all tags can be self-closing regardless of tag type\n        Parser parser = Parser.xmlParser().setTrackErrors(10);\n        String xml = \"<div id='1'/><p/><div>Foo</div><div></div><foo></foo>\";\n        Document doc = Jsoup.parse(xml, \"\", parser);\n        ParseErrorList errors = parser.getErrors();\n        assertEquals(0, errors.size());\n        assertEquals(\"<div id=\\\"1\\\" /><p /><div>Foo</div><div /><foo></foo>\", TextUtil.stripNewlines(doc.outerHtml()));\n        // we infer that empty els can be represented with self-closing if seen in parse\n    }\n\n    @Test public void xmlParserHasUnlimitedDepthByDefault() {\n        Parser parser = Parser.xmlParser();\n        Document doc = Jsoup.parse(deepXml(600), \"\", parser);\n        Element target = doc.selectFirst(\"target\");\n        assertNotNull(target);\n        assertTrue(depth(target) > 512);\n    }\n\n    @Test public void xmlParserRespectsConfiguredMaxDepth() {\n        Parser parser = Parser.xmlParser().setMaxDepth(5);\n        Document doc = Jsoup.parse(deepXml(100), \"\", parser);\n        Element target = doc.selectFirst(\"target\");\n        assertNotNull(target);\n        assertEquals(parser.getMaxDepth(), depth(target));\n    }\n\n    private static String deepXml(int depth) {\n        StringBuilder xml = new StringBuilder(\"<root>\");\n        for (int i = 0; i < depth; i++) {\n            xml.append(\"<n>\");\n        }\n        xml.append(\"<target />\");\n        for (int i = 0; i < depth; i++) {\n            xml.append(\"</n>\");\n        }\n        xml.append(\"</root>\");\n        return xml.toString();\n    }\n\n    private static int depth(Element el) {\n        int d = 0;\n        while ((el = el.parent()) != null) {\n            d++;\n        }\n        return d;\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/safety/CleanerTest.java",
    "content": "package org.jsoup.safety;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.MultiLocaleExtension.MultiLocaleTest;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Entities;\nimport org.jsoup.nodes.Range;\nimport org.jsoup.parser.ParseSettings;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.Tag;\nimport org.jsoup.parser.TagSet;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.Arrays;\nimport java.util.Locale;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n Tests for the cleaner.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class CleanerTest {\n    @Test public void simpleBehaviourTest() {\n        String h = \"<div><p class=foo><a href='http://evil.com'>Hello <b id=bar>there</b>!</a></div>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.simpleText());\n\n        assertEquals(\"Hello <b>there</b>!\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void simpleBehaviourTest2() {\n        String h = \"Hello <b>there</b>!\";\n        String cleanHtml = Jsoup.clean(h, Safelist.simpleText());\n\n        assertEquals(\"Hello <b>there</b>!\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void basicBehaviourTest() {\n        String h = \"<div><p><a href='javascript:sendAllMoney()'>Dodgy</a> <A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basic());\n\n        assertEquals(\"<p><a rel=\\\"nofollow\\\">Dodgy</a> <a href=\\\"http://nice.com\\\" rel=\\\"nofollow\\\">Nice</a></p><blockquote>Hello</blockquote>\",\n                TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void basicWithImagesTest() {\n        String h = \"<div><p><img src='http://example.com/' alt=Image></p><p><img src='ftp://ftp.example.com'></p></div>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basicWithImages());\n        assertEquals(\"<p><img src=\\\"http://example.com/\\\" alt=\\\"Image\\\"></p><p><img></p>\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void testRelaxed() {\n        String h = \"<h1>Head</h1><table><tr><td>One<td>Two</td></tr></table>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<h1>Head</h1><table><tbody><tr><td>One</td><td>Two</td></tr></tbody></table>\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void testRemoveTags() {\n        String h = \"<div><p><A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basic().removeTags(\"a\"));\n\n        assertEquals(\"<p>Nice</p><blockquote>Hello</blockquote>\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void testRemoveAttributes() {\n        String h = \"<div><p>Nice</p><blockquote cite='http://example.com/quotations'>Hello</blockquote>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basic().removeAttributes(\"blockquote\", \"cite\"));\n\n        assertEquals(\"<p>Nice</p><blockquote>Hello</blockquote>\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test void allAttributes() {\n        String h = \"<div class=foo data=true><p class=bar>Text</p></div><blockquote cite='https://example.com'>Foo\";\n        Safelist safelist = Safelist.relaxed();\n        safelist.addAttributes(\":all\", \"class\");\n        safelist.addAttributes(\"div\", \"data\");\n\n        String clean1 = Jsoup.clean(h, safelist);\n        assertEquals(\"<div class=\\\"foo\\\" data=\\\"true\\\"><p class=\\\"bar\\\">Text</p></div><blockquote cite=\\\"https://example.com\\\">Foo</blockquote>\", TextUtil.stripNewlines(clean1));\n\n        safelist.removeAttributes(\":all\", \"class\", \"cite\");\n\n        String clean2 = Jsoup.clean(h, safelist);\n        assertEquals(\"<div data=\\\"true\\\"><p>Text</p></div><blockquote>Foo</blockquote>\", TextUtil.stripNewlines(clean2));\n    }\n\n    @Test void removeProtocols() {\n        String h = \"<a href='any://example.com'>Link</a>\";\n        Safelist safelist = Safelist.relaxed();\n        String clean1 = Jsoup.clean(h, safelist);\n        assertEquals(\"<a>Link</a>\", clean1);\n\n        safelist.removeProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\", \"mailto\");\n        String clean2 = Jsoup.clean(h, safelist); // all removed means any will work\n        assertEquals(\"<a href=\\\"any://example.com\\\">Link</a>\", clean2);\n    }\n\n    @Test public void testRemoveEnforcedAttributes() {\n        String h = \"<div><p><A HREF='HTTP://nice.com'>Nice</a></p><blockquote>Hello</blockquote>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basic().removeEnforcedAttribute(\"a\", \"rel\"));\n\n        assertEquals(\"<p><a href=\\\"http://nice.com\\\">Nice</a></p><blockquote>Hello</blockquote>\",\n                TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void testRemoveProtocols() {\n        String h = \"<p>Contact me <a href='mailto:info@example.com'>here</a></p>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basic().removeProtocols(\"a\", \"href\", \"ftp\", \"mailto\"));\n\n        assertEquals(\"<p>Contact me <a rel=\\\"nofollow\\\">here</a></p>\",\n                TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @MultiLocaleTest\n    public void safeListedProtocolShouldBeRetained(Locale locale) {\n        Locale.setDefault(locale);\n\n        Safelist safelist = Safelist.none()\n                .addTags(\"a\")\n                .addAttributes(\"a\", \"href\")\n                .addProtocols(\"a\", \"href\", \"something\");\n\n        String cleanHtml = Jsoup.clean(\"<a href=\\\"SOMETHING://x\\\"></a>\", safelist);\n\n        assertEquals(\"<a href=\\\"SOMETHING://x\\\"></a>\", TextUtil.stripNewlines(cleanHtml));\n    }\n\n    @Test public void testDropComments() {\n        String h = \"<p>Hello<!-- no --></p>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<p>Hello</p>\", cleanHtml);\n    }\n\n    @Test public void testDropXmlProc() {\n        String h = \"<?import namespace=\\\"xss\\\"><p>Hello</p>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<p>Hello</p>\", cleanHtml);\n    }\n\n    @Test public void testDropScript() {\n        String h = \"<SCRIPT SRC=//ha.ckers.org/.j><SCRIPT>alert(/XSS/.source)</SCRIPT>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"\", cleanHtml);\n    }\n\n    @Test public void testDropImageScript() {\n        String h = \"<IMG SRC=\\\"javascript:alert('XSS')\\\">\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<img>\", cleanHtml);\n    }\n\n    @Test public void testCleanJavascriptHref() {\n        String h = \"<A HREF=\\\"javascript:document.location='http://www.google.com/'\\\">XSS</A>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<a>XSS</a>\", cleanHtml);\n    }\n\n    @Test public void testCleanAnchorProtocol() {\n        String validAnchor = \"<a href=\\\"#valid\\\">Valid anchor</a>\";\n        String invalidAnchor = \"<a href=\\\"#anchor with spaces\\\">Invalid anchor</a>\";\n\n        // A Safelist that does not allow anchors will strip them out.\n        String cleanHtml = Jsoup.clean(validAnchor, Safelist.relaxed());\n        assertEquals(\"<a>Valid anchor</a>\", cleanHtml);\n\n        cleanHtml = Jsoup.clean(invalidAnchor, Safelist.relaxed());\n        assertEquals(\"<a>Invalid anchor</a>\", cleanHtml);\n\n        // A Safelist that allows them will keep them.\n        Safelist relaxedWithAnchor = Safelist.relaxed().addProtocols(\"a\", \"href\", \"#\");\n\n        cleanHtml = Jsoup.clean(validAnchor, relaxedWithAnchor);\n        assertEquals(validAnchor, cleanHtml);\n\n        // An invalid anchor is never valid.\n        cleanHtml = Jsoup.clean(invalidAnchor, relaxedWithAnchor);\n        assertEquals(\"<a>Invalid anchor</a>\", cleanHtml);\n    }\n\n    @Test public void testDropsUnknownTags() {\n        String h = \"<p><custom foo=true>Test</custom></p>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.relaxed());\n        assertEquals(\"<p>Test</p>\", cleanHtml);\n    }\n\n    @Test public void testHandlesEmptyAttributes() {\n        String h = \"<img alt=\\\"\\\" src= unknown=''>\";\n        String cleanHtml = Jsoup.clean(h, Safelist.basicWithImages());\n        assertEquals(\"<img alt=\\\"\\\">\", cleanHtml);\n    }\n\n    @Test public void testIsValidBodyHtml() {\n        String ok = \"<p>Test <b><a href='http://example.com/' rel='nofollow'>OK</a></b></p>\";\n        String ok1 = \"<p>Test <b><a href='http://example.com/'>OK</a></b></p>\"; // missing enforced is OK because still needs run thru cleaner\n        String nok1 = \"<p><script></script>Not <b>OK</b></p>\";\n        String nok2 = \"<p align=right>Test Not <b>OK</b></p>\";\n        String nok3 = \"<!-- comment --><p>Not OK</p>\"; // comments and the like will be cleaned\n        String nok4 = \"<html><head>Foo</head><body><b>OK</b></body></html>\"; // not body html\n        String nok5 = \"<p>Test <b><a href='http://example.com/' rel='nofollowme'>OK</a></b></p>\";\n        String nok6 = \"<p>Test <b><a href='http://example.com/'>OK</b></p>\"; // missing close tag\n        String nok7 = \"</div>What\";\n        assertTrue(Jsoup.isValid(ok, Safelist.basic()));\n        assertTrue(Jsoup.isValid(ok1, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok1, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok2, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok3, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok4, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok5, Safelist.basic()));\n        assertFalse(Jsoup.isValid(nok6, Safelist.basic()));\n        assertFalse(Jsoup.isValid(ok, Safelist.none()));\n        assertFalse(Jsoup.isValid(nok7, Safelist.basic()));\n    }\n\n    @Test public void testIsValidDocument() {\n        String ok = \"<html><head></head><body><p>Hello</p></body><html>\";\n        String nok = \"<html><head><script>woops</script><title>Hello</title></head><body><p>Hello</p></body><html>\";\n\n        Safelist relaxed = Safelist.relaxed();\n        Cleaner cleaner = new Cleaner(relaxed);\n        Document okDoc = Jsoup.parse(ok);\n        assertTrue(cleaner.isValid(okDoc));\n        assertFalse(cleaner.isValid(Jsoup.parse(nok)));\n        assertFalse(new Cleaner(Safelist.none()).isValid(okDoc));\n    }\n\n    @Test void configuredCleanerMayBeSharedAcrossThreads() throws InterruptedException {\n        // https://github.com/jhy/jsoup/issues/2473\n        String html = \"<a href='/foo'>Link</a><img src='/bar' alt='Q'>\";\n        String baseUri = \"https://example.com/\";\n        String expected = \"<a href=\\\"https://example.com/foo\\\">Link</a><img src=\\\"https://example.com/bar\\\" alt=\\\"Q\\\">\";\n        Cleaner cleaner = new Cleaner(Safelist.basicWithImages());\n\n        int numThreads = 10;\n        int numLoops = 20;\n        String[] cleaned = new String[numThreads * numLoops];\n        AtomicInteger next = new AtomicInteger();\n        AtomicReference<Throwable> failure = new AtomicReference<>();\n        CountDownLatch start = new CountDownLatch(1);\n        CountDownLatch done = new CountDownLatch(numThreads);\n        Thread[] threads = new Thread[numThreads];\n\n        for (int i = 0; i < numThreads; i++) {\n            Thread thread = new Thread(() -> {\n                try {\n                    start.await();\n                    for (int j = 0; j < numLoops; j++) {\n                        Document dirty = Jsoup.parseBodyFragment(html, baseUri);\n                        cleaned[next.getAndIncrement()] = cleaner.clean(dirty).body().html();\n                    }\n                } catch (Throwable t) {\n                    failure.compareAndSet(null, t);\n                    if (t instanceof InterruptedException) Thread.currentThread().interrupt();\n                } finally {\n                    done.countDown();\n                }\n            });\n            threads[i] = thread;\n            thread.start();\n        }\n\n        start.countDown();\n        done.await();\n\n        if (failure.get() != null)\n            throw new AssertionError(\"Concurrent cleaner use failed\", failure.get());\n\n        assertEquals(cleaned.length, next.get());\n        for (String clean : cleaned) {\n            assertEquals(expected, clean);\n        }\n    }\n\n    @Test public void resolvesRelativeLinks() {\n        String html = \"<a href='/foo'>Link</a><img src='/bar'>\";\n        String clean = Jsoup.clean(html, \"http://example.com/\", Safelist.basicWithImages());\n        assertEquals(\"<a href=\\\"http://example.com/foo\\\">Link</a><img src=\\\"http://example.com/bar\\\">\", clean);\n    }\n\n    @Test void cleanDoesNotModifyInputDocumentWhenResolvingRelativeLinks() {\n        String html = \"<a href='/foo'>Link</a>\";\n        Cleaner cleaner = new Cleaner(Safelist.basic());\n        Document dirty = Jsoup.parseBodyFragment(html, \"http://example.com/\");\n        Document clean = cleaner.clean(dirty);\n\n        assertEquals(\"<a href=\\\"http://example.com/foo\\\">Link</a>\", clean.body().html());\n        assertEquals(\"/foo\", dirty.expectFirst(\"a\").attr(\"href\"));\n    }\n\n    @Test void isValidDoesNotModifyInputDocumentWhenResolvingRelativeLinks() {\n        String html = \"<a href='/foo'>Link</a>\";\n        Cleaner cleaner = new Cleaner(Safelist.basic());\n        Document dirty = Jsoup.parseBodyFragment(html, \"http://example.com/\");\n\n        assertTrue(cleaner.isValid(dirty));\n        assertEquals(\"<a href=\\\"http://example.com/foo\\\">Link</a>\",\n            cleaner.clean(Jsoup.parseBodyFragment(html, \"http://example.com/\")).body().html());\n        assertEquals(\"/foo\", dirty.expectFirst(\"a\").attr(\"href\"));\n    }\n\n    @Test void allPseudoTagProtocolsNormalizeRelativeLinks() {\n        Safelist safelist = new Safelist()\n            .addTags(\"a\")\n            .addAttributes(\":all\", \"href\")\n            .addProtocols(\":all\", \"href\", \"http\", \"https\");\n        Document dirty = Jsoup.parseBodyFragment(\"<a href='/foo'>Link</a>\", \"http://example.com/\");\n\n        assertEquals(\"<a href=\\\"http://example.com/foo\\\">Link</a>\", new Cleaner(safelist).clean(dirty).body().html());\n    }\n\n    @Test void tagSpecificAttributesDoNotInheritAllPseudoTagProtocols() {\n        Safelist safelist = new Safelist()\n            .addTags(\"a\")\n            .addAttributes(\"a\", \"href\")\n            .addAttributes(\":all\", \"href\")\n            .addProtocols(\":all\", \"href\", \"http\", \"https\");\n        Document dirty = Jsoup.parseBodyFragment(\"<a href='/foo'>Link</a>\", \"http://example.com/\");\n\n        assertEquals(\"<a href=\\\"/foo\\\">Link</a>\", new Cleaner(safelist).clean(dirty).body().html());\n    }\n\n    @Test public void preservesRelativeLinksIfConfigured() {\n        String html = \"<a href='/foo'>Link</a><img src='/bar'> <img src='javascript:alert()'>\";\n        String clean = Jsoup.clean(html, \"http://example.com/\", Safelist.basicWithImages().preserveRelativeLinks(true));\n        assertEquals(\"<a href=\\\"/foo\\\">Link</a><img src=\\\"/bar\\\"> <img>\", clean);\n    }\n\n    @Test public void dropsUnresolvableRelativeLinks() { // when not preserving\n        String html = \"<a href='/foo'>Link</a>\";\n        String clean = Jsoup.clean(html, Safelist.basic());\n        assertEquals(\"<a rel=\\\"nofollow\\\">Link</a>\", clean);\n    }\n\n    @Test void dropsJavascriptWhenRelativeLinks() {\n        String html =\"<a href='javascript:alert()'>One</a>\";\n        Safelist safelist = Safelist.basic().preserveRelativeLinks(true);\n        assertEquals(\"<a rel=\\\"nofollow\\\">One</a>\", Jsoup.clean(html, safelist));\n        assertFalse(Jsoup.isValid(html, safelist));\n    }\n\n    @Test void dropsConcealedJavascriptProtocolWhenRelativesLinksEnabled() {\n        Safelist safelist = Safelist.basic().preserveRelativeLinks(true);\n        String html = \"<a href=\\\"&#0013;ja&Tab;va&Tab;script&#0010;:alert(1)\\\">Link</a>\";\n        String clean = Jsoup.clean(html, \"https://\", safelist);\n        assertEquals(\"<a rel=\\\"nofollow\\\">Link</a>\", clean);\n        assertFalse(Jsoup.isValid(html, safelist));\n\n        String colon = \"<a href=\\\"ja&Tab;va&Tab;script&colon;alert(1)\\\">Link</a>\";\n        String cleanColon = Jsoup.clean(colon, \"https://\", safelist);\n        assertEquals(\"<a rel=\\\"nofollow\\\">Link</a>\", cleanColon);\n        assertFalse(Jsoup.isValid(colon, safelist));\n    }\n\n    @Test void dropsConcealedJavascriptProtocolWhenRelativesLinksDisabled() {\n        Safelist safelist = Safelist.basic().preserveRelativeLinks(false);\n        String html = \"<a href=\\\"ja&Tab;vas&#0013;cript:alert(1)\\\">Link</a>\";\n        String clean = Jsoup.clean(html, \"https://\", safelist);\n        assertEquals(\"<a rel=\\\"nofollow\\\">Link</a>\", clean);\n        assertFalse(Jsoup.isValid(html, safelist));\n    }\n\n    @Test public void handlesCustomProtocols() {\n        String html = \"<img src='cid:12345' /> <img src='data:gzzt' />\";\n        String dropped = Jsoup.clean(html, Safelist.basicWithImages());\n        assertEquals(\"<img> <img>\", dropped);\n\n        String preserved = Jsoup.clean(html, Safelist.basicWithImages().addProtocols(\"img\", \"src\", \"cid\", \"data\"));\n        assertEquals(\"<img src=\\\"cid:12345\\\"> <img src=\\\"data:gzzt\\\">\", preserved);\n    }\n\n    @Test public void handlesAllPseudoTag() {\n        String html = \"<p class='foo' src='bar'><a class='qux'>link</a></p>\";\n        Safelist safelist = new Safelist()\n                .addAttributes(\":all\", \"class\")\n                .addAttributes(\"p\", \"style\")\n                .addTags(\"p\", \"a\");\n\n        String clean = Jsoup.clean(html, safelist);\n        assertEquals(\"<p class=\\\"foo\\\"><a class=\\\"qux\\\">link</a></p>\", clean);\n    }\n\n    @Test public void addsTagOnAttributesIfNotSet() {\n        String html = \"<p class='foo' src='bar'>One</p>\";\n        Safelist safelist = new Safelist()\n            .addAttributes(\"p\", \"class\");\n        // ^^ safelist does not have explicit tag add for p, inferred from add attributes.\n        String clean = Jsoup.clean(html, safelist);\n        assertEquals(\"<p class=\\\"foo\\\">One</p>\", clean);\n    }\n\n    @Test public void supplyOutputSettings() {\n        // test that one can override the default document output settings\n        Document.OutputSettings os = new Document.OutputSettings();\n        os.prettyPrint(false);\n        os.escapeMode(Entities.EscapeMode.extended);\n        os.charset(\"ascii\");\n\n        String html = \"<div><p>&bernou;</p></div>\";\n        String customOut = Jsoup.clean(html, \"http://foo.com/\", Safelist.relaxed(), os);\n        String defaultOut = Jsoup.clean(html, \"http://foo.com/\", Safelist.relaxed());\n        assertNotSame(defaultOut, customOut);\n\n        assertEquals(\"<div><p>&Bscr;</p></div>\", customOut); // entities now prefers shorted names if aliased\n        assertEquals(\"<div>\\n\" +\n            \" <p>ℬ</p>\\n\" +\n            \"</div>\", defaultOut);\n\n        os.charset(\"ASCII\");\n        os.escapeMode(Entities.EscapeMode.base);\n        String customOut2 = Jsoup.clean(html, \"http://foo.com/\", Safelist.relaxed(), os);\n        assertEquals(\"<div><p>&#x212c;</p></div>\", customOut2);\n    }\n\n    @Test public void handlesFramesets() {\n        String dirty = \"<html><head><script></script><noscript></noscript></head><frameset><frame src=\\\"foo\\\" /><frame src=\\\"foo\\\" /></frameset></html>\";\n        String clean = Jsoup.clean(dirty, Safelist.basic());\n        assertEquals(\"\", clean); // nothing good can come out of that\n\n        Document dirtyDoc = Jsoup.parse(dirty);\n        Document cleanDoc = new Cleaner(Safelist.basic()).clean(dirtyDoc);\n        assertNotNull(cleanDoc);\n        assertEquals(0, cleanDoc.body().childNodeSize());\n    }\n\n    @Test public void cleansInternationalText() {\n        assertEquals(\"привет\", Jsoup.clean(\"привет\", Safelist.none()));\n    }\n\n    @Test\n    public void testScriptTagInSafeList() {\n        Safelist safelist = Safelist.relaxed();\n        safelist.addTags( \"script\" );\n        assertTrue( Jsoup.isValid(\"Hello<script>alert('Doh')</script>World !\", safelist) );\n    }\n\n    @Test\n    public void bailsIfRemovingProtocolThatsNotSet() {\n        assertThrows(IllegalArgumentException.class, () -> {\n            // a case that came up on the email list\n            Safelist w = Safelist.none();\n\n            // note no add tag, and removing protocol without adding first\n            w.addAttributes(\"a\", \"href\");\n            w.removeProtocols(\"a\", \"href\", \"javascript\"); // with no protocols enforced, this was a noop. Now validates.\n        });\n    }\n\n    @Test public void handlesControlCharactersAfterTagName() {\n        String html = \"<a/\\06>\";\n        String clean = Jsoup.clean(html, Safelist.basic());\n        assertEquals(\"<a rel=\\\"nofollow\\\"></a>\", clean);\n    }\n\n    @Test public void handlesAttributesWithNoValue() {\n        // https://github.com/jhy/jsoup/issues/973\n        String clean = Jsoup.clean(\"<a href>Clean</a>\", Safelist.basic());\n\n        assertEquals(\"<a rel=\\\"nofollow\\\">Clean</a>\", clean);\n    }\n\n    @Test public void handlesNoHrefAttribute() {\n        String dirty = \"<a>One</a> <a href>Two</a>\";\n        Safelist relaxedWithAnchor = Safelist.relaxed().addProtocols(\"a\", \"href\", \"#\");\n        String clean = Jsoup.clean(dirty, relaxedWithAnchor);\n        assertEquals(\"<a>One</a> <a>Two</a>\", clean);\n    }\n\n    @Test void cleanerPreservesCaseVariantOfMatchingEnforcedAttribute() {\n        Document dirty = Jsoup.parse(\"<a href='http://example.com/' REL='nofollow'>Link</a>\", \"\",\n            Parser.htmlParser().settings(ParseSettings.preserveCase));\n        Cleaner cleaner = new Cleaner(Safelist.basic());\n\n        Document clean = cleaner.clean(dirty);\n        Element link = clean.expectFirst(\"a\");\n        assertTrue(link.hasAttr(\"REL\"));\n        assertEquals(\"nofollow\", link.attr(\"REL\"));\n        assertTrue(cleaner.isValid(dirty));\n    }\n\n    @Test public void handlesNestedQuotesInAttribute() {\n        // https://github.com/jhy/jsoup/issues/1243 - no repro\n        String orig = \"<div style=\\\"font-family: 'Calibri'\\\">Will (not) fail</div>\";\n        Safelist allow = Safelist.relaxed()\n            .addAttributes(\"div\", \"style\");\n\n        String clean = Jsoup.clean(orig, allow);\n        boolean isValid = Jsoup.isValid(orig, allow);\n\n        assertEquals(orig, TextUtil.stripNewlines(clean)); // only difference is pretty print wrap & indent\n        assertTrue(isValid);\n    }\n\n    @Test public void copiesOutputSettings() {\n        Document orig = Jsoup.parse(\"<p>test<br></p>\");\n        orig.outputSettings().syntax(Document.OutputSettings.Syntax.xml);\n        orig.outputSettings().escapeMode(Entities.EscapeMode.xhtml);\n        Safelist safelist = Safelist.none().addTags(\"p\", \"br\");\n\n        Document result = new Cleaner(safelist).clean(orig);\n        assertEquals(Document.OutputSettings.Syntax.xml, result.outputSettings().syntax());\n        assertEquals(\"<p>test\\n <br /></p>\", result.body().html());\n    }\n\n    @Test void preservesSourcePositionViaUserData() {\n        Document orig = Jsoup.parse(\"<script>xss</script>\\n <p id=1>Hello</p>\", Parser.htmlParser().setTrackPosition(true));\n        Element p = orig.expectFirst(\"p\");\n        Range origRange = p.sourceRange();\n        assertEquals(\"2,2:22-2,10:30\", origRange.toString());\n        assertEquals(\"1,1:0-1,1:0\", orig.sourceRange().toString());\n        assertEquals(\"2,19:39-2,19:39\", orig.endSourceRange().toString());\n\n        Range.AttributeRange attributeRange = p.attributes().sourceRange(\"id\");\n        assertEquals(\"2,5:25-2,7:27=2,8:28-2,9:29\", attributeRange.toString());\n\n        Document clean = new Cleaner(Safelist.relaxed().addAttributes(\"p\", \"id\")).clean(orig);\n        Element cleanP = clean.expectFirst(\"p\");\n        assertEquals(\"1\", cleanP.id());\n        Range cleanRange = cleanP.sourceRange();\n        assertEquals(origRange, cleanRange);\n        assertEquals(p.endSourceRange(), cleanP.endSourceRange());\n        assertEquals(attributeRange, cleanP.attributes().sourceRange(\"id\"));\n    }\n\n    @ParameterizedTest @ValueSource(booleans = {true, false})\n    void cleansCaseSensitiveElements(boolean preserveCase) {\n        // https://github.com/jhy/jsoup/issues/2049\n        String html = \"<svg><feMerge baseFrequency=2><feMergeNode kernelMatrix=1 /><feMergeNode><clipPath /></feMergeNode><feMergeNode />\";\n        String[] tags = {\"svg\", \"feMerge\", \"feMergeNode\", \"clipPath\"};\n        String[] attrs = {\"kernelMatrix\", \"baseFrequency\"};\n\n        if (!preserveCase) {\n            tags = Arrays.stream(tags).map(String::toLowerCase).toArray(String[]::new);\n            attrs = Arrays.stream(attrs).map(String::toLowerCase).toArray(String[]::new);\n        }\n\n        Safelist safelist = Safelist.none().addTags(tags).addAttributes(\":all\", attrs);\n        String clean = Jsoup.clean(html, safelist);\n        String expected = \"<svg>\\n\" +\n            \" <feMerge baseFrequency=\\\"2\\\">\\n\" +\n            \"  <feMergeNode kernelMatrix=\\\"1\\\" />\\n\" +\n            \"  <feMergeNode>\\n\" +\n            \"   <clipPath />\\n\" +\n            \"  </feMergeNode>\\n\" +\n            \"  <feMergeNode />\\n\" +\n            \" </feMerge>\\n\" +\n            \"</svg>\";\n        assertEquals(expected, clean);\n    }\n\n    @Test void nofollowOnlyOnExternalLinks() {\n        // We want to add nofollow to external links, but not to for relative links or those on the same site\n        String html = \"<a href='http://external.com/'>One</a> <a href='/relative/'>Two</a> <a href='../other/'>Three</a> <a href='http://example.com/bar'>Four</a>\";\n\n        Safelist basic = Safelist.basic().preserveRelativeLinks(true);\n        String clean = Jsoup.clean(html, \"http://example.com/\", basic);\n        assertEquals(\"<a href=\\\"http://external.com/\\\" rel=\\\"nofollow\\\">One</a> <a href=\\\"/relative/\\\">Two</a> <a href=\\\"../other/\\\">Three</a> <a href=\\\"http://example.com/bar\\\">Four</a>\", clean);\n\n        // If we don't pass in a base URI, still want to preserve the relative links.\n        String clean2 = Jsoup.clean(html, basic);\n        assertEquals(\"<a href=\\\"http://external.com/\\\" rel=\\\"nofollow\\\">One</a> <a href=\\\"/relative/\\\">Two</a> <a href=\\\"../other/\\\">Three</a> <a href=\\\"http://example.com/bar\\\" rel=\\\"nofollow\\\">Four</a>\", clean2);\n        // Four gets nofollowed because we didn't specify the base URI, so must assume it is external\n\n        // Want it to be valid with relative links (and no base uri required / provided):\n        assertTrue(Jsoup.isValid(html, basic));\n\n        // test that it works in safelist.relaxed as well, which doesn't by default have rel=nofollow\n        Safelist relaxed = Safelist.relaxed().preserveRelativeLinks(true).addEnforcedAttribute(\"a\", \"rel\", \"nofollow\");\n        String clean3 = Jsoup.clean(html, \"http://example.com/\", relaxed);\n        assertEquals(\"<a href=\\\"http://external.com/\\\" rel=\\\"nofollow\\\">One</a> <a href=\\\"/relative/\\\">Two</a> <a href=\\\"../other/\\\">Three</a> <a href=\\\"http://example.com/bar\\\">Four</a>\", clean3);\n        assertTrue(Jsoup.isValid(html, relaxed));\n\n        String clean4 = Jsoup.clean(html, relaxed);\n        assertEquals(\"<a href=\\\"http://external.com/\\\" rel=\\\"nofollow\\\">One</a> <a href=\\\"/relative/\\\">Two</a> <a href=\\\"../other/\\\">Three</a> <a href=\\\"http://example.com/bar\\\" rel=\\\"nofollow\\\">Four</a>\", clean4);\n    }\n\n    @Test void canonicalizesEnforcedAttributes() {\n        Document customDirty = Jsoup.parse(\"<a REL='external'>One</a>\", \"\",\n            Parser.htmlParser().settings(ParseSettings.preserveCase));\n        Cleaner customCleaner = new Cleaner(Safelist.none()\n            .addTags(\"a\")\n            .addEnforcedAttribute(\"a\", \"rel\", \"external\"));\n        assertEquals(\"<a rel=\\\"external\\\">One</a>\", customCleaner.clean(customDirty).body().html());\n    }\n\n    @Test void canonicalizesNofollowEnforcedAttribute() {\n        Document dirty = Jsoup.parse(\"<a href='http://external.com/' REL='nofollow'>One</a>\", \"\",\n            Parser.htmlParser().settings(ParseSettings.preserveCase));\n        Cleaner cleaner = new Cleaner(Safelist.basic());\n        assertEquals(\"<a href=\\\"http://external.com/\\\" rel=\\\"nofollow\\\">One</a>\", cleaner.clean(dirty).body().html());\n    }\n\n    @Test void preservesMatchingSourceNofollowWhenEnforcementSuppressed() {\n        Document dirty = Jsoup.parse(\"<a href='http://example.com/foo' REL='nofollow'>One</a>\", \"http://example.com/\",\n            Parser.htmlParser().settings(ParseSettings.preserveCase));\n        Cleaner cleaner = new Cleaner(Safelist.basic());\n        assertEquals(\"<a href=\\\"http://example.com/foo\\\" REL=\\\"nofollow\\\">One</a>\", cleaner.clean(dirty).body().html());\n    }\n\n    @Test void discardsSvgScriptData() {\n        // https://github.com/jhy/jsoup/issues/2320\n        Safelist svgOk = Safelist.none().addTags(\"svg\");\n        String cleaned = Jsoup.clean(\"<svg><script> a < b </script></svg>\", svgOk);\n        assertEquals(\"<svg></svg>\", cleaned);\n    }\n\n    @Test void canSupplyConfiguredTagset() {\n        // https://github.com/jhy/jsoup/issues/2326\n\n        // by default, iframe is data\n        String input = \"<iframe>content is <data></iframe>\";\n        Safelist safelist = Safelist.relaxed().addTags(\"iframe\");\n        String clean = Jsoup.clean(input, safelist);\n        assertEquals(\"<iframe>content is <data></iframe>\", clean);\n\n        Document doc = Jsoup.parse(input);\n        assertEquals(\"\", doc.text()); // data is not text\n\n        // can change to text\n        TagSet tags = TagSet.Html();\n        Tag iframe = tags.valueOf(\"iframe\", Parser.NamespaceHtml);\n        iframe.clear(Tag.Data).set(Tag.RcData);\n        Document doc2 = Jsoup.parse(input, Parser.htmlParser().tagSet(tags));\n        assertEquals(\"content is <data>\", doc2.text());\n        assertEquals(\"<iframe>content is &lt;data&gt;</iframe>\", doc2.body().html());\n\n        // text nodes are escaped\n        assertEquals(\"<iframe>content is &lt;data&gt;</iframe>\", doc2.body().html());\n\n        // can use cleaner with updated tagset\n        Cleaner cleaner = new Cleaner(Safelist.relaxed());\n        String clean2 = cleaner.clean(doc2).body().html();\n        assertEquals(\"content is &lt;data&gt;\", clean2);\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/safety/SafelistTest.java",
    "content": "package org.jsoup.safety;\n\nimport org.jsoup.helper.ValidationException;\nimport org.jsoup.nodes.Attribute;\nimport org.jsoup.nodes.Attributes;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.parser.Tag;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class SafelistTest {\n    private static final String TEST_TAG = \"testTag\";\n    private static final String TEST_ATTRIBUTE = \"testAttribute\";\n    private static final String TEST_SCHEME = \"valid-scheme\";\n    private static final String TEST_VALUE = TEST_SCHEME + \"://testValue\";\n\n    @Test\n    public void testCopyConstructor_noSideEffectOnTags() {\n        Safelist safelist1 = Safelist.none().addTags(TEST_TAG);\n        Safelist safelist2 = new Safelist(safelist1);\n        safelist1.addTags(\"invalidTag\");\n\n        assertFalse(safelist2.isSafeTag(\"invalidTag\"));\n    }\n\n    @Test\n    public void testCopyConstructor_noSideEffectOnAttributes() {\n        Safelist safelist1 = Safelist.none().addAttributes(TEST_TAG, TEST_ATTRIBUTE);\n        Safelist safelist2 = new Safelist(safelist1);\n        safelist1.addAttributes(TEST_TAG, \"invalidAttribute\");\n\n        assertFalse(safelist2.isSafeAttribute(TEST_TAG, null, new Attribute(\"invalidAttribute\", TEST_VALUE)));\n    }\n\n    @Test\n    public void testCopyConstructor_noSideEffectOnEnforcedAttributes() {\n        Safelist safelist1 = Safelist.none().addEnforcedAttribute(TEST_TAG, TEST_ATTRIBUTE, TEST_VALUE);\n        Safelist safelist2 = new Safelist(safelist1);\n        safelist1.addEnforcedAttribute(TEST_TAG, TEST_ATTRIBUTE, \"invalidValue\");\n\n        for (Attribute enforcedAttribute : safelist2.getEnforcedAttributes(TEST_TAG)) {\n            assertNotEquals(\"invalidValue\", enforcedAttribute.getValue());\n        }\n    }\n\n    @Test\n    public void testCopyConstructor_noSideEffectOnProtocols() {\n        final String invalidScheme = \"invalid-scheme\";\n        Safelist safelist1 = Safelist.none()\n                .addAttributes(TEST_TAG, TEST_ATTRIBUTE)\n                .addProtocols(TEST_TAG, TEST_ATTRIBUTE, TEST_SCHEME);\n        Safelist safelist2 = new Safelist(safelist1);\n        safelist1.addProtocols(TEST_TAG, TEST_ATTRIBUTE, invalidScheme);\n\n        Attributes attributes = new Attributes();\n        Attribute invalidAttribute = new Attribute(TEST_ATTRIBUTE, invalidScheme + \"://someValue\");\n        attributes.put(invalidAttribute);\n        Element invalidElement = new Element(Tag.valueOf(TEST_TAG), \"\", attributes);\n\n        assertFalse(safelist2.isSafeAttribute(TEST_TAG, invalidElement, invalidAttribute));\n    }\n\n    @Test\n    void isSafeAttributeDoesNotModifyLiveAttribute() {\n        Attributes attributes = new Attributes().put(\"href\", \"/foo\");\n        Element link = new Element(Tag.valueOf(\"a\"), \"https://example.com/\", attributes);\n        Attribute href = link.attributes().attribute(\"href\");\n        assertNotNull(href);\n\n        assertTrue(Safelist.basic().isSafeAttribute(\"a\", link, href));\n        assertEquals(\"/foo\", href.getValue());\n        assertEquals(\"/foo\", link.attr(\"href\"));\n    }\n\n    @Test\n    void enforcedAttributeMatchesInputKeyCaseInsensitively() {\n        Attribute rel = new Attribute(\"REL\", \"nofollow\");\n        Attributes attributes = new Attributes().put(rel);\n        Element link = new Element(Tag.valueOf(\"a\"), \"\", attributes);\n\n        assertTrue(Safelist.basic().isSafeAttribute(\"a\", link, rel));\n    }\n\n    @Test\n    void noscriptIsBlocked() {\n        boolean threw = false;\n        Safelist safelist = null;\n        try {\n            safelist = Safelist.none().addTags(\"NOSCRIPT\");\n        } catch (ValidationException validationException) {\n            threw = true;\n            assertTrue(validationException.getMessage().contains(\"unsupported\"));\n        }\n        assertTrue(threw);\n        assertNull(safelist);\n    }\n\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/CssTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Tag;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\n\npublic class CssTest {\n\n\tprivate Document html = null;\n\tprivate static String htmlString;\n\n\t@BeforeAll\n\tpublic static void initClass() {\n\t\tStringBuilder sb = new StringBuilder(\"<html><head></head><body>\");\n\n\t\tsb.append(\"<div id='pseudo'>\");\n\t\tfor (int i = 1; i <= 10; i++) {\n\t\t\tsb.append(String.format(\"<p>%d</p>\",i));\n\t\t}\n\t\tsb.append(\"</div>\");\n\n\t\tsb.append(\"<div id='type'>\");\n\t\tfor (int i = 1; i <= 10; i++) {\n\t\t\tsb.append(String.format(\"<p>%d</p>\",i));\n\t\t\tsb.append(String.format(\"<span>%d</span>\",i));\n\t\t\tsb.append(String.format(\"<em>%d</em>\",i));\n            sb.append(String.format(\"<svg>%d</svg>\",i));\n\t\t}\n\t\tsb.append(\"</div>\");\n\n\t\tsb.append(\"<span id='onlySpan'><br /></span>\");\n\t\tsb.append(\"<p class='empty'><!-- Comment only is still empty! --></p>\");\n\n\t\tsb.append(\"<div id='only'>\");\n\t\tsb.append(\"Some text before the <em>only</em> child in this div\");\n\t\tsb.append(\"</div>\");\n\n\t\tsb.append(\"</body></html>\");\n\t\thtmlString = sb.toString();\n\t}\n\n\t@BeforeEach\n\tpublic void init() {\n\t\thtml  = Jsoup.parse(htmlString);\n\t}\n\n\t@Test\n\tpublic void firstChild() {\n\t\tcheck(html.select(\"#pseudo :first-child\"), \"1\");\n\t\tcheck(html.select(\"html:first-child\"));\n\t}\n\n\t@Test\n\tpublic void lastChild() {\n\t\tcheck(html.select(\"#pseudo :last-child\"), \"10\");\n\t\tcheck(html.select(\"html:last-child\"));\n\t}\n\n\t@Test\n\tpublic void nthChild_simple() {\n\t\tfor(int i = 1; i <=10; i++) {\n\t\t\tcheck(html.select(String.format(\"#pseudo :nth-child(%d)\", i)), String.valueOf(i));\n\t\t}\n\t}\n\n    @Test\n    public void nthOfType_unknownTag() {\n        for(int i = 1; i <=10; i++) {\n            check(html.select(String.format(\"#type svg:nth-of-type(%d)\", i)), String.valueOf(i));\n        }\n    }\n\n\t@Test\n\tpublic void nthLastChild_simple() {\n\t\tfor(int i = 1; i <=10; i++) {\n\t\t\tcheck(html.select(String.format(\"#pseudo :nth-last-child(%d)\", i)), String.valueOf(11-i));\n\t\t}\n\t}\n\n\t@Test\n\tpublic void nthOfType_simple() {\n\t\tfor(int i = 1; i <=10; i++) {\n\t\t\tcheck(html.select(String.format(\"#type p:nth-of-type(%d)\", i)), String.valueOf(i));\n\t\t}\n\t}\n\n\t@Test\n\tpublic void nthLastOfType_simple() {\n\t\tfor(int i = 1; i <=10; i++) {\n\t\t\tcheck(html.select(String.format(\"#type :nth-last-of-type(%d)\", i)), String.valueOf(11-i),String.valueOf(11-i),String.valueOf(11-i),String.valueOf(11-i));\n\t\t}\n\t}\n\n\t@Test\n\tpublic void nthChild_advanced() {\n\t\tcheck(html.select(\"#pseudo :nth-child(-5)\"));\n\t\tcheck(html.select(\"#pseudo :nth-child(odd)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-child(2n-1)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-child(2n+1)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-child(2n+3)\"), \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-child(even)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-child(2n)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-child(3n-1)\"), \"2\", \"5\", \"8\");\n\t\tcheck(html.select(\"#pseudo :nth-child(-2n+5)\"), \"1\", \"3\", \"5\");\n\t\tcheck(html.select(\"#pseudo :nth-child(+5)\"), \"5\");\n\t}\n\n\t@Test\n\tpublic void nthOfType_advanced() {\n\t\tcheck(html.select(\"#type :nth-of-type(-5)\"));\n\t\tcheck(html.select(\"#type p:nth-of-type(odd)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type em:nth-of-type(2n-1)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type p:nth-of-type(2n+1)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type span:nth-of-type(2n+3)\"), \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type p:nth-of-type(even)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type p:nth-of-type(2n)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type p:nth-of-type(3n-1)\"), \"2\", \"5\", \"8\");\n\t\tcheck(html.select(\"#type p:nth-of-type(-2n+5)\"), \"1\", \"3\", \"5\");\n\t\tcheck(html.select(\"#type :nth-of-type(+5)\"), \"5\", \"5\", \"5\", \"5\");\n\t}\n\n\n\t@Test\n\tpublic void nthLastChild_advanced() {\n\t\tcheck(html.select(\"#pseudo :nth-last-child(-5)\"));\n\t\tcheck(html.select(\"#pseudo :nth-last-child(odd)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(2n-1)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(2n+1)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(2n+3)\"), \"2\", \"4\", \"6\", \"8\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(even)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(2n)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(3n-1)\"), \"3\", \"6\", \"9\");\n\n\t\tcheck(html.select(\"#pseudo :nth-last-child(-2n+5)\"), \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#pseudo :nth-last-child(+5)\"), \"6\");\n\t}\n\n\t@Test\n\tpublic void nthLastOfType_advanced() {\n\t\tcheck(html.select(\"#type :nth-last-of-type(-5)\"));\n\t\tcheck(html.select(\"#type p:nth-last-of-type(odd)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type em:nth-last-of-type(2n-1)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type p:nth-last-of-type(2n+1)\"), \"2\", \"4\", \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type span:nth-last-of-type(2n+3)\"), \"2\", \"4\", \"6\", \"8\");\n\t\tcheck(html.select(\"#type p:nth-last-of-type(even)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type p:nth-last-of-type(2n)\"), \"1\", \"3\", \"5\", \"7\", \"9\");\n\t\tcheck(html.select(\"#type p:nth-last-of-type(3n-1)\"), \"3\", \"6\", \"9\");\n\n\t\tcheck(html.select(\"#type span:nth-last-of-type(-2n+5)\"), \"6\", \"8\", \"10\");\n\t\tcheck(html.select(\"#type :nth-last-of-type(+5)\"), \"6\", \"6\", \"6\", \"6\");\n\t}\n\n\t@Test\n\tpublic void firstOfType() {\n\t\tcheck(html.select(\"div:not(#only) :first-of-type\"), \"1\", \"1\", \"1\", \"1\", \"1\");\n\t}\n\n\t@Test\n\tpublic void lastOfType() {\n\t\tcheck(html.select(\"div:not(#only) :last-of-type\"), \"10\", \"10\", \"10\", \"10\", \"10\");\n\t}\n\n\t@Test\n\tpublic void empty() {\n\t\tfinal Elements sel = html.select(\":empty\");\n\t\tassertEquals(3, sel.size());\n\t\tassertEquals(\"head\", sel.get(0).tagName());\n\t\tassertEquals(\"br\", sel.get(1).tagName());\n\t\tassertEquals(\"p\", sel.get(2).tagName());\n\t}\n\n\t@Test\n\tpublic void onlyChild() {\n\t\tfinal Elements sel = html.select(\"span :only-child\");\n\t\tassertEquals(1, sel.size());\n\t\tassertEquals(\"br\", sel.get(0).tagName());\n\n\t\tcheck(html.select(\"#only :only-child\"), \"only\");\n\t}\n\n\t@Test\n\tpublic void onlyOfType() {\n\t\tfinal Elements sel = html.select(\":only-of-type\");\n\t\tassertEquals(6, sel.size());\n\t\tassertEquals(\"head\", sel.get(0).tagName());\n\t\tassertEquals(\"body\", sel.get(1).tagName());\n\t\tassertEquals(\"span\", sel.get(2).tagName());\n\t\tassertEquals(\"br\", sel.get(3).tagName());\n\t\tassertEquals(\"p\", sel.get(4).tagName());\n\t\tassertTrue(sel.get(4).hasClass(\"empty\"));\n\t\tassertEquals(\"em\", sel.get(5).tagName());\n\t}\n\n\tprotected void check(Elements result, String...expectedContent ) {\n\t\tassertEquals(expectedContent.length, result.size(), \"Number of elements\");\n\t\tfor (int i = 0; i < expectedContent.length; i++) {\n\t\t\tassertNotNull(result.get(i));\n\t\t\tassertEquals(expectedContent[i], result.get(i).ownText(), \"Expected element\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void root() {\n\t\tElements sel = html.select(\":root\");\n\t\tassertEquals(1, sel.size());\n\t\tassertNotNull(sel.get(0));\n\t\tassertEquals(Tag.valueOf(\"html\"), sel.get(0).tag());\n\n\t\tElements sel2 = html.select(\"body\").select(\":root\");\n\t\tassertEquals(1, sel2.size());\n\t\tassertNotNull(sel2.get(0));\n\t\tassertEquals(Tag.valueOf(\"body\"), sel2.get(0).tag());\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/ElementsTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.DataNode;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.FormElement;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Iterator;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\n/**\n Tests for ElementList.\n\n @author Jonathan Hedley, jonathan@hedley.net */\npublic class ElementsTest {\n    @Test public void filter() {\n        String h = \"<p>Excl</p><div class=headline><p>Hello</p><p>There</p></div><div class=headline><h1>Headline</h1></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\".headline\").select(\"p\");\n        assertEquals(2, els.size());\n        assertEquals(\"Hello\", els.get(0).text());\n        assertEquals(\"There\", els.get(1).text());\n    }\n\n    @Test public void attributes() {\n        String h = \"<p title=foo><p title=bar><p class=foo><p class=bar>\";\n        Document doc = Jsoup.parse(h);\n        Elements withTitle = doc.select(\"p[title]\");\n        assertEquals(2, withTitle.size());\n        assertTrue(withTitle.hasAttr(\"title\"));\n        assertFalse(withTitle.hasAttr(\"class\"));\n        assertEquals(\"foo\", withTitle.attr(\"title\"));\n\n        withTitle.removeAttr(\"title\");\n        assertEquals(2, withTitle.size()); // existing Elements are not reevaluated\n        assertEquals(0, doc.select(\"p[title]\").size());\n\n        Elements ps = doc.select(\"p\").attr(\"style\", \"classy\");\n        assertEquals(4, ps.size());\n        assertEquals(\"classy\", ps.last().attr(\"style\"));\n        assertEquals(\"bar\", ps.last().attr(\"class\"));\n    }\n\n    @Test public void hasAttr() {\n        Document doc = Jsoup.parse(\"<p title=foo><p title=bar><p class=foo><p class=bar>\");\n        Elements ps = doc.select(\"p\");\n        assertTrue(ps.hasAttr(\"class\"));\n        assertFalse(ps.hasAttr(\"style\"));\n    }\n\n    @Test public void hasAbsAttr() {\n        Document doc = Jsoup.parse(\"<a id=1 href='/foo'>One</a> <a id=2 href='https://jsoup.org'>Two</a>\");\n        Elements one = doc.select(\"#1\");\n        Elements two = doc.select(\"#2\");\n        Elements both = doc.select(\"a\");\n        assertFalse(one.hasAttr(\"abs:href\"));\n        assertTrue(two.hasAttr(\"abs:href\"));\n        assertTrue(both.hasAttr(\"abs:href\")); // hits on #2\n    }\n\n    @Test public void attr() {\n        Document doc = Jsoup.parse(\"<p title=foo><p title=bar><p class=foo><p class=bar>\");\n        String classVal = doc.select(\"p\").attr(\"class\");\n        assertEquals(\"foo\", classVal);\n    }\n\n    @Test public void absAttr() {\n        Document doc = Jsoup.parse(\"<a id=1 href='/foo'>One</a> <a id=2 href='https://jsoup.org'>Two</a>\");\n        Elements one = doc.select(\"#1\");\n        Elements two = doc.select(\"#2\");\n        Elements both = doc.select(\"a\");\n\n        assertEquals(\"\", one.attr(\"abs:href\"));\n        assertEquals(\"https://jsoup.org\", two.attr(\"abs:href\"));\n        assertEquals(\"https://jsoup.org\", both.attr(\"abs:href\"));\n    }\n\n    @Test public void classes() {\n        Document doc = Jsoup.parse(\"<div><p class='mellow yellow'></p><p class='red green'></p>\");\n\n        Elements els = doc.select(\"p\");\n        assertTrue(els.hasClass(\"red\"));\n        assertFalse(els.hasClass(\"blue\"));\n        els.addClass(\"blue\");\n        els.removeClass(\"yellow\");\n        els.toggleClass(\"mellow\");\n\n        assertEquals(\"blue\", els.get(0).className());\n        assertEquals(\"red green blue mellow\", els.get(1).className());\n    }\n\n    @Test public void hasClassCaseInsensitive() {\n        Elements els = Jsoup.parse(\"<p Class=One>One <p class=Two>Two <p CLASS=THREE>THREE\").select(\"p\");\n        Element one = els.get(0);\n        Element two = els.get(1);\n        Element thr = els.get(2);\n\n        assertTrue(one.hasClass(\"One\"));\n        assertTrue(one.hasClass(\"ONE\"));\n\n        assertTrue(two.hasClass(\"TWO\"));\n        assertTrue(two.hasClass(\"Two\"));\n\n        assertTrue(thr.hasClass(\"ThreE\"));\n        assertTrue(thr.hasClass(\"three\"));\n    }\n\n    @Test public void text() {\n        String h = \"<div><p>Hello<p>there<p>world</div>\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"Hello there world\", doc.select(\"div > *\").text());\n    }\n\n    @Test public void hasText() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div><p></p></div>\");\n        Elements divs = doc.select(\"div\");\n        assertTrue(divs.hasText());\n        assertFalse(doc.select(\"div + div\").hasText());\n    }\n\n    @Test public void html() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div><p>There</p></div>\");\n        Elements divs = doc.select(\"div\");\n        assertEquals(\"<p>Hello</p>\\n<p>There</p>\", divs.html());\n    }\n\n    @Test public void outerHtml() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div><p>There</p></div>\");\n        Elements divs = doc.select(\"div\");\n        assertEquals(\"<div><p>Hello</p></div><div><p>There</p></div>\", TextUtil.stripNewlines(divs.outerHtml()));\n    }\n\n    @Test public void setHtml() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two</p><p>Three</p>\");\n        Elements ps = doc.select(\"p\");\n\n        ps.prepend(\"<b>Bold</b>\").append(\"<i>Ital</i>\");\n        assertEquals(\"<p><b>Bold</b>Two<i>Ital</i></p>\", TextUtil.stripNewlines(ps.get(1).outerHtml()));\n\n        ps.html(\"<span>Gone</span>\");\n        assertEquals(\"<p><span>Gone</span></p>\", TextUtil.stripNewlines(ps.get(1).outerHtml()));\n    }\n\n    @Test public void val() {\n        Document doc = Jsoup.parse(\"<input value='one' /><textarea>two</textarea>\");\n        Elements els = doc.select(\"input, textarea\");\n        assertEquals(2, els.size());\n        assertEquals(\"one\", els.val());\n        assertEquals(\"two\", els.last().val());\n\n        els.val(\"three\");\n        assertEquals(\"three\", els.first().val());\n        assertEquals(\"three\", els.last().val());\n        assertEquals(\"<textarea>three</textarea>\", els.last().outerHtml());\n    }\n\n    @Test public void before() {\n        Document doc = Jsoup.parse(\"<p>This <a>is</a> <a>jsoup</a>.</p>\");\n        doc.select(\"a\").before(\"<span>foo</span>\");\n        assertEquals(\"<p>This <span>foo</span><a>is</a> <span>foo</span><a>jsoup</a>.</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void after() {\n        Document doc = Jsoup.parse(\"<p>This <a>is</a> <a>jsoup</a>.</p>\");\n        doc.select(\"a\").after(\"<span>foo</span>\");\n        assertEquals(\"<p>This <a>is</a><span>foo</span> <a>jsoup</a><span>foo</span>.</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void wrap() {\n        String h = \"<p><b>This</b> is <b>jsoup</b></p>\";\n        Document doc = Jsoup.parse(h);\n        doc.select(\"b\").wrap(\"<i></i>\");\n        assertEquals(\"<p><i><b>This</b></i> is <i><b>jsoup</b></i></p>\", doc.body().html());\n    }\n\n    @Test public void wrapDiv() {\n        String h = \"<p><b>This</b> is <b>jsoup</b>.</p> <p>How do you like it?</p>\";\n        Document doc = Jsoup.parse(h);\n        doc.select(\"p\").wrap(\"<div></div>\");\n        assertEquals(\n            \"<div>\\n <p><b>This</b> is <b>jsoup</b>.</p>\\n</div>\\n<div>\\n <p>How do you like it?</p>\\n</div>\",\n            doc.body().html());\n    }\n\n    @Test public void unwrap() {\n        String h = \"<div><font>One</font> <font><a href=\\\"/\\\">Two</a></font></div\";\n        Document doc = Jsoup.parse(h);\n        doc.select(\"font\").unwrap();\n        assertEquals(\"<div>\\n\" +\n            \" One <a href=\\\"/\\\">Two</a>\\n\" +\n            \"</div>\", doc.body().html());\n    }\n\n    @Test public void unwrapP() {\n        String h = \"<p><a>One</a> Two</p> Three <i>Four</i> <p>Fix <i>Six</i></p>\";\n        Document doc = Jsoup.parse(h);\n        doc.select(\"p\").unwrap();\n        assertEquals(\"<a>One</a> Two Three <i>Four</i> Fix <i>Six</i>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test public void unwrapKeepsSpace() {\n        String h = \"<p>One <span>two</span> <span>three</span> four</p>\";\n        Document doc = Jsoup.parse(h);\n        doc.select(\"span\").unwrap();\n        assertEquals(\"<p>One two three four</p>\", doc.body().html());\n    }\n\n    @Test public void empty() {\n        Document doc = Jsoup.parse(\"<div><p>Hello <b>there</b></p> <p>now!</p></div>\");\n        doc.outputSettings().prettyPrint(false);\n\n        doc.select(\"p\").empty();\n        assertEquals(\"<div><p></p> <p></p></div>\", doc.body().html());\n    }\n\n    @Test public void remove() {\n        Document doc = Jsoup.parse(\"<div><p>Hello <b>there</b></p> jsoup <p>now!</p></div>\");\n        doc.outputSettings().prettyPrint(false);\n\n        doc.select(\"p\").remove();\n        assertEquals(\"<div> jsoup </div>\", doc.body().html());\n    }\n\n    @Test public void eq() {\n        String h = \"<p>Hello<p>there<p>world\";\n        Document doc = Jsoup.parse(h);\n        assertEquals(\"there\", doc.select(\"p\").eq(1).text());\n        assertEquals(\"there\", doc.select(\"p\").get(1).text());\n    }\n\n    @Test public void is() {\n        String h = \"<p>Hello<p title=foo>there<p>world\";\n        Document doc = Jsoup.parse(h);\n        Elements ps = doc.select(\"p\");\n        assertTrue(ps.is(\"[title=foo]\"));\n        assertFalse(ps.is(\"[title=bar]\"));\n    }\n\n    @Test public void parents() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><p>There</p>\");\n        Elements parents = doc.select(\"p\").parents();\n\n        assertEquals(3, parents.size());\n        assertEquals(\"div\", parents.get(0).tagName());\n        assertEquals(\"body\", parents.get(1).tagName());\n        assertEquals(\"html\", parents.get(2).tagName());\n    }\n\n    @Test public void not() {\n        Document doc = Jsoup.parse(\"<div id=1><p>One</p></div> <div id=2><p><span>Two</span></p></div>\");\n\n        Elements div1 = doc.select(\"div\").not(\":has(p > span)\");\n        assertEquals(1, div1.size());\n        assertEquals(\"1\", div1.first().id());\n\n        Elements div2 = doc.select(\"div\").not(\"#1\");\n        assertEquals(1, div2.size());\n        assertEquals(\"2\", div2.first().id());\n    }\n\n    @Test public void tagNameSet() {\n        Document doc = Jsoup.parse(\"<p>Hello <i>there</i> <i>now</i></p>\");\n        doc.select(\"i\").tagName(\"em\");\n\n        assertEquals(\"<p>Hello <em>there</em> <em>now</em></p>\", doc.body().html());\n    }\n\n    @Test public void traverse() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        doc.select(\"div\").traverse(new NodeVisitor() {\n            @Override\n            public void head(Node node, int depth) {\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n            }\n        });\n        assertEquals(\"<div><p><#text></#text></p></div><div><#text></#text></div>\", accum.toString());\n    }\n\n    @Test public void forms() {\n        Document doc = Jsoup.parse(\"<form id=1><input name=q></form><div /><form id=2><input name=f></form>\");\n        Elements els = doc.select(\"form, div\");\n        assertEquals(3, els.size());\n\n        List<FormElement> forms = els.forms();\n        assertEquals(2, forms.size());\n        assertNotNull(forms.get(0));\n        assertNotNull(forms.get(1));\n        assertEquals(\"1\", forms.get(0).id());\n        assertEquals(\"2\", forms.get(1).id());\n    }\n\n    @Test public void comments() {\n        Document doc = Jsoup.parse(\"<!-- comment1 --><p><!-- comment2 --><p class=two><!-- comment3 -->\");\n        List<Comment> comments = doc.select(\"p\").comments();\n        assertEquals(2, comments.size());\n        assertEquals(\" comment2 \", comments.get(0).getData());\n        assertEquals(\" comment3 \", comments.get(1).getData());\n\n        List<Comment> comments1 = doc.select(\"p.two\").comments();\n        assertEquals(1, comments1.size());\n        assertEquals(\" comment3 \", comments1.get(0).getData());\n    }\n\n    @Test public void textNodes() {\n        Document doc = Jsoup.parse(\"One<p>Two<a>Three</a><p>Four</p>Five\");\n        List<TextNode> textNodes = doc.select(\"p\").textNodes();\n        assertEquals(2, textNodes.size());\n        assertEquals(\"Two\", textNodes.get(0).text());\n        assertEquals(\"Four\", textNodes.get(1).text());\n    }\n\n    @Test public void dataNodes() {\n        Document doc = Jsoup.parse(\"<p>One</p><script>Two</script><style>Three</style>\");\n        List<DataNode> dataNodes = doc.select(\"p, script, style\").dataNodes();\n        assertEquals(2, dataNodes.size());\n        assertEquals(\"Two\", dataNodes.get(0).getWholeData());\n        assertEquals(\"Three\", dataNodes.get(1).getWholeData());\n\n        doc = Jsoup.parse(\"<head><script type=application/json><crux></script><script src=foo>Blah</script>\");\n        Elements script = doc.select(\"script[type=application/json]\");\n        List<DataNode> scriptNode = script.dataNodes();\n        assertEquals(1, scriptNode.size());\n        DataNode dataNode = scriptNode.get(0);\n        assertEquals(\"<crux>\", dataNode.getWholeData());\n\n        // check if they're live\n        dataNode.setWholeData(\"<cromulent>\");\n        assertEquals(\"<script type=\\\"application/json\\\"><cromulent></script>\", script.outerHtml());\n    }\n\n    @Test public void nodesEmpty() {\n        Document doc = Jsoup.parse(\"<p>\");\n        assertEquals(0, doc.select(\"form\").textNodes().size());\n    }\n\n    @Test public void classWithHyphen() {\n        Document doc = Jsoup.parse(\"<p class='tab-nav'>Check</p>\");\n        Elements els = doc.getElementsByClass(\"tab-nav\");\n        assertEquals(1, els.size());\n        assertEquals(\"Check\", els.text());\n    }\n\n    @Test public void siblings() {\n        Document doc = Jsoup.parse(\"<div><p>1<p>2<p>3<p>4<p>5<p>6</div><div><p>7<p>8<p>9<p>10<p>11<p>12</div>\");\n\n        Elements els = doc.select(\"p:eq(3)\"); // gets p4 and p10\n        assertEquals(2, els.size());\n\n        Elements next = els.next();\n        assertEquals(2, next.size());\n        assertEquals(\"5\", next.first().text());\n        assertEquals(\"11\", next.last().text());\n\n        assertEquals(0, els.next(\"p:contains(6)\").size());\n        final Elements nextF = els.next(\"p:contains(5)\");\n        assertEquals(1, nextF.size());\n        assertEquals(\"5\", nextF.first().text());\n\n        Elements nextA = els.nextAll();\n        assertEquals(4, nextA.size());\n        assertEquals(\"5\", nextA.first().text());\n        assertEquals(\"12\", nextA.last().text());\n\n        Elements nextAF = els.nextAll(\"p:contains(6)\");\n        assertEquals(1, nextAF.size());\n        assertEquals(\"6\", nextAF.first().text());\n\n        Elements prev = els.prev();\n        assertEquals(2, prev.size());\n        assertEquals(\"3\", prev.first().text());\n        assertEquals(\"9\", prev.last().text());\n\n        assertEquals(0, els.prev(\"p:contains(1)\").size());\n        final Elements prevF = els.prev(\"p:contains(3)\");\n        assertEquals(1, prevF.size());\n        assertEquals(\"3\", prevF.first().text());\n\n        Elements prevA = els.prevAll();\n        assertEquals(6, prevA.size());\n        assertEquals(\"3\", prevA.first().text());\n        assertEquals(\"7\", prevA.last().text());\n\n        Elements prevAF = els.prevAll(\"p:contains(1)\");\n        assertEquals(1, prevAF.size());\n        assertEquals(\"1\", prevAF.first().text());\n    }\n\n    @Test public void eachText() {\n        Document doc = Jsoup.parse(\"<div><p>1<p>2<p>3<p>4<p>5<p>6</div><div><p>7<p>8<p>9<p>10<p>11<p>12<p></p></div>\");\n        List<String> divText = doc.select(\"div\").eachText();\n        assertEquals(2, divText.size());\n        assertEquals(\"1 2 3 4 5 6\", divText.get(0));\n        assertEquals(\"7 8 9 10 11 12\", divText.get(1));\n\n        List<String> pText = doc.select(\"p\").eachText();\n        Elements ps = doc.select(\"p\");\n        assertEquals(13, ps.size());\n        assertEquals(12, pText.size()); // not 13, as last doesn't have text\n        assertEquals(\"1\", pText.get(0));\n        assertEquals(\"2\", pText.get(1));\n        assertEquals(\"5\", pText.get(4));\n        assertEquals(\"7\", pText.get(6));\n        assertEquals(\"12\", pText.get(11));\n    }\n\n    @Test public void eachAttr() {\n        Document doc = Jsoup.parse(\n            \"<div><a href='/foo'>1</a><a href='http://example.com/bar'>2</a><a href=''>3</a><a>4</a>\",\n            \"http://example.com\");\n\n        List<String> hrefAttrs = doc.select(\"a\").eachAttr(\"href\");\n        assertEquals(3, hrefAttrs.size());\n        assertEquals(\"/foo\", hrefAttrs.get(0));\n        assertEquals(\"http://example.com/bar\", hrefAttrs.get(1));\n        assertEquals(\"\", hrefAttrs.get(2));\n        assertEquals(4, doc.select(\"a\").size());\n\n        List<String> absAttrs = doc.select(\"a\").eachAttr(\"abs:href\");\n        assertEquals(3, absAttrs.size());\n        assertEquals(3, absAttrs.size());\n        assertEquals(\"http://example.com/foo\", absAttrs.get(0));\n        assertEquals(\"http://example.com/bar\", absAttrs.get(1));\n        assertEquals(\"http://example.com\", absAttrs.get(2));\n    }\n\n    @Test public void setElementByIndex() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n        Element newP = doc.createElement(\"p\").text(\"New\").attr(\"id\", \"new\");\n\n        Elements ps = doc.select(\"p\");\n        Element two = ps.get(1);\n        Element old = ps.set(1, newP);\n        assertSame(old, two);\n        assertSame(newP, ps.get(1)); // replaced in list\n        assertEquals(\"<p>One</p>\\n<p id=\\\"new\\\">New</p>\\n<p>Three</p>\", doc.body().html()); // replaced in dom\n    }\n\n    @Test public void removeElementByIndex() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n\n        Elements ps = doc.select(\"p\");\n        Element two = ps.get(1);\n        assertTrue(ps.contains(two));\n        Element old = ps.remove(1);\n        assertSame(old, two);\n\n        assertEquals(2, ps.size()); // removed from list\n        assertFalse(ps.contains(old));\n        assertEquals(\"<p>One</p>\\n<p>Three</p>\", doc.body().html()); // removed from dom\n    }\n\n    @Test public void removeElementByObject() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n\n        Elements ps = doc.select(\"p\");\n        Element two = ps.get(1);\n        assertTrue(ps.contains(two));\n        boolean removed = ps.remove(two);\n        assertTrue(removed);\n\n        assertEquals(2, ps.size()); // removed from list\n        assertFalse(ps.contains(two));\n        assertEquals(\"<p>One</p>\\n<p>Three</p>\", doc.body().html()); // removed from dom\n    }\n\n    @Test public void removeElementObjectNoops() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n        String origHtml = doc.html();\n        Element newP = doc.createElement(\"p\").text(\"New\");\n\n        Elements ps = doc.select(\"p\");\n        int size = ps.size();\n        assertFalse(ps.remove(newP));\n        assertFalse(ps.remove(newP.childNodes()));\n        assertEquals(origHtml, doc.html());\n        assertEquals(size, ps.size());\n    }\n\n    @Test public void clear() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two</p><div>Three</div>\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(2, ps.size());\n        ps.clear();\n        assertEquals(0, ps.size());\n\n        assertEquals(0, doc.select(\"p\").size());\n    }\n\n    @Test public void removeAll() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four</p><div>Div\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(4, ps.size());\n        Elements midPs = doc.select(\"p:gt(0):lt(3)\"); //Two and Three\n        assertEquals(2, midPs.size());\n\n        boolean removed = ps.removeAll(midPs);\n        assertEquals(2, ps.size());\n        assertTrue(removed);\n        assertEquals(2, midPs.size());\n\n        Elements divs = doc.select(\"div\");\n        assertEquals(1, divs.size());\n        assertFalse(ps.removeAll(divs));\n        assertEquals(2, ps.size());\n\n        assertEquals(\"<p>One</p>\\n<p>Four</p>\\n<div>Div</div>\", doc.body().html());\n    }\n\n    @Test public void retainAll() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four</p><div>Div\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(4, ps.size());\n        Elements midPs = doc.select(\"p:gt(0):lt(3)\"); //Two and Three\n        assertEquals(2, midPs.size());\n\n        boolean removed = ps.retainAll(midPs);\n        assertEquals(2, ps.size());\n        assertTrue(removed);\n        assertEquals(2, midPs.size());\n\n        assertEquals(\"<p>Two</p>\\n<p>Three</p>\\n<div>Div</div>\", doc.body().html());\n\n        Elements psAgain = doc.select(\"p\");\n        assertFalse(midPs.retainAll(psAgain));\n\n        assertEquals(\"<p>Two</p>\\n<p>Three</p>\\n<div>Div</div>\", doc.body().html());\n    }\n\n    @Test public void iteratorRemovesFromDom() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four\");\n        Elements ps = doc.select(\"p\");\n\n        assertEquals(4, ps.size());\n        for (Iterator<Element> it = ps.iterator(); it.hasNext(); ) {\n            Element el = it.next();\n            if (el.text().contains(\"Two\"))\n                it.remove();\n        }\n        assertEquals(3, ps.size());\n        assertEquals(\"<p>One</p>\\n<p>Three</p>\\n<p>Four</p>\", doc.body().html());\n    }\n\n    @Test public void removeIf() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four\");\n        Elements ps = doc.select(\"p\");\n\n        assertEquals(4, ps.size());\n        boolean removed = ps.removeIf(el -> el.text().contains(\"Two\"));\n        assertTrue(removed);\n        assertEquals(3, ps.size());\n        assertEquals(\"<p>One</p>\\n<p>Three</p>\\n<p>Four</p>\", doc.body().html());\n\n        assertFalse(ps.removeIf(el -> el.text().contains(\"Five\")));\n        assertEquals(\"<p>One</p>\\n<p>Three</p>\\n<p>Four</p>\", doc.body().html());\n    }\n\n    @Test public void removeIfSupportsConcurrentRead() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(4, ps.size());\n\n        boolean removed = ps.removeIf(el -> ps.contains(el));\n        assertTrue(removed);\n        assertEquals(0, ps.size());\n        assertEquals(\"\", doc.body().html());\n    }\n\n    @Test public void replaceAll() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three<p>Four\");\n        Elements ps = doc.select(\"p\");\n        assertEquals(4, ps.size());\n\n        ps.replaceAll(el -> {\n            Element div = doc.createElement(\"div\");\n            div.text(el.text());\n            return div;\n        });\n\n        // Check Elements\n        for (Element p : ps) {\n            assertEquals(\"div\", p.tagName());\n        }\n\n        // check dom\n        assertEquals(\"<div>One</div><div>Two</div><div>Three</div><div>Four</div>\", TextUtil.normalizeSpaces(doc.body().html()));\n    }\n\n    @Test void selectFirst() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two <span>Jsoup</span></p><p><span>Three</span></p>\");\n        Element span = doc.children().selectFirst(\"span\");\n        assertNotNull(span);\n        assertEquals(\"Jsoup\", span.text());\n    }\n\n    @Test void selectFirstNullOnNoMatch() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two</p><p>Three</p>\");\n        Element span = doc.children().selectFirst(\"span\");\n        assertNull(span);\n    }\n\n    @Test void expectFirst() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two <span>Jsoup</span></p><p><span>Three</span></p>\");\n        Element span = doc.children().expectFirst(\"span\");\n        assertNotNull(span);\n        assertEquals(\"Jsoup\", span.text());\n    }\n\n    @Test void expectFirstThrowsOnNoMatch() {\n        Document doc = Jsoup.parse(\"<p>One</p><p>Two</p><p>Three</p>\");\n\n        boolean threw = false;\n        try {\n            Element span = doc.children().expectFirst(\"span\");\n        } catch (IllegalArgumentException e) {\n            threw = true;\n            assertEquals(\"No elements matched the query 'span' in the elements.\", e.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n    @Test void selectFirstFromPreviousSelect() {\n        Document doc = Jsoup.parse(\"<div><p>One</p></div><div><p><span>Two</span></p></div><div><p><span>Three</span></p></div>\");\n        Elements divs = doc.select(\"div\");\n        assertEquals(3, divs.size());\n\n        Element span = divs.selectFirst(\"p span\");\n        assertNotNull(span);\n        assertEquals(\"Two\", span.text());\n\n        // test roots\n        assertNotNull(span.selectFirst(\"span\")); // reselect self\n        assertNull(span.selectFirst(\">span\")); // no span>span\n\n        assertNotNull(divs.selectFirst(\"div\")); // reselect self, similar to element.select\n        assertNull(divs.selectFirst(\">div\")); // no div>div\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/EvaluatorDebug.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.internal.StringUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\n\npublic class EvaluatorDebug {\n\n    /**\n     Cast an Evaluator into a pseudo Document, to help visualize the query. Quite coupled to the current impl.\n     */\n    public static Document asDocument(Evaluator eval) {\n        Document doc = new Document(null);\n        doc.outputSettings().outline(true).indentAmount(2);\n\n        Element el = asElement(eval);\n        doc.appendChild(el);\n\n        return doc;\n    }\n\n    public static Document asDocument(String query) {\n        Evaluator eval = QueryParser.parse(query);\n        return asDocument(eval);\n    }\n\n    public static Element asElement(Evaluator eval) {\n        Class<? extends Evaluator> evalClass = eval.getClass();\n        Element el = new Element(evalClass.getSimpleName());\n        el.attr(\"css\", eval.toString());\n        el.attr(\"cost\", Integer.toString(eval.cost()));\n\n        if (eval instanceof CombiningEvaluator) {\n            for (Evaluator inner : ((CombiningEvaluator) eval).sortedEvaluators) {\n                el.appendChild(asElement(inner));\n            }\n        } else if (eval instanceof StructuralEvaluator.ImmediateParentRun) {\n            for (Evaluator inner : ((StructuralEvaluator.ImmediateParentRun) eval).evaluators) {\n                el.appendChild(asElement(inner));\n            }\n        } else if (eval instanceof StructuralEvaluator) {\n            Evaluator inner = ((StructuralEvaluator) eval).evaluator;\n            el.appendChild(asElement(inner));\n        }\n\n        return el;\n    }\n\n    public static String sexpr(String query) {\n        return sexpr(QueryParser.parse(query));\n    }\n\n    public static String sexpr(Evaluator eval) {\n        Document doc = asDocument(eval);\n        SexprVisitor sv = new SexprVisitor();\n        doc.childNode(0).traverse(sv); // skip outer #document\n        return sv.result();\n    }\n\n    static class SexprVisitor implements NodeVisitor {\n        StringBuilder sb = StringUtil.borrowBuilder();\n\n        @Override public void head(Node node, int depth) {\n            sb\n                .append('(')\n                .append(node.nodeName());\n\n            if (node.childNodeSize() == 0)\n                sb\n                    .append(\" '\")\n                    .append(node.attr(\"css\"))\n                    .append(\"'\");\n            else\n                sb.append(\" \");\n        }\n\n        @Override public void tail(Node node, int depth) {\n            sb.append(')');\n        }\n\n        String result() {\n            return StringUtil.releaseBuilder(sb);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/EvaluatorTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.helper.Regex;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.regex.Pattern;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class EvaluatorTest {\n\n    @Test\n    public void testTagToString() {\n        Evaluator.Tag evaluator = new Evaluator.Tag(\"div\");\n        assertEquals(\"div\", evaluator.toString());\n    }\n\n    @Test\n    public void testTagStartsWithToString() {\n        Evaluator.TagStartsWith evaluator = new Evaluator.TagStartsWith(\"ns\");\n        assertEquals(\"ns|*\", evaluator.toString());\n    }\n\n    @Test\n    public void testTagEndsWithToString() {\n        Evaluator.TagEndsWith evaluator = new Evaluator.TagEndsWith(\"div\");\n        assertEquals(\"*|div\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeToString() {\n        Evaluator.Attribute evaluator = new Evaluator.Attribute(\"example\");\n        assertEquals(\"[example]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeStartingToString() {\n        Evaluator.AttributeStarting evaluator = new Evaluator.AttributeStarting(\"example\");\n        assertEquals(\"[^example]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueToString() {\n        Evaluator.AttributeWithValue evaluator = new Evaluator.AttributeWithValue(\"example\", \"value\");\n        assertEquals(\"[example=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueNotToString() {\n        Evaluator.AttributeWithValueNot evaluator = new Evaluator.AttributeWithValueNot(\"example\", \"value\");\n        assertEquals(\"[example!=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueStartingToString() {\n        Evaluator.AttributeWithValueStarting evaluator = new Evaluator.AttributeWithValueStarting(\"example\", \"value\");\n        assertEquals(\"[example^=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueEndingToString() {\n        Evaluator.AttributeWithValueEnding evaluator = new Evaluator.AttributeWithValueEnding(\"example\", \"value\");\n        assertEquals(\"[example$=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueContainingToString() {\n        Evaluator.AttributeWithValueContaining evaluator =\n            new Evaluator.AttributeWithValueContaining(\"example\", \"value\");\n        assertEquals(\"[example*=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testAttributeWithValueMatchingToString() {\n        Pattern pattern = Pattern.compile(\"value\");\n        Evaluator.AttributeWithValueMatching evaluator = new Evaluator.AttributeWithValueMatching(\"example\", pattern);\n        assertEquals(\"[example~=value]\", evaluator.toString());\n    }\n\n    @Test\n    public void testIdToString() {\n        Evaluator.Id evaluator = new Evaluator.Id(\"exampleId\");\n        assertEquals(\"#exampleId\", evaluator.toString());\n    }\n\n    @Test\n    public void testClassToString() {\n        Evaluator.Class evaluator = new Evaluator.Class(\"exampleClass\");\n        assertEquals(\".exampleClass\", evaluator.toString());\n    }\n\n    @Test\n    public void testAllElementsToString() {\n        Evaluator.AllElements evaluator = new Evaluator.AllElements();\n        assertEquals(\"*\", evaluator.toString());\n    }\n\n    @Test\n    public void testIndexLessThanToString() {\n        Evaluator.IndexLessThan evaluator = new Evaluator.IndexLessThan(5);\n        assertEquals(\":lt(5)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIndexGreaterThanToString() {\n        Evaluator.IndexGreaterThan evaluator = new Evaluator.IndexGreaterThan(5);\n        assertEquals(\":gt(5)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIndexEqualsToString() {\n        Evaluator.IndexEquals evaluator = new Evaluator.IndexEquals(5);\n        assertEquals(\":eq(5)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsLastChildToString() {\n        Evaluator.IsLastChild evaluator = new Evaluator.IsLastChild();\n        assertEquals(\":last-child\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsFirstOfTypeToString() {\n        Evaluator.IsFirstOfType evaluator = new Evaluator.IsFirstOfType();\n        assertEquals(\":first-of-type\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsLastOfTypeToString() {\n        Evaluator.IsLastOfType evaluator = new Evaluator.IsLastOfType();\n        assertEquals(\":last-of-type\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsNthChildToStringVariants() {\n        Evaluator.IsNthChild evaluator1 = new Evaluator.IsNthChild(0, 3);\n        assertEquals(\":nth-child(3)\", evaluator1.toString());\n\n        Evaluator.IsNthChild evaluator2 = new Evaluator.IsNthChild(2, 0);\n        assertEquals(\":nth-child(2n)\", evaluator2.toString());\n\n        Evaluator.IsNthChild evaluator3 = new Evaluator.IsNthChild(2, 3);\n        assertEquals(\":nth-child(2n+3)\", evaluator3.toString());\n    }\n\n    @Test\n    public void testIsNthChildToString() {\n        Evaluator.IsNthChild evaluator = new Evaluator.IsNthChild(2, 3);\n        assertEquals(\":nth-child(2n+3)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsNthLastChildToString() {\n        Evaluator.IsNthLastChild evaluator = new Evaluator.IsNthLastChild(2, 3);\n        assertEquals(\":nth-last-child(2n+3)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsNthOfTypeToString() {\n        Evaluator.IsNthOfType evaluator = new Evaluator.IsNthOfType(2, 3);\n        assertEquals(\":nth-of-type(2n+3)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsNthLastOfTypeToString() {\n        Evaluator.IsNthLastOfType evaluator = new Evaluator.IsNthLastOfType(2, 3);\n        assertEquals(\":nth-last-of-type(2n+3)\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsFirstChildToString() {\n        Evaluator.IsFirstChild evaluator = new Evaluator.IsFirstChild();\n        assertEquals(\":first-child\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsRootToString() {\n        Evaluator.IsRoot evaluator = new Evaluator.IsRoot();\n        assertEquals(\":root\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsOnlyChildToString() {\n        Evaluator.IsOnlyChild evaluator = new Evaluator.IsOnlyChild();\n        assertEquals(\":only-child\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsOnlyOfTypeToString() {\n        Evaluator.IsOnlyOfType evaluator = new Evaluator.IsOnlyOfType();\n        assertEquals(\":only-of-type\", evaluator.toString());\n    }\n\n    @Test\n    public void testIsEmptyToString() {\n        Evaluator.IsEmpty evaluator = new Evaluator.IsEmpty();\n        assertEquals(\":empty\", evaluator.toString());\n    }\n\n    @Test\n    public void testContainsTextToString() {\n        Evaluator.ContainsText evaluator = new Evaluator.ContainsText(\"example\");\n        assertEquals(\":contains(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testContainsWholeTextToString() {\n        Evaluator.ContainsWholeText evaluator = new Evaluator.ContainsWholeText(\"example\");\n        assertEquals(\":containsWholeText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testContainsWholeOwnTextToString() {\n        Evaluator.ContainsWholeOwnText evaluator = new Evaluator.ContainsWholeOwnText(\"example\");\n        assertEquals(\":containsWholeOwnText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testContainsDataToString() {\n        Evaluator.ContainsData evaluator = new Evaluator.ContainsData(\"example\");\n        assertEquals(\":containsData(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testContainsOwnTextToString() {\n        Evaluator.ContainsOwnText evaluator = new Evaluator.ContainsOwnText(\"example\");\n        assertEquals(\":containsOwn(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesToString() {\n        Pattern pattern = Pattern.compile(\"example\");\n        Evaluator.Matches evaluator = new Evaluator.Matches(pattern);\n        assertEquals(\":matches(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesOwnToString() {\n        Pattern pattern = Pattern.compile(\"example\");\n        Evaluator.MatchesOwn evaluator = new Evaluator.MatchesOwn(pattern);\n        assertEquals(\":matchesOwn(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesWholeTextToString() {\n        Pattern pattern = Pattern.compile(\"example\");\n        Evaluator.MatchesWholeText evaluator = new Evaluator.MatchesWholeText(pattern);\n        assertEquals(\":matchesWholeText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesWholeOwnTextToString() {\n        Pattern pattern = Pattern.compile(\"example\");\n        Evaluator.MatchesWholeOwnText evaluator = new Evaluator.MatchesWholeOwnText(pattern);\n        assertEquals(\":matchesWholeOwnText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesToStringRegex() {\n        Regex pattern = Regex.compile(\"example\");\n        Evaluator.Matches evaluator = new Evaluator.Matches(pattern);\n        assertEquals(\":matches(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesOwnToStringRegex() {\n        Regex pattern = Regex.compile(\"example\");\n        Evaluator.MatchesOwn evaluator = new Evaluator.MatchesOwn(pattern);\n        assertEquals(\":matchesOwn(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesWholeTextToStringRegex() {\n        Regex pattern = Regex.compile(\"example\");\n        Evaluator.MatchesWholeText evaluator = new Evaluator.MatchesWholeText(pattern);\n        assertEquals(\":matchesWholeText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchesWholeOwnTextToStringRegex() {\n        Regex pattern = Regex.compile(\"example\");\n        Evaluator.MatchesWholeOwnText evaluator = new Evaluator.MatchesWholeOwnText(pattern);\n        assertEquals(\":matchesWholeOwnText(example)\", evaluator.toString());\n    }\n\n    @Test\n    public void testMatchTextToString() {\n        Evaluator.MatchText evaluator = new Evaluator.MatchText();\n        assertEquals(\":matchText\", evaluator.toString());\n    }\n\n    @Test void nthPosition() {\n        Element orphan = new Element(\"div\");\n        Document doc = Jsoup.parse(\"<div><p>One<p>Two<p>Three<p>Four</p><h1>Five</h1></div>\");\n        Element div = doc.expectFirst(\"div\");\n        Elements ps = doc.select(\"p\");\n        Element h1 = doc.expectFirst(\"h1\");\n\n        Evaluator.CssNthEvaluator lastchild = new Evaluator.IsNthLastChild(1, 0);\n        assertEquals(0, lastchild.calculatePosition(orphan, orphan));\n        assertEquals(2, lastchild.calculatePosition(div, ps.get(3)));\n\n        Evaluator.IsNthOfType nthType = new Evaluator.IsNthOfType(1, 0);\n        assertEquals(0, nthType.calculatePosition(orphan, orphan));\n        assertEquals(1, nthType.calculatePosition(div, ps.get(0)));\n        assertEquals(2, nthType.calculatePosition(div, ps.get(1)));\n        assertEquals(1, nthType.calculatePosition(div, h1));\n\n        Evaluator.IsNthLastOfType nthLastType = new Evaluator.IsNthLastOfType(1, 0);\n        assertEquals(0, nthLastType.calculatePosition(orphan, orphan));\n        assertEquals(4, nthLastType.calculatePosition(div, ps.get(0)));\n        assertEquals(3, nthLastType.calculatePosition(div, ps.get(1)));\n        assertEquals(1, nthLastType.calculatePosition(div, h1));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/NodesTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.TextNode;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class NodesTest {\n    @Test void before() {\n        Document doc = Jsoup.parse(\"<span>One</span> <span>Two</span> <span>Three</span>\");\n        Nodes<TextNode> nodes = doc.selectNodes(\"::text:contains(o)\", TextNode.class);\n        nodes.before(\"<wbr>\");\n        assertEquals(\"<span><wbr>One</span> <span><wbr>Two</span> <span>Three</span>\", doc.body().html());\n    }\n\n    @Test void after() {\n        Document doc = Jsoup.parse(\"<span>One</span> <span>Two</span> <span>Three</span>\");\n        Nodes<TextNode> nodes = doc.selectNodes(\"::text:contains(o)\", TextNode.class);\n        nodes.after(\"<wbr>\");\n        assertEquals(\"<span>One<wbr></span> <span>Two<wbr></span> <span>Three</span>\", doc.body().html());\n    }\n\n    @Test void wrap() {\n        Document doc = Jsoup.parse(\"<span>One</span> <span>Two</span> <span>Three</span>\");\n        Nodes<TextNode> nodes = doc.selectNodes(\"::text:contains(o)\", TextNode.class);\n        nodes.wrap(\"<b></b>\");\n        assertEquals(\"<span><b>One</b></span> <span><b>Two</b></span> <span>Three</span>\", doc.body().html());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/QueryParserTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.junit.jupiter.api.Test;\n\nimport static org.jsoup.select.EvaluatorDebug.asElement;\nimport static org.jsoup.select.EvaluatorDebug.sexpr;\nimport static org.jsoup.select.Selector.SelectorParseException;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests for the Selector Query Parser.\n *\n * @author Jonathan Hedley\n */\npublic class QueryParserTest {\n    @Test public void testConsumeSubQuery() {\n        Document doc = Jsoup.parse(\"<html><head>h</head><body>\" +\n                \"<li><strong>l1</strong></li>\" +\n                \"<a><li><strong>l2</strong></li></a>\" +\n                \"<p><strong>yes</strong></p>\" +\n                \"</body></html>\");\n        assertEquals(\"l1 yes\", doc.body().select(\">p>strong,>li>strong\").text()); // selecting immediate from body\n        assertEquals(\"l1 yes\", doc.body().select(\" > p > strong , > li > strong\").text()); // space variants\n        assertEquals(\"l2 yes\", doc.select(\"body>p>strong,body>*>li>strong\").text());\n        assertEquals(\"l2 yes\", doc.select(\"body>*>li>strong,body>p>strong\").text());\n        assertEquals(\"l2 yes\", doc.select(\"body>p>strong,body>*>li>strong\").text());\n    }\n\n    @Test public void testImmediateParentRun() {\n        String query = \"div > p > bold.brass\";\n        assertEquals(\"(ImmediateParentRun (Tag 'div')(Tag 'p')(And (Tag 'bold')(Class '.brass')))\", sexpr(query));\n\n        /*\n        <ImmediateParentRun css=\"div > p > bold.brass\" cost=\"11\">\n          <Tag css=\"div\" cost=\"1\"></Tag>\n          <Tag css=\"p\" cost=\"1\"></Tag>\n          <And css=\"bold.brass\" cost=\"7\">\n            <Tag css=\"bold\" cost=\"1\"></Tag>\n            <Class css=\".brass\" cost=\"6\"></Class>\n          </And>\n        </ImmediateParentRun>\n         */\n    }\n\n    @Test public void testOrGetsCorrectPrecedence() {\n        // tests that a selector \"a b, c d, e f\" evals to (a AND b) OR (c AND d) OR (e AND f)\"\n        // top level or, three child ands\n        String query = \"a b, c d, e f\";\n        String parsed = sexpr(query);\n        assertEquals(\"(Or (And (Tag 'b')(Ancestor (Tag 'a')))(And (Tag 'd')(Ancestor (Tag 'c')))(And (Tag 'f')(Ancestor (Tag 'e'))))\", parsed);\n\n        /*\n        <Or css=\"a b, c d, e f\" cost=\"9\">\n          <And css=\"a b\" cost=\"3\">\n            <Tag css=\"b\" cost=\"1\"></Tag>\n            <Parent css=\"a \" cost=\"2\">\n              <Tag css=\"a\" cost=\"1\"></Tag>\n            </Parent>\n          </And>\n          <And css=\"c d\" cost=\"3\">\n            <Tag css=\"d\" cost=\"1\"></Tag>\n            <Parent css=\"c \" cost=\"2\">\n              <Tag css=\"c\" cost=\"1\"></Tag>\n            </Parent>\n          </And>\n          <And css=\"e f\" cost=\"3\">\n            <Tag css=\"f\" cost=\"1\"></Tag>\n            <Parent css=\"e \" cost=\"2\">\n              <Tag css=\"e\" cost=\"1\"></Tag>\n            </Parent>\n          </And>\n        </Or>\n         */\n    }\n\n    @Test public void testParsesMultiCorrectly() {\n        String query = \".foo.qux[attr=bar] > ol.bar, ol > li + li\";\n        String parsed = sexpr(query);\n        assertEquals(\"(Or (And (Tag 'li')(ImmediatePreviousSibling (ImmediateParentRun (Tag 'ol')(Tag 'li'))))(ImmediateParentRun (And (AttributeWithValue '[attr=bar]')(Class '.foo')(Class '.qux'))(And (Tag 'ol')(Class '.bar'))))\", parsed);\n\n        /*\n        <Or css=\".foo.qux[attr=bar] > ol.bar, ol > li + li\" cost=\"31\">\n          <And css=\"ol > li + li\" cost=\"7\">\n            <Tag css=\"li\" cost=\"1\"></Tag>\n            <ImmediatePreviousSibling css=\"ol > li + \" cost=\"6\">\n              <ImmediateParentRun css=\"ol > li\" cost=\"4\">\n                <Tag css=\"ol\" cost=\"1\"></Tag>\n                <Tag css=\"li\" cost=\"1\"></Tag>\n              </ImmediateParentRun>\n            </ImmediatePreviousSibling>\n          </And>\n          <ImmediateParentRun css=\".foo.qux[attr=bar] > ol.bar\" cost=\"24\">\n            <And css=\".foo.qux[attr=bar]\" cost=\"15\">\n              <AttributeWithValue css=\"[attr=bar]\" cost=\"3\"></AttributeWithValue>\n              <Class css=\".foo\" cost=\"6\"></Class>\n              <Class css=\".qux\" cost=\"6\"></Class>\n            </And>\n            <And css=\"ol.bar\" cost=\"7\">\n              <Tag css=\"ol\" cost=\"1\"></Tag>\n              <Class css=\".bar\" cost=\"6\"></Class>\n            </And>\n          </ImmediateParentRun>\n        </Or>\n         */\n    }\n\n    @Test void idDescenderClassOrder() {\n        // https://github.com/jhy/jsoup/issues/2254\n        // '#id .class' cost\n        String query = \"#id .class\";\n        String parsed = sexpr(query);\n        assertEquals(\"(And (Class '.class')(Ancestor (Id '#id')))\", parsed);\n\n        /*\n        <And css=\"#id .class\" cost=\"22\">\n         <Class css=\".class\" cost=\"6\"></Class>\n         <Ancestor css=\"#id \" cost=\"16\">\n          <Id css=\"#id\" cost=\"2\"></Id>\n         </Ancestor>\n        </And>\n         */\n    }\n\n\n    @Test\n    public void exceptionOnUncloseAttribute() {\n        Selector.SelectorParseException exception =\n            assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse(\"section > a[href=\\\"]\"));\n        assertEquals(\n            \"Did not find balanced marker at 'href=\\\"]'\",\n            exception.getMessage());\n    }\n\n    @Test\n    public void testParsesSingleQuoteInContains() {\n        Selector.SelectorParseException exception =\n            assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse(\"p:contains(One \\\" One)\"));\n        assertEquals(\"Did not find balanced marker at 'One \\\" One)'\",\n            exception.getMessage());\n    }\n\n    @Test\n    public void exceptOnEmptySelector() {\n        SelectorParseException exception = assertThrows(SelectorParseException.class, () -> QueryParser.parse(\"\"));\n        assertEquals(\"String must not be empty\", exception.getMessage());\n    }\n\n    @Test\n    public void exceptOnNullSelector() {\n        SelectorParseException exception = assertThrows(SelectorParseException.class, () -> QueryParser.parse(null));\n        assertEquals(\"String must not be empty\", exception.getMessage());\n    }\n\n    @Test\n    public void exceptOnUnhandledEvaluator() {\n        SelectorParseException exception =\n            assertThrows(SelectorParseException.class, () -> QueryParser.parse(\"div / foo\"));\n        assertEquals(\"Could not parse query 'div / foo': unexpected token at '/ foo'\", exception.getMessage());\n    }\n\n    @Test public void okOnSpacesForeAndAft() {\n        Evaluator parse = QueryParser.parse(\" span div  \");\n        assertEquals(\"span div\", parse.toString());\n    }\n\n    @Test public void structuralEvaluatorsToString() {\n        String q = \"a:not(:has(span.foo)) b d > e + f ~ g\";\n        Evaluator parse = QueryParser.parse(q);\n        assertEquals(q, parse.toString());\n        String parsed = sexpr(q);\n        assertEquals(\"(And (Tag 'g')(PreviousSibling (And (Tag 'f')(ImmediatePreviousSibling (ImmediateParentRun (And (Tag 'd')(Ancestor (And (Tag 'b')(Ancestor (And (Tag 'a')(Not (Has (And (Tag 'span')(Class '.foo')))))))))(Tag 'e'))))))\", parsed);\n    }\n\n    @Test public void parsesOrAfterAttribute() {\n        // https://github.com/jhy/jsoup/issues/2073\n        String q = \"#parent [class*=child], .some-other-selector .nested\";\n        String parsed = sexpr(q);\n        assertEquals(\"(Or (And (AttributeWithValueContaining '[class*=child]')(Ancestor (Id '#parent')))(And (Class '.nested')(Ancestor (Class '.some-other-selector'))))\", parsed);\n\n        assertEquals(\"(Or (Class '.some-other-selector')(And (AttributeWithValueContaining '[class*=child]')(Ancestor (Id '#parent'))))\", sexpr(\"#parent [class*=child], .some-other-selector\"));\n        assertEquals(\"(Or (And (Id '#el')(AttributeWithValueContaining '[class*=child]'))(Class '.some-other-selector'))\", sexpr(\"#el[class*=child], .some-other-selector\"));\n        assertEquals(\"(Or (And (AttributeWithValueContaining '[class*=child]')(Ancestor (Id '#parent')))(And (Class '.nested')(Ancestor (Class '.some-other-selector'))))\", sexpr(\"#parent [class*=child], .some-other-selector .nested\"));\n    }\n\n    @Test void parsesEscapedSubqueries() {\n        String html = \"<div class='-4a'>One</div> <div id='-4a'>Two</div>\";\n        Document doc = Jsoup.parse(html);\n\n        String classQ = \"div.-\\\\34 a\";\n        Element div1 = doc.expectFirst(classQ);\n        assertEquals(\"One\", div1.wholeText());\n\n        String idQ = \"#-\\\\34 a\";\n        Element div2 = doc.expectFirst(idQ);\n        assertEquals(\"Two\", div2.wholeText());\n\n        String genClassQ = \"html > body > div.-\\\\34 a\";\n        assertEquals(genClassQ, div1.cssSelector());\n        assertSame(div1, doc.expectFirst(genClassQ));\n\n        String deepIdQ = \"html > body > #-\\\\34 a\";\n        assertEquals(idQ, div2.cssSelector());\n        assertSame(div2, doc.expectFirst(deepIdQ));\n\n        assertEquals(\"(ImmediateParentRun (Tag 'html')(Tag 'body')(And (Tag 'div')(Class '.-4a')))\", sexpr(genClassQ));\n        assertEquals(\"(ImmediateParentRun (Tag 'html')(Tag 'body')(Id '#-4a'))\", sexpr(deepIdQ));\n    }\n\n    @Test void trailingParens() {\n        SelectorParseException exception =\n            assertThrows(SelectorParseException.class, () -> QueryParser.parse(\"div:has(p))\"));\n        assertEquals(\"Could not parse query 'div:has(p))': unexpected token at ')'\", exception.getMessage());\n    }\n\n    @Test void consecutiveCombinators() {\n        Selector.SelectorParseException exception1 =\n                assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse(\"div>>p\"));\n        assertEquals(\n                \"Could not parse query 'div>>p': unexpected token at '>p'\",\n                exception1.getMessage());\n\n        Selector.SelectorParseException exception2 =\n                assertThrows(Selector.SelectorParseException.class, () -> QueryParser.parse(\"+ + div\"));\n        assertEquals(\n                \"Could not parse query '+ + div': unexpected token at '+ div'\",\n                exception2.getMessage());\n    }\n\n    @Test void hasNodeSelector() {\n        String q = \"p:has(::comment:contains(some text))\";\n        Evaluator e = QueryParser.parse(q);\n        assertEquals(\"(And (Tag 'p')(Has (And (InstanceType '::comment')(ContainsValue ':contains(some text)'))))\", sexpr(e));\n        assertEquals(q, e.toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/SelectorIT.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.parser.Parser;\nimport org.jsoup.parser.StreamParser;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\npublic class SelectorIT {\n\n    @Test\n    public void multiThreadHas() throws InterruptedException {\n        final String html = \"<div id=1></div><div id=2><p>One</p><p>Two</p>\";\n        final Evaluator eval = QueryParser.parse(\"div:has(p)\");\n\n        int numThreads = 20;\n        int numThreadLoops = 5;\n\n        SelectorIT.ThreadCatcher catcher = new SelectorIT.ThreadCatcher();\n\n        Thread[] threads = new Thread[numThreads];\n        for (int threadNum = 0; threadNum < numThreads; threadNum++) {\n            Thread thread = new Thread(() -> {\n                Document doc = Jsoup.parse(html);\n                for (int loop = 0; loop < numThreadLoops; loop++) {\n                    Elements els = doc.select(eval);\n                    assertEquals(1, els.size());\n                    assertEquals(\"2\", els.get(0).id());\n                }\n            });\n            thread.setName(\"Runner-\" + threadNum);\n            thread.start();\n            thread.setUncaughtExceptionHandler(catcher);\n            threads[threadNum] = thread;\n        }\n\n        // now join them all\n        for (Thread thread : threads) {\n            thread.join();\n        }\n\n        assertEquals(0, catcher.exceptionCount.get());\n    }\n\n    static class ThreadCatcher implements Thread.UncaughtExceptionHandler {\n        AtomicInteger exceptionCount = new AtomicInteger();\n\n        @Override\n        public void uncaughtException(Thread t, Throwable e) {\n\n            e.printStackTrace();\n            exceptionCount.incrementAndGet();\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/SelectorTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.MultiLocaleExtension.MultiLocaleTest;\nimport org.jsoup.nodes.CDataNode;\nimport org.jsoup.nodes.Comment;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static org.jsoup.select.EvaluatorDebug.sexpr;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * Tests that the selector selects correctly.\n *\n * @author Jonathan Hedley, jonathan@hedley.net\n */\npublic class SelectorTest {\n\n    /** Test that the selected elements match exactly the specified IDs. */\n    public static void assertSelectedIds(Elements els, String... ids) {\n        assertNotNull(els);\n        assertEquals(ids.length, els.size(), \"Incorrect number of selected elements\");\n        for (int i = 0; i < ids.length; i++) {\n            assertEquals(ids[i], els.get(i).id(), \"Incorrect content at index\");\n        }\n    }\n\n    public static void assertSelectedOwnText(Elements els, String... ownTexts) {\n        assertNotNull(els);\n        assertEquals(ownTexts.length, els.size(), \"Incorrect number of selected elements\");\n        for (int i = 0; i < ownTexts.length; i++) {\n            assertEquals(ownTexts[i], els.get(i).ownText(), \"Incorrect content at index\");\n        }\n    }\n\n    @Test public void testByTag() {\n        // should be case-insensitive\n        Elements els = Jsoup.parse(\"<div id=1><div id=2><p>Hello</p></div></div><DIV id=3>\").select(\"DIV\");\n        assertSelectedIds(els, \"1\", \"2\", \"3\");\n\n        Elements none = Jsoup.parse(\"<div id=1><div id=2><p>Hello</p></div></div><div id=3>\").select(\"span\");\n        assertTrue(none.isEmpty());\n    }\n\n    @Test public void byEscapedTag() {\n        // tested same result as js document.querySelector\n        Document doc = Jsoup.parse(\"<p.p>One</p.p> <p\\\\p>Two</p\\\\p>\");\n\n        Element one = doc.expectFirst(\"p\\\\.p\");\n        assertEquals(\"One\", one.text());\n\n        Element two = doc.expectFirst(\"p\\\\\\\\p\");\n        assertEquals(\"Two\", two.text());\n    }\n\n    @Test public void testById() {\n        Elements els = Jsoup.parse(\"<div><p id=foo>Hello</p><p id=foo>Foo two!</p></div>\").select(\"#foo\");\n        assertSelectedOwnText(els, \"Hello\", \"Foo two!\");\n\n        Elements none = Jsoup.parse(\"<div id=1></div>\").select(\"#foo\");\n        assertTrue(none.isEmpty());\n    }\n\n    @Test public void byEscapedId() {\n        Document doc = Jsoup.parse(\"<p id='i.d'>One</p> <p id='i\\\\d'>Two</p> <p id='one-two/three'>Three</p>\");\n\n        Element one = doc.expectFirst(\"#i\\\\.d\");\n        assertEquals(\"One\", one.text());\n\n        Element two = doc.expectFirst(\"#i\\\\\\\\d\");\n        assertEquals(\"Two\", two.text());\n\n        Element thr = doc.expectFirst(\"p#one-two\\\\/three\");\n        assertEquals(\"Three\", thr.text());\n    }\n\n    @Test public void testByClass() {\n        Elements els = Jsoup.parse(\"<p id=0 class='ONE two'><p id=1 class='one'><p id=2 class='two'>\").select(\"P.One\");\n        assertSelectedIds(els, \"0\", \"1\");\n\n        Elements none = Jsoup.parse(\"<div class='one'></div>\").select(\".foo\");\n        assertTrue(none.isEmpty());\n\n        Elements els2 = Jsoup.parse(\"<div class='One-Two' id=1></div>\").select(\".one-two\");\n        assertSelectedIds(els2, \"1\");\n    }\n\n    @Test public void byEscapedClass() {\n        Document doc = Jsoup.parse(\"<p class='one.two#three'>One</p>\");\n        assertSelectedOwnText(doc.select(\"p.one\\\\.two\\\\#three\"), \"One\");\n    }\n\n    @Test public void testByClassCaseInsensitive() {\n        String html = \"<p Class=foo>One <p Class=Foo>Two <p class=FOO>Three <p class=farp>Four\";\n        Elements elsFromClass = Jsoup.parse(html).select(\"P.Foo\");\n        Elements elsFromAttr = Jsoup.parse(html).select(\"p[class=foo]\");\n\n        assertEquals(elsFromAttr.size(), elsFromClass.size());\n        assertSelectedOwnText(elsFromClass, \"One\", \"Two\", \"Three\");\n    }\n\n\n    @MultiLocaleTest\n    public void testByAttribute(Locale locale) {\n        Locale.setDefault(locale);\n\n        String h = \"<div Title=Foo /><div Title=Bar /><div Style=Qux /><div title=Balim /><div title=SLIM />\" +\n                \"<div data-name='with spaces'/>\";\n        Document doc = Jsoup.parse(h);\n\n        Elements withTitle = doc.select(\"[title]\");\n        assertEquals(4, withTitle.size());\n\n        Elements foo = doc.select(\"[TITLE=foo]\");\n        assertEquals(1, foo.size());\n\n        Elements foo2 = doc.select(\"[title=\\\"foo\\\"]\");\n        assertEquals(1, foo2.size());\n\n        Elements foo3 = doc.select(\"[title=\\\"Foo\\\"]\");\n        assertEquals(1, foo3.size());\n\n        Elements dataName = doc.select(\"[data-name=\\\"with spaces\\\"]\");\n        assertEquals(1, dataName.size());\n        assertEquals(\"with spaces\", dataName.first().attr(\"data-name\"));\n\n        Elements not = doc.select(\"div[title!=bar]\");\n        assertEquals(5, not.size());\n        assertEquals(\"Foo\", not.first().attr(\"title\"));\n\n        Elements starts = doc.select(\"[title^=ba]\");\n        assertEquals(2, starts.size());\n        assertEquals(\"Bar\", starts.first().attr(\"title\"));\n        assertEquals(\"Balim\", starts.last().attr(\"title\"));\n\n        Elements ends = doc.select(\"[title$=im]\");\n        assertEquals(2, ends.size());\n        assertEquals(\"Balim\", ends.first().attr(\"title\"));\n        assertEquals(\"SLIM\", ends.last().attr(\"title\"));\n\n        Elements contains = doc.select(\"[title*=i]\");\n        assertEquals(2, contains.size());\n        assertEquals(\"Balim\", contains.first().attr(\"title\"));\n        assertEquals(\"SLIM\", contains.last().attr(\"title\"));\n    }\n\n    @Test public void testNamespacedTag() {\n        Document doc = Jsoup.parse(\"<div><abc:def id=1>Hello</abc:def></div> <abc:def class=bold id=2>There</abc:def>\");\n        Elements byTag = doc.select(\"abc|def\");\n        assertSelectedIds(byTag, \"1\", \"2\");\n\n        Elements byAttr = doc.select(\".bold\");\n        assertSelectedIds(byAttr, \"2\");\n\n        Elements byTagAttr = doc.select(\"abc|def.bold\");\n        assertSelectedIds(byTagAttr, \"2\");\n\n        Elements byContains = doc.select(\"abc|def:contains(e)\");\n        assertSelectedIds(byContains, \"1\", \"2\");\n    }\n\n    @Test public void testWildcardNamespacedTag() {\n        Document doc = Jsoup.parse(\"<div><abc:def id=1>Hello</abc:def></div> <abc:def class=bold id=2>There</abc:def>\");\n        Elements byTag = doc.select(\"*|def\");\n        assertSelectedIds(byTag, \"1\", \"2\");\n\n        Elements byAttr = doc.select(\".bold\");\n        assertSelectedIds(byAttr, \"2\");\n\n        Elements byTagAttr = doc.select(\"*|def.bold\");\n        assertSelectedIds(byTagAttr, \"2\");\n\n        Elements byContains = doc.select(\"*|def:contains(e)\");\n        assertSelectedIds(byContains, \"1\", \"2\");\n    }\n\n    @Test public void testNamespacedWildcardTag() {\n        // https://github.com/jhy/jsoup/issues/1811\n        Document doc = Jsoup.parse(\"<p>One</p> <ac:p id=2>Two</ac:p> <ac:img id=3>Three</ac:img>\");\n        Elements byNs = doc.select(\"ac|*\");\n        assertSelectedIds(byNs, \"2\", \"3\");\n    }\n\n    @Test public void testWildcardNamespacedXmlTag() {\n        Document doc = Jsoup.parse(\n            \"<div><Abc:Def id=1>Hello</Abc:Def></div> <Abc:Def class=bold id=2>There</abc:def>\",\n            \"\", Parser.xmlParser()\n        );\n\n        Elements byTag = doc.select(\"*|Def\");\n        assertSelectedIds(byTag, \"1\", \"2\");\n\n        Elements byAttr = doc.select(\".bold\");\n        assertSelectedIds(byAttr, \"2\");\n\n        Elements byTagAttr = doc.select(\"*|Def.bold\");\n        assertSelectedIds(byTagAttr, \"2\");\n\n        Elements byContains = doc.select(\"*|Def:contains(e)\");\n        assertSelectedIds(byContains, \"1\", \"2\");\n    }\n\n    @Test public void testWildCardNamespacedCaseVariations() {\n        Document doc = Jsoup.parse(\"<One:Two>One</One:Two><three:four>Two</three:four>\", \"\", Parser.xmlParser());\n        Elements els1 = doc.select(\"One|Two\");\n        Elements els2 = doc.select(\"one|two\");\n        Elements els3 = doc.select(\"Three|Four\");\n        Elements els4 = doc.select(\"three|Four\");\n\n        assertEquals(els1, els2);\n        assertEquals(els3, els4);\n        assertEquals(\"One\", els1.text());\n        assertEquals(1, els1.size());\n        assertEquals(\"Two\", els3.text());\n        assertEquals(1, els2.size());\n    }\n\n    @MultiLocaleTest\n    public void testByAttributeStarting(Locale locale) {\n        Locale.setDefault(locale);\n\n        Document doc = Jsoup.parse(\"<div id=1 ATTRIBUTE data-name=jsoup>Hello</div><p data-val=5 id=2>There</p><p id=3>No</p>\");\n        Elements withData = doc.select(\"[^data-]\");\n        assertEquals(2, withData.size());\n        assertEquals(\"1\", withData.first().id());\n        assertEquals(\"2\", withData.last().id());\n\n        withData = doc.select(\"p[^data-]\");\n        assertEquals(1, withData.size());\n        assertEquals(\"2\", withData.first().id());\n\n        assertEquals(1, doc.select(\"[^attrib]\").size());\n    }\n\n    @Test public void testByAttributeRegex() {\n        Document doc = Jsoup.parse(\"<p><img src=foo.png id=1><img src=bar.jpg id=2><img src=qux.JPEG id=3><img src=old.gif><img></p>\");\n        Elements imgs = doc.select(\"img[src~=(?i)\\\\.(png|jpe?g)]\");\n        assertSelectedIds(imgs, \"1\", \"2\", \"3\");\n    }\n\n    @Test public void testByAttributeRegexCharacterClass() {\n        Document doc = Jsoup.parse(\"<p><img src=foo.png id=1><img src=bar.jpg id=2><img src=qux.JPEG id=3><img src=old.gif id=4></p>\");\n        Elements imgs = doc.select(\"img[src~=[o]]\");\n        assertSelectedIds(imgs, \"1\", \"4\");\n    }\n\n    @Test public void testByAttributeRegexCombined() {\n        Document doc = Jsoup.parse(\"<div><table class=x><td>Hello</td></table></div>\");\n        Elements els = doc.select(\"div table[class~=x|y]\");\n        assertEquals(1, els.size());\n        assertEquals(\"Hello\", els.text());\n    }\n\n    @Test public void testCombinedWithContains() {\n        Document doc = Jsoup.parse(\"<p id=1>One</p><p>Two +</p><p>Three +</p>\");\n        Elements els = doc.select(\"p#1 + :contains(+)\");\n        assertEquals(1, els.size());\n        assertEquals(\"Two +\", els.text());\n        assertEquals(\"p\", els.first().tagName());\n    }\n\n    @Test public void testAllElements() {\n        String h = \"<div><p>Hello</p><p><b>there</b></p></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements allDoc = doc.select(\"*\");\n        Elements allUnderDiv = doc.select(\"div *\");\n        assertEquals(8, allDoc.size());\n        assertEquals(3, allUnderDiv.size());\n        assertEquals(\"p\", allUnderDiv.first().tagName());\n    }\n\n    @Test public void testAllWithClass() {\n        String h = \"<p class=first>One<p class=first>Two<p>Three\";\n        Document doc = Jsoup.parse(h);\n        Elements ps = doc.select(\"*.first\");\n        assertEquals(2, ps.size());\n    }\n\n    @Test public void testGroupOr() {\n        String h = \"<div title=foo /><div title=bar /><div /><p></p><img /><span title=qux>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\"p,div,[title]\");\n\n        assertEquals(5, els.size());\n        assertEquals(\"div\", els.get(0).tagName());\n        assertEquals(\"foo\", els.get(0).attr(\"title\"));\n        assertEquals(\"div\", els.get(1).tagName());\n        assertEquals(\"bar\", els.get(1).attr(\"title\"));\n        assertEquals(\"div\", els.get(2).tagName());\n        assertEquals(0, els.get(2).attr(\"title\").length()); // missing attributes come back as empty string\n        assertFalse(els.get(2).hasAttr(\"title\"));\n        assertEquals(\"p\", els.get(3).tagName());\n        assertEquals(\"span\", els.get(4).tagName());\n    }\n\n    @Test public void testGroupOrAttribute() {\n        String h = \"<div id=1 /><div id=2 /><div title=foo /><div title=bar />\";\n        Elements els = Jsoup.parse(h).select(\"[id],[title=foo]\");\n\n        assertEquals(3, els.size());\n        assertEquals(\"1\", els.get(0).id());\n        assertEquals(\"2\", els.get(1).id());\n        assertEquals(\"foo\", els.get(2).attr(\"title\"));\n    }\n\n    @Test public void descendant() {\n        String h = \"<div class=head><p class=first>Hello</p><p>There</p></div><p>None</p>\";\n        Document doc = Jsoup.parse(h);\n        Element root = doc.getElementsByClass(\"HEAD\").first();\n\n        Elements els = root.select(\".head p\");\n        assertEquals(2, els.size());\n        assertEquals(\"Hello\", els.get(0).text());\n        assertEquals(\"There\", els.get(1).text());\n\n        Elements p = root.select(\"p.first\");\n        assertEquals(1, p.size());\n        assertEquals(\"Hello\", p.get(0).text());\n\n        Elements empty = root.select(\"p .first\"); // self, not descend, should not match\n        assertEquals(0, empty.size());\n\n        Elements aboveRoot = root.select(\"body div.head\");\n        assertEquals(0, aboveRoot.size());\n    }\n\n    @Test public void and() {\n        String h = \"<div id=1 class='foo bar' title=bar name=qux><p class=foo title=bar>Hello</p></div\";\n        Document doc = Jsoup.parse(h);\n\n        Elements div = doc.select(\"div.foo\");\n        assertEquals(1, div.size());\n        assertEquals(\"div\", div.first().tagName());\n\n        Elements p = doc.select(\"div .foo\"); // space indicates like \"div *.foo\"\n        assertEquals(1, p.size());\n        assertEquals(\"p\", p.first().tagName());\n\n        Elements div2 = doc.select(\"div#1.foo.bar[title=bar][name=qux]\"); // very specific!\n        assertEquals(1, div2.size());\n        assertEquals(\"div\", div2.first().tagName());\n\n        Elements p2 = doc.select(\"div *.foo\"); // space indicates like \"div *.foo\"\n        assertEquals(1, p2.size());\n        assertEquals(\"p\", p2.first().tagName());\n    }\n\n    @Test public void deeperDescendant() {\n        String h = \"<div class=head><p><span class=first>Hello</div><div class=head><p class=first><span>Another</span><p>Again</div>\";\n        Document doc = Jsoup.parse(h);\n        Element root = doc.getElementsByClass(\"head\").first();\n\n        Elements els = root.select(\"div p .first\");\n        assertEquals(1, els.size());\n        assertEquals(\"Hello\", els.first().text());\n        assertEquals(\"span\", els.first().tagName());\n\n        Elements aboveRoot = root.select(\"body p .first\");\n        assertEquals(0, aboveRoot.size());\n    }\n\n    @Test public void parentChildElement() {\n        String h = \"<div id=1><div id=2><div id = 3></div></div></div><div id=4></div>\";\n        Document doc = Jsoup.parse(h);\n\n        Elements divs = doc.select(\"div > div\");\n        assertEquals(2, divs.size());\n        assertEquals(\"2\", divs.get(0).id()); // 2 is child of 1\n        assertEquals(\"3\", divs.get(1).id()); // 3 is child of 2\n\n        Elements div2 = doc.select(\"div#1 > div\");\n        assertEquals(1, div2.size());\n        assertEquals(\"2\", div2.get(0).id());\n    }\n\n    @Test public void parentWithClassChild() {\n        String h = \"<h1 class=foo><a href=1 /></h1><h1 class=foo><a href=2 class=bar /></h1><h1><a href=3 /></h1>\";\n        Document doc = Jsoup.parse(h);\n\n        Elements allAs = doc.select(\"h1 > a\");\n        assertEquals(3, allAs.size());\n        assertEquals(\"a\", allAs.first().tagName());\n\n        Elements fooAs = doc.select(\"h1.foo > a\");\n        assertEquals(2, fooAs.size());\n        assertEquals(\"a\", fooAs.first().tagName());\n\n        Elements barAs = doc.select(\"h1.foo > a.bar\");\n        assertEquals(1, barAs.size());\n    }\n\n    @Test public void parentChildStar() {\n        String h = \"<div id=1><p>Hello<p><b>there</b></p></div><div id=2><span>Hi</span></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements divChilds = doc.select(\"div > *\");\n        assertEquals(3, divChilds.size());\n        assertEquals(\"p\", divChilds.get(0).tagName());\n        assertEquals(\"p\", divChilds.get(1).tagName());\n        assertEquals(\"span\", divChilds.get(2).tagName());\n    }\n\n    @Test public void streamParentChildStar() {\n        String h = \"<div id=1><p>Hello<p><b>there</b></p></div><div id=2><span>Hi</span></div>\";\n        Document doc = Jsoup.parse(h);\n\n        List<Element> divChilds = doc.selectStream(\"div > *\")\n            .collect(Collectors.toList());\n\n        assertEquals(3, divChilds.size());\n        assertEquals(\"p\", divChilds.get(0).tagName());\n        assertEquals(\"p\", divChilds.get(1).tagName());\n        assertEquals(\"span\", divChilds.get(2).tagName());\n    }\n\n    @Test public void multiChildDescent() {\n        String h = \"<div id=foo><h1 class=bar><a href=http://example.com/>One</a></h1></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\"div#foo > h1.bar > a[href*=example]\");\n        assertEquals(1, els.size());\n        assertEquals(\"a\", els.first().tagName());\n    }\n\n    @Test public void caseInsensitive() {\n        String h = \"<dIv tItle=bAr><div>\"; // mixed case so a simple toLowerCase() on value doesn't catch\n        Document doc = Jsoup.parse(h);\n\n        assertEquals(2, doc.select(\"DiV\").size());\n        assertEquals(1, doc.select(\"DiV[TiTLE]\").size());\n        assertEquals(1, doc.select(\"DiV[TiTLE=BAR]\").size());\n        assertEquals(0, doc.select(\"DiV[TiTLE=BARBARELLA]\").size());\n    }\n\n    @Test public void adjacentSiblings() {\n        String h = \"<ol><li>One<li>Two<li>Three</ol>\";\n        Document doc = Jsoup.parse(h);\n        Elements sibs = doc.select(\"li + li\");\n        assertEquals(2, sibs.size());\n        assertEquals(\"Two\", sibs.get(0).text());\n        assertEquals(\"Three\", sibs.get(1).text());\n    }\n\n    @Test public void adjacentSiblingsWithId() {\n        String h = \"<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>\";\n        Document doc = Jsoup.parse(h);\n        Elements sibs = doc.select(\"li#1 + li#2\");\n        assertEquals(1, sibs.size());\n        assertEquals(\"Two\", sibs.get(0).text());\n    }\n\n    @Test public void notAdjacent() {\n        String h = \"<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>\";\n        Document doc = Jsoup.parse(h);\n        Elements sibs = doc.select(\"li#1 + li#3\");\n        assertEquals(0, sibs.size());\n    }\n\n    @Test public void mixCombinator() {\n        String h = \"<div class=foo><ol><li>One<li>Two<li>Three</ol></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements sibs = doc.select(\"body > div.foo li + li\");\n\n        assertEquals(2, sibs.size());\n        assertEquals(\"Two\", sibs.get(0).text());\n        assertEquals(\"Three\", sibs.get(1).text());\n    }\n\n    @Test public void mixCombinatorGroup() {\n        String h = \"<div class=foo><ol><li>One<li>Two<li>Three</ol></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\".foo > ol, ol > li + li\");\n\n        assertEquals(3, els.size());\n        assertEquals(\"ol\", els.get(0).tagName());\n        assertEquals(\"Two\", els.get(1).text());\n        assertEquals(\"Three\", els.get(2).text());\n    }\n\n    @Test public void generalSiblings() {\n        String h = \"<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\"#1 ~ #3\");\n        assertEquals(1, els.size());\n        assertEquals(\"Three\", els.first().text());\n    }\n\n    @Test public void elelemtDescendantSkipsNodes() {\n        String h = \"<div class=foo> <!-- foo --> <ol> <li>One<li><!-- bar --> Two<li>Three</ol></div>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\".foo > ol, ol > li + li\");\n\n        assertEquals(3, els.size());\n        assertEquals(\"ol\", els.get(0).tagName());\n        assertEquals(\"Two\", els.get(1).text());\n        assertEquals(\"Three\", els.get(2).text());\n    }\n\n    @Test public void siblingsSkipNodes() {\n        String h = \"<ol><li id=1><!-- foo -->One<li id=2><!-- foo -->Two<li id=3><!-- foo -->Three</ol>\";\n        Document doc = Jsoup.parse(h);\n        Elements els = doc.select(\"#1 ~ #3\");\n        assertEquals(1, els.size());\n        assertEquals(\"Three\", els.first().text());\n    }\n\n    // for http://github.com/jhy/jsoup/issues#issue/10\n    @Test public void testCharactersInIdAndClass() {\n        // using CSS spec for identifiers (id and class): a-z0-9, -, _. NOT . (which is OK in html spec, but not css)\n        String h = \"<div><p id='a1-foo_bar'>One</p><p class='b2-qux_bif'>Two</p></div>\";\n        Document doc = Jsoup.parse(h);\n\n        Element el1 = doc.getElementById(\"a1-foo_bar\");\n        assertEquals(\"One\", el1.text());\n        Element el2 = doc.getElementsByClass(\"b2-qux_bif\").first();\n        assertEquals(\"Two\", el2.text());\n\n        Element el3 = doc.select(\"#a1-foo_bar\").first();\n        assertEquals(\"One\", el3.text());\n        Element el4 = doc.select(\".b2-qux_bif\").first();\n        assertEquals(\"Two\", el4.text());\n    }\n\n    // for http://github.com/jhy/jsoup/issues#issue/13\n    @Test public void testSupportsLeadingCombinator() {\n        String h = \"<div><p><span>One</span><span>Two</span></p></div>\";\n        Document doc = Jsoup.parse(h);\n\n        Element p = doc.select(\"div > p\").first();\n        Elements spans = p.select(\"> span\");\n        assertEquals(2, spans.size());\n        assertEquals(\"One\", spans.first().text());\n\n        // make sure doesn't get nested\n        h = \"<div id=1><div id=2><div id=3></div></div></div>\";\n        doc = Jsoup.parse(h);\n        Element div = doc.select(\"div\").select(\" > div\").first();\n        assertEquals(\"2\", div.id());\n    }\n\n    @Test public void testPseudoLessThan() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>\");\n        Elements ps = doc.select(\"div p:lt(2)\");\n        assertEquals(3, ps.size());\n        assertEquals(\"One\", ps.get(0).text());\n        assertEquals(\"Two\", ps.get(1).text());\n        assertEquals(\"Four\", ps.get(2).text());\n    }\n\n    @Test public void testPseudoGreaterThan() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</p></div><div><p>Four</p>\");\n        Elements ps = doc.select(\"div p:gt(0)\");\n        assertEquals(2, ps.size());\n        assertEquals(\"Two\", ps.get(0).text());\n        assertEquals(\"Three\", ps.get(1).text());\n    }\n\n    @Test public void testPseudoEquals() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>\");\n        Elements ps = doc.select(\"div p:eq(0)\");\n        assertEquals(2, ps.size());\n        assertEquals(\"One\", ps.get(0).text());\n        assertEquals(\"Four\", ps.get(1).text());\n\n        Elements ps2 = doc.select(\"div:eq(0) p:eq(0)\");\n        assertEquals(1, ps2.size());\n        assertEquals(\"One\", ps2.get(0).text());\n        assertEquals(\"p\", ps2.get(0).tagName());\n    }\n\n    @Test public void testPseudoBetween() {\n        Document doc = Jsoup.parse(\"<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>\");\n        Elements ps = doc.select(\"div p:gt(0):lt(2)\");\n        assertEquals(1, ps.size());\n        assertEquals(\"Two\", ps.get(0).text());\n    }\n\n    @Test public void testPseudoCombined() {\n        Document doc = Jsoup.parse(\"<div class='foo'><p>One</p><p>Two</p></div><div><p>Three</p><p>Four</p></div>\");\n        Elements ps = doc.select(\"div.foo p:gt(0)\");\n        assertEquals(1, ps.size());\n        assertEquals(\"Two\", ps.get(0).text());\n    }\n\n    @Test public void testPseudoHas() {\n        Document doc = Jsoup.parse(\"<div id=0><p><span>Hello</span></p></div> <div id=1><span class=foo>There</span></div> <div id=2><p>Not</p></div>\");\n\n        Elements divs1 = doc.select(\"div:has(span)\");\n        assertEquals(2, divs1.size());\n        assertEquals(\"0\", divs1.get(0).id());\n        assertEquals(\"1\", divs1.get(1).id());\n\n        Elements divs2 = doc.select(\"div:has([class])\");\n        assertEquals(1, divs2.size());\n        assertEquals(\"1\", divs2.get(0).id());\n\n        Elements divs3 = doc.select(\"div:has(span, p)\");\n        assertEquals(3, divs3.size());\n        assertEquals(\"0\", divs3.get(0).id());\n        assertEquals(\"1\", divs3.get(1).id());\n        assertEquals(\"2\", divs3.get(2).id());\n\n        Elements els1 = doc.body().select(\":has(p)\");\n        assertEquals(3, els1.size()); // body, div, div\n        assertEquals(\"body\", els1.first().tagName());\n        assertEquals(\"0\", els1.get(1).id());\n        assertEquals(\"2\", els1.get(2).id());\n\n        Elements els2 = doc.body().select(\":has(> span)\");\n        assertEquals(2,els2.size()); // p, div\n        assertEquals(\"p\",els2.first().tagName());\n        assertEquals(\"1\", els2.get(1).id());\n    }\n\n    @Test public void testNestedHas() {\n        Document doc = Jsoup.parse(\"<div><p><span>One</span></p></div> <div><p>Two</p></div>\");\n        Elements divs = doc.select(\"div:has(p:has(span))\");\n        assertEquals(1, divs.size());\n        assertEquals(\"One\", divs.first().text());\n\n        // test matches in has\n        divs = doc.select(\"div:has(p:matches((?i)two))\");\n        assertEquals(1, divs.size());\n        assertEquals(\"div\", divs.first().tagName());\n        assertEquals(\"Two\", divs.first().text());\n\n        // test contains in has\n        divs = doc.select(\"div:has(p:contains(two))\");\n        assertEquals(1, divs.size());\n        assertEquals(\"div\", divs.first().tagName());\n        assertEquals(\"Two\", divs.first().text());\n    }\n\n    @Test public void testHasSibling() {\n        // https://github.com/jhy/jsoup/issues/2137\n        Document doc = Jsoup.parse(\"<h1 id=1>One</h1> <h2>Two</h2> <h1>Three</h1>\");\n        Elements els = doc.select(\"h1:has(+h2)\");\n        assertSelectedIds(els, \"1\");\n\n        els = doc.select(\"h1:has(~h1)\");\n        assertSelectedIds(els, \"1\");\n\n        // nested with sibling\n        doc = Jsoup.parse(\"<div id=1><p><i>One</i><i>Two</p><p><i>Three</p></div> <div><p><i>Four</div>\");\n        els = doc.select(\"div:has(p:has(i:has(~i)))\");\n        assertSelectedIds(els, \"1\");\n    }\n\n    @MultiLocaleTest\n    public void testPseudoContains(Locale locale) {\n        Locale.setDefault(locale);\n\n        Document doc = Jsoup.parse(\"<div><p>The Rain.</p> <p class=light>The <i>RAIN</i>.</p> <p>Rain, the.</p></div>\");\n\n        Elements ps1 = doc.select(\"p:contains(Rain)\");\n        assertEquals(3, ps1.size());\n\n        Elements ps2 = doc.select(\"p:contains(the rain)\");\n        assertEquals(2, ps2.size());\n        assertEquals(\"The Rain.\", ps2.first().html());\n        assertEquals(\"The <i>RAIN</i>.\", ps2.last().html());\n\n        Elements ps3 = doc.select(\"p:contains(the Rain):has(i)\");\n        assertEquals(1, ps3.size());\n        assertEquals(\"light\", ps3.first().className());\n\n        Elements ps4 = doc.select(\".light:contains(rain)\");\n        assertEquals(1, ps4.size());\n        assertEquals(\"light\", ps3.first().className());\n\n        Elements ps5 = doc.select(\":contains(rain)\");\n        assertEquals(8, ps5.size()); // html, body, div,...\n\n        Elements ps6 = doc.select(\":contains(RAIN)\");\n        assertEquals(8, ps6.size());\n    }\n\n    @Test public void testPsuedoContainsWithParentheses() {\n        Document doc = Jsoup.parse(\"<div><p id=1>This (is good)</p><p id=2>This is bad)</p>\");\n\n        Elements ps1 = doc.select(\"p:contains(this (is good))\");\n        assertEquals(1, ps1.size());\n        assertEquals(\"1\", ps1.first().id());\n\n        Elements ps2 = doc.select(\"p:contains(this is bad\\\\))\");\n        assertEquals(1, ps2.size());\n        assertEquals(\"2\", ps2.first().id());\n    }\n\n    @Test void containsWholeText() {\n        Document doc = Jsoup.parse(\"<div><p> jsoup\\n The <i>HTML</i> Parser</p><p>jsoup The HTML Parser</div>\");\n        Elements ps = doc.select(\"p\");\n\n        Elements es1 = doc.select(\"p:containsWholeText( jsoup\\n The HTML Parser)\");\n        Elements es2 = doc.select(\"p:containsWholeText(jsoup The HTML Parser)\");\n        assertEquals(1, es1.size());\n        assertEquals(1, es2.size());\n        assertEquals(ps.get(0), es1.first());\n        assertEquals(ps.get(1), es2.first());\n\n        assertEquals(0, doc.select(\"div:containsWholeText(jsoup the html parser)\").size());\n        assertEquals(0, doc.select(\"div:containsWholeText(jsoup\\n the html parser)\").size());\n\n        doc = Jsoup.parse(\"<div><p></p><p> </p><p>.  </p>\");\n        Elements blanks = doc.select(\"p:containsWholeText(  )\");\n        assertEquals(1, blanks.size());\n        assertEquals(\".  \", blanks.first().wholeText());\n    }\n\n    @Test void containsWholeOwnText() {\n        Document doc = Jsoup.parse(\"<div><p> jsoup\\n The <i>HTML</i> Parser</p><p>jsoup The HTML Parser<br></div>\");\n        Elements ps = doc.select(\"p\");\n\n        Elements es1 = doc.select(\"p:containsWholeOwnText( jsoup\\n The  Parser)\");\n        Elements es2 = doc.select(\"p:containsWholeOwnText(jsoup The HTML Parser\\n)\");\n        assertEquals(1, es1.size());\n        assertEquals(1, es2.size());\n        assertEquals(ps.get(0), es1.first());\n        assertEquals(ps.get(1), es2.first());\n\n        assertEquals(0, doc.select(\"div:containsWholeOwnText(jsoup the html parser)\").size());\n        assertEquals(0, doc.select(\"div:containsWholeOwnText(jsoup\\n the  parser)\").size());\n\n        doc = Jsoup.parse(\"<div><p></p><p> </p><p>.  </p>\");\n        Elements blanks = doc.select(\"p:containsWholeOwnText(  )\");\n        assertEquals(1, blanks.size());\n        assertEquals(\".  \", blanks.first().wholeText());\n    }\n\n    @MultiLocaleTest\n    public void containsOwn(Locale locale) {\n        Locale.setDefault(locale);\n\n        Document doc = Jsoup.parse(\"<p id=1>Hello <b>there</b> igor</p>\");\n        Elements ps = doc.select(\"p:containsOwn(Hello IGOR)\");\n        assertEquals(1, ps.size());\n        assertEquals(\"1\", ps.first().id());\n\n        assertEquals(0, doc.select(\"p:containsOwn(there)\").size());\n\n        Document doc2 = Jsoup.parse(\"<p>Hello <b>there</b> IGOR</p>\");\n        assertEquals(1, doc2.select(\"p:containsOwn(igor)\").size());\n\n    }\n\n    @Test public void testMatches() {\n        Document doc = Jsoup.parse(\"<p id=1>The <i>Rain</i></p> <p id=2>There are 99 bottles.</p> <p id=3>Harder (this)</p> <p id=4>Rain</p>\");\n\n        Elements p1 = doc.select(\"p:matches(The rain)\"); // no match, case sensitive\n        assertEquals(0, p1.size());\n\n        Elements p2 = doc.select(\"p:matches((?i)the rain)\"); // case insense. should include root, html, body\n        assertEquals(1, p2.size());\n        assertEquals(\"1\", p2.first().id());\n\n        Elements p4 = doc.select(\"p:matches((?i)^rain$)\"); // bounding\n        assertEquals(1, p4.size());\n        assertEquals(\"4\", p4.first().id());\n\n        Elements p5 = doc.select(\"p:matches(\\\\d+)\");\n        assertEquals(1, p5.size());\n        assertEquals(\"2\", p5.first().id());\n\n        Elements p6 = doc.select(\"p:matches(\\\\w+\\\\s+\\\\(\\\\w+\\\\))\"); // test bracket matching\n        assertEquals(1, p6.size());\n        assertEquals(\"3\", p6.first().id());\n\n        Elements p7 = doc.select(\"p:matches((?i)the):has(i)\"); // multi\n        assertEquals(1, p7.size());\n        assertEquals(\"1\", p7.first().id());\n    }\n\n    @Test public void matchesOwn() {\n        Document doc = Jsoup.parse(\"<p id=1>Hello <b>there</b> now</p>\");\n\n        Elements p1 = doc.select(\"p:matchesOwn((?i)hello now)\");\n        assertEquals(1, p1.size());\n        assertEquals(\"1\", p1.first().id());\n\n        assertEquals(0, doc.select(\"p:matchesOwn(there)\").size());\n    }\n\n    @Test public void matchesWholeText() {\n        Document doc = Jsoup.parse(\"<p id=1>Hello <b>there</b>\\n now</p><p id=2> </p><p id=3></p>\");\n\n        Elements p1 = doc.select(\"p:matchesWholeText((?i)hello there\\n now)\");\n        assertEquals(1, p1.size());\n        assertEquals(\"1\", p1.first().id());\n\n        assertEquals(1, doc.select(\"p:matchesWholeText(there\\n now)\").size());\n        assertEquals(0, doc.select(\"p:matchesWholeText(There\\n now)\").size());\n\n        Elements p2 = doc.select(\"p:matchesWholeText(^\\\\s+$)\");\n        assertEquals(1, p2.size());\n        assertEquals(\"2\", p2.first().id());\n\n        Elements p3 = doc.select(\"p:matchesWholeText(^$)\");\n        assertEquals(1, p3.size());\n        assertEquals(\"3\", p3.first().id());\n    }\n\n    @Test public void matchesWholeOwnText() {\n        Document doc = Jsoup.parse(\"<p id=1>Hello <b>there</b>\\n now</p><p id=2> </p><p id=3><i>Text</i></p>\");\n\n        Elements p1 = doc.select(\"p:matchesWholeOwnText((?i)hello \\n now)\");\n        assertEquals(1, p1.size());\n        assertEquals(\"1\", p1.first().id());\n\n        assertEquals(0, doc.select(\"p:matchesWholeOwnText(there\\n now)\").size());\n\n        Elements p2 = doc.select(\"p:matchesWholeOwnText(^\\\\s+$)\");\n        assertEquals(1, p2.size());\n        assertEquals(\"2\", p2.first().id());\n\n        Elements p3 = doc.select(\"p:matchesWholeOwnText(^$)\");\n        assertEquals(1, p3.size());\n        assertEquals(\"3\", p3.first().id());\n    }\n\n    @Test public void testRelaxedTags() {\n        Document doc = Jsoup.parse(\"<abc_def id=1>Hello</abc_def> <abc-def id=2>There</abc-def>\");\n\n        Elements el1 = doc.select(\"abc_def\");\n        assertEquals(1, el1.size());\n        assertEquals(\"1\", el1.first().id());\n\n        Elements el2 = doc.select(\"abc-def\");\n        assertEquals(1, el2.size());\n        assertEquals(\"2\", el2.first().id());\n    }\n\n    @Test public void notParas() {\n        Document doc = Jsoup.parse(\"<p id=1>One</p> <p>Two</p> <p><span>Three</span></p>\");\n\n        Elements el1 = doc.select(\"p:not([id=1])\");\n        assertEquals(2, el1.size());\n        assertEquals(\"Two\", el1.first().text());\n        assertEquals(\"Three\", el1.last().text());\n\n        Elements el2 = doc.select(\"p:not(:has(span))\");\n        assertEquals(2, el2.size());\n        assertEquals(\"One\", el2.first().text());\n        assertEquals(\"Two\", el2.last().text());\n    }\n\n    @Test public void notAll() {\n        Document doc = Jsoup.parse(\"<p>Two</p> <p><span>Three</span></p>\");\n\n        Elements el1 = doc.body().select(\":not(p)\"); // should just be the span\n        assertEquals(2, el1.size());\n        assertEquals(\"body\", el1.first().tagName());\n        assertEquals(\"span\", el1.last().tagName());\n    }\n\n    @Test public void notClass() {\n        Document doc = Jsoup.parse(\"<div class=left>One</div><div class=right id=1><p>Two</p></div>\");\n\n        Elements el1 = doc.select(\"div:not(.left)\");\n        assertEquals(1, el1.size());\n        assertEquals(\"1\", el1.first().id());\n    }\n\n    @Test public void handlesCommasInSelector() {\n        Document doc = Jsoup.parse(\"<p name='1,2'>One</p><div>Two</div><ol><li>123</li><li>Text</li></ol>\");\n\n        Elements ps = doc.select(\"[name=1,2]\");\n        assertEquals(1, ps.size());\n\n        Elements containers = doc.select(\"div, li:matches([0-9,]+)\");\n        assertEquals(2, containers.size());\n        assertEquals(\"div\", containers.get(0).tagName());\n        assertEquals(\"li\", containers.get(1).tagName());\n        assertEquals(\"123\", containers.get(1).text());\n    }\n\n    @Test public void selectSupplementaryCharacter() {\n        String s = new String(Character.toChars(135361));\n        Document doc = Jsoup.parse(\"<div k\" + s + \"='\" + s + \"'>^\" + s +\"$/div>\");\n        assertEquals(\"div\", doc.select(\"div[k\" + s + \"]\").first().tagName());\n        assertEquals(\"div\", doc.select(\"div:containsOwn(\" + s + \")\").first().tagName());\n    }\n\n    @Test\n    public void selectClassWithSpace() {\n        final String html = \"<div class=\\\"value\\\">class without space</div>\\n\"\n                          + \"<div class=\\\"value \\\">class with space</div>\";\n\n        Document doc = Jsoup.parse(html);\n\n        Elements found = doc.select(\"div[class=value ]\");\n        assertEquals(1, found.size());\n        assertEquals(\"class with space\", found.get(0).text());\n\n        found = doc.select(\"div[class=\\\"value \\\"]\");\n        assertEquals(1, found.size());\n        assertEquals(\"class with space\", found.get(0).text());\n\n        found = doc.select(\"div[class=\\\"value\\\\ \\\"]\");\n        assertEquals(0, found.size());\n    }\n\n    @Test public void selectSameElements() {\n        final String html = \"<div>one</div><div>one</div>\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"div\");\n        assertEquals(2, els.size());\n\n        Elements subSelect = els.select(\":contains(one)\");\n        assertEquals(2, subSelect.size());\n    }\n\n    @Test public void attributeWithBrackets() {\n        String html = \"<div data='End]'>One</div> <div data='[Another)]]'>Two</div>\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"One\", doc.select(\"div[data='End]']\").first().text());\n        assertEquals(\"Two\", doc.select(\"div[data='[Another)]]']\").first().text());\n        assertEquals(\"One\", doc.select(\"div[data=\\\"End]\\\"]\").first().text());\n        assertEquals(\"Two\", doc.select(\"div[data=\\\"[Another)]]\\\"]\").first().text());\n    }\n\n    @MultiLocaleTest\n    public void containsData(Locale locale) {\n        Locale.setDefault(locale);\n\n        String html = \"<p>function</p><script>FUNCTION</script><style>item</style><span><!-- comments --></span>\";\n        Document doc = Jsoup.parse(html);\n        Element body = doc.body();\n\n        Elements dataEls1 = body.select(\":containsData(function)\");\n        Elements dataEls2 = body.select(\"script:containsData(function)\");\n        Elements dataEls3 = body.select(\"span:containsData(comments)\");\n        Elements dataEls4 = body.select(\":containsData(o)\");\n        Elements dataEls5 = body.select(\"style:containsData(ITEM)\");\n\n        assertEquals(2, dataEls1.size()); // body and script\n        assertEquals(1, dataEls2.size());\n        assertEquals(dataEls1.last(), dataEls2.first());\n        assertEquals(\"<script>FUNCTION</script>\", dataEls2.outerHtml());\n        assertEquals(1, dataEls3.size());\n        assertEquals(\"span\", dataEls3.first().tagName());\n        assertEquals(3, dataEls4.size());\n        assertEquals(\"body\", dataEls4.first().tagName());\n        assertEquals(\"script\", dataEls4.get(1).tagName());\n        assertEquals(\"span\", dataEls4.get(2).tagName());\n        assertEquals(1, dataEls5.size());\n    }\n\n    @Test public void containsWithQuote() {\n        String html = \"<p>One'One</p><p>One'Two</p>\";\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(\"p:contains(One\\\\'One)\");\n        assertEquals(1, els.size());\n        assertEquals(\"One'One\", els.text());\n    }\n\n    @Test public void selectFirst() {\n        String html = \"<p>One<p>Two<p>Three\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"One\", doc.selectFirst(\"p\").text());\n    }\n\n    @Test public void selectFirstWithAnd() {\n        String html = \"<p>One<p class=foo>Two<p>Three\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"Two\", doc.selectFirst(\"p.foo\").text());\n    }\n\n    @Test public void selectFirstWithOr() {\n        String html = \"<p>One<p>Two<p>Three<div>Four\";\n        Document doc = Jsoup.parse(html);\n        assertEquals(\"One\", doc.selectFirst(\"p, div\").text());\n    }\n\n    @Test public void matchText() {\n        String html = \"<p>One<br>Two</p>\";\n        Document doc = Jsoup.parse(html);\n        doc.outputSettings().prettyPrint(false);\n        String origHtml = doc.html();\n\n        Elements one = doc.select(\"p:matchText:first-child\");\n        assertEquals(\"One\", one.first().text());\n\n        Elements two = doc.select(\"p:matchText:last-child\");\n        assertEquals(\"Two\", two.first().text());\n\n        assertEquals(origHtml, doc.html());\n\n        assertEquals(\"Two\", doc.select(\"p:matchText + br + *\").text());\n    }\n\n    @Test public void nthLastChildWithNoParent() {\n        Element el = new Element(\"p\").text(\"Orphan\");\n        Elements els = el.select(\"p:nth-last-child(1)\");\n        assertEquals(0, els.size());\n    }\n\n    @Test public void splitOnBr() {\n        String html = \"<div><p>One<br>Two<br>Three</p></div>\";\n        Document doc = Jsoup.parse(html);\n\n        Elements els = doc.select(\"p:matchText\");\n        assertEquals(3, els.size());\n        assertEquals(\"One\", els.get(0).text());\n        assertEquals(\"Two\", els.get(1).text());\n        assertEquals(\"Three\", els.get(2).toString());\n    }\n\n    @Test public void matchTextAttributes() {\n        Document doc = Jsoup.parse(\"<div><p class=one>One<br>Two<p class=two>Three<br>Four\");\n        Elements els = doc.select(\"p.two:matchText:last-child\");\n\n        assertEquals(1, els.size());\n        assertEquals(\"Four\", els.text());\n    }\n\n    @Test public void findBetweenSpan() {\n        Document doc = Jsoup.parse(\"<p><span>One</span> Two <span>Three</span>\");\n        Elements els = doc.select(\"span ~ p:matchText\"); // the Two becomes its own p, sibling of the span\n        // todo - think this should really be 'p:matchText span ~ p'. The :matchText should behave as a modifier to expand the nodes.\n\n        assertEquals(1, els.size());\n        assertEquals(\"Two\", els.text());\n    }\n\n    @Test public void startsWithBeginsWithSpace() {\n        Document doc = Jsoup.parse(\"<small><a href=\\\" mailto:abc@def.net\\\">(abc@def.net)</a></small>\");\n        Elements els = doc.select(\"a[href^=' mailto']\");\n\n        assertEquals(1, els.size());\n    }\n\n    @Test public void endsWithEndsWithSpaces() {\n        Document doc = Jsoup.parse(\"<small><a href=\\\" mailto:abc@def.net \\\">(abc@def.net)</a></small>\");\n        Elements els = doc.select(\"a[href$='.net ']\");\n\n        assertEquals(1, els.size());\n    }\n\n    // https://github.com/jhy/jsoup/issues/1257\n    private final String mixedCase =\n        \"<html xmlns:n=\\\"urn:ns\\\"><n:mixedCase>text</n:mixedCase></html>\";\n    private final String lowercase =\n        \"<html xmlns:n=\\\"urn:ns\\\"><n:lowercase>text</n:lowercase></html>\";\n\n    @Test\n    public void html_mixed_case_simple_name() {\n        Document doc = Jsoup.parse(mixedCase, \"\", Parser.htmlParser());\n        assertEquals(0, doc.select(\"mixedCase\").size());\n    }\n\n    @Test\n    public void html_mixed_case_wildcard_name() {\n        Document doc = Jsoup.parse(mixedCase, \"\", Parser.htmlParser());\n        assertEquals(1, doc.select(\"*|mixedCase\").size());\n    }\n\n    @Test\n    public void html_lowercase_simple_name() {\n        Document doc = Jsoup.parse(lowercase, \"\", Parser.htmlParser());\n        assertEquals(0, doc.select(\"lowercase\").size());\n    }\n\n    @Test\n    public void html_lowercase_wildcard_name() {\n        Document doc = Jsoup.parse(lowercase, \"\", Parser.htmlParser());\n        assertEquals(1, doc.select(\"*|lowercase\").size());\n    }\n\n    @Test\n    public void xml_mixed_case_simple_name() {\n        Document doc = Jsoup.parse(mixedCase, \"\", Parser.xmlParser());\n        assertEquals(0, doc.select(\"mixedCase\").size());\n    }\n\n    @Test\n    public void xml_mixed_case_wildcard_name() {\n        Document doc = Jsoup.parse(mixedCase, \"\", Parser.xmlParser());\n        assertEquals(1, doc.select(\"*|mixedCase\").size());\n    }\n\n    @Test\n    public void xml_lowercase_simple_name() {\n        Document doc = Jsoup.parse(lowercase, \"\", Parser.xmlParser());\n        assertEquals(0, doc.select(\"lowercase\").size());\n    }\n\n    @Test\n    public void xml_lowercase_wildcard_name() {\n        Document doc = Jsoup.parse(lowercase, \"\", Parser.xmlParser());\n        assertEquals(1, doc.select(\"*|lowercase\").size());\n    }\n\n    @Test\n    public void trimSelector() {\n        // https://github.com/jhy/jsoup/issues/1274\n        Document doc = Jsoup.parse(\"<p><span>Hello\");\n        Elements els = doc.select(\" p span \");\n        assertEquals(1, els.size());\n        assertEquals(\"Hello\", els.first().text());\n    }\n\n    @Test\n    public void xmlWildcardNamespaceTest() {\n        // https://github.com/jhy/jsoup/issues/1208\n        Document doc = Jsoup.parse(\"<ns1:MyXmlTag>1111</ns1:MyXmlTag><ns2:MyXmlTag>2222</ns2:MyXmlTag>\", \"\", Parser.xmlParser());\n        Elements select = doc.select(\"*|MyXmlTag\");\n        assertEquals(2, select.size());\n        assertEquals(\"1111\", select.get(0).text());\n        assertEquals(\"2222\", select.get(1).text());\n    }\n\n    @Test\n    public void childElements() {\n        // https://github.com/jhy/jsoup/issues/1292\n        String html = \"<body><span id=1>One <span id=2>Two</span></span></body>\";\n        Document doc = Jsoup.parse(html);\n\n        Element outer = doc.selectFirst(\"span\");\n        Element span = outer.selectFirst(\"span\");\n        Element inner = outer.selectFirst(\"* span\");\n\n        assertEquals(\"1\", outer.id());\n        assertEquals(\"1\", span.id());\n        assertEquals(\"2\", inner.id());\n        assertEquals(outer, span);\n        assertNotEquals(outer, inner);\n    }\n\n    @Test\n    public void selectFirstLevelChildrenOnly() {\n        // testcase for https://github.com/jhy/jsoup/issues/984\n        String html = \"<div><span>One <span>Two</span></span> <span>Three <span>Four</span></span>\";\n        Document doc = Jsoup.parse(html);\n\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n\n        // want to select One and Three only - the first level children\n        Elements spans = div.select(\":root > span\");\n        assertEquals(2, spans.size());\n        assertEquals(\"One Two\", spans.get(0).text());\n        assertEquals(\"Three Four\", spans.get(1).text());\n    }\n\n    @Test\n    public void wildcardNamespaceMatchesNoNamespace() {\n        // https://github.com/jhy/jsoup/issues/1565\n        String xml = \"<package><meta>One</meta><opf:meta>Two</opf:meta></package>\";\n        Document doc = Jsoup.parse(xml, \"\", Parser.xmlParser());\n\n        Elements metaEls = doc.select(\"meta\");\n        assertEquals(1, metaEls.size());\n        assertEquals(\"One\", metaEls.get(0).text());\n\n        Elements nsEls = doc.select(\"*|meta\");\n        assertEquals(2, nsEls.size());\n        assertEquals(\"One\", nsEls.get(0).text());\n        assertEquals(\"Two\", nsEls.get(1).text());\n    }\n\n    @Test void containsTextQueryIsNormalized() {\n        Document doc = Jsoup.parse(\"<p><p id=1>Hello  there now<em>!</em>\");\n        Elements a = doc.select(\"p:contains(Hello   there  now!)\");\n        Elements b = doc.select(\":containsOwn(hello   there  now)\");\n        Elements c = doc.select(\"p:contains(Hello there now)\");\n        Elements d = doc.select(\":containsOwn(hello There now)\");\n        Elements e = doc.select(\"p:contains(HelloThereNow)\");\n\n        assertEquals(1, a.size());\n        assertEquals(a, b);\n        assertEquals(a, c);\n        assertEquals(a, d);\n        assertEquals(0, e.size());\n        assertNotEquals(a, e);\n    }\n\n    @Test public void selectorExceptionNotStringFormatException() {\n        Selector.SelectorParseException ex = new Selector.SelectorParseException(\"%&\");\n        assertEquals(\"%&\", ex.getMessage());\n    }\n\n    @Test public void evaluatorMemosAreReset() {\n        Evaluator eval = QueryParser.parse(\"p ~ p\");\n        CombiningEvaluator.And andEval = (CombiningEvaluator.And) eval;\n        StructuralEvaluator.PreviousSibling prevEval = (StructuralEvaluator.PreviousSibling) andEval.evaluators.get(0);\n        Map<Node, Map<Node, Boolean>> map = prevEval.threadMemo.get();\n        assertEquals(0, map.size()); // no memo yet\n\n        Document doc1 = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n        Document doc2 = Jsoup.parse(\"<p>One2<p>Two2<p>Three2\");\n\n        Elements s1 = doc1.select(eval);\n        assertEquals(2, s1.size());\n        assertEquals(\"Two\", s1.first().text());\n        Elements s2 = doc2.select(eval);\n        assertEquals(2, s2.size());\n        assertEquals(\"Two2\", s2.first().text());\n\n        assertEquals(0, map.size()); // reset after collect\n    }\n\n    @Test public void blankTextNodesAreConsideredEmpty() {\n        // https://github.com/jhy/jsoup/issues/1976\n        String html = \"<li id=1>\\n </li><li id=2></li><li id=3> </li><li id=4>One</li><li id=5><span></li>\";\n        Document doc = Jsoup.parse(html);\n        Elements empty = doc.select(\"li:empty\");\n        Elements notEmpty = doc.select(\"li:not(:empty)\");\n\n        assertSelectedIds(empty, \"1\", \"2\", \"3\");\n        assertSelectedIds(notEmpty, \"4\", \"5\");\n    }\n\n    @Test\n    public void emptyPseudo() {\n        // https://github.com/jhy/jsoup/issues/2130\n        String html = \"<ul>\" +\n            \"  <li id='1'>\\n </li>\" + // Blank text node only\n            \"  <li id='2'></li>\" + // No nodes\n            \"  <li id='3'><!-- foo --></li>\" + // Comment node only\n            \"  <li id='4'>One</li>\" + // Text node with content\n            \"  <li id='5'><span></span></li>\" + // Element node\n            \"  <li id='6'>\\n <span></span></li>\" + // Blank text node followed by an element\n            \"  <li id='7'><!-- foo --><i></i></li>\" + // Comment node with element\n            \"</ul>\";\n        Document doc = Jsoup.parse(html);\n        Elements empty = doc.select(\"li:empty\");\n        assertSelectedIds(empty, \"1\", \"2\", \"3\");\n\n        Elements notEmpty = doc.select(\"li:not(:empty)\");\n        assertSelectedIds(notEmpty, \"4\", \"5\", \"6\", \"7\");\n    }\n\n    @Test public void parentFromSpecifiedDescender() {\n        // https://github.com/jhy/jsoup/issues/2018\n        String html = \"<ul id=outer><li>Foo</li><li>Bar <ul id=inner><li>Baz</li><li>Qux</li></ul> </li></ul>\";\n        Document doc = Jsoup.parse(html);\n\n        Element ul = doc.expectFirst(\"#outer\");\n        assertEquals(2, ul.childrenSize());\n\n        Element li1 = ul.expectFirst(\"> li:nth-child(1)\");\n        assertEquals(\"Foo\", li1.ownText());\n        assertTrue(li1.select(\"ul\").isEmpty());\n\n        Element li2 = ul.expectFirst(\"> li:nth-child(2)\");\n        assertEquals(\"Bar\", li2.ownText());\n\n        // And now for the bug - li2 select was not restricted to the li2 context\n        Elements innerLis = li2.select(\"ul > li\");\n        assertSelectedOwnText(innerLis, \"Baz\", \"Qux\");\n\n        // Confirm that parent selector (\" \") works same as immediate parent (\">\");\n        Elements innerLisFromParent = li2.select(\"ul li\");\n        assertEquals(innerLis, innerLisFromParent);\n    }\n\n    @Test public void rootImmediateParentSubquery() {\n        // a combinator at the start of the query is applied to the Root selector. i.e. \"> p\" matches a P immediately parented\n        // by the Root (which is <html> for a top level query, or the context element in :has)\n        // in the sub query, the combinator was dropped incorrectly\n        String html = \"<p id=0><span>A</p> <p id=1><b><i><span>B</p> <p id=2><i>C</p>\\n\";\n        Document doc = Jsoup.parse(html);\n\n        Elements els = doc.select(\"p:has(> span, > i)\"); // should match a p with an immediate span or i\n        assertSelectedIds(els, \"0\", \"2\");\n    }\n\n    @Test public void is() {\n        String html = \"<h1 id=1><p></p></h1> <section><h1 id=2></h1></section> <article><h2 id=3></h2></article> <h2 id=4><p></p></h2>\";\n        Document doc = Jsoup.parse(html);\n\n        assertSelectedIds(\n            doc.select(\":is(section, article) :is(h1, h2, h3)\"),\n            \"2\", \"3\");\n\n        assertSelectedIds(\n            doc.select(\":is(section, article) ~ :is(h1, h2, h3):has(p)\"),\n            \"4\");\n\n        assertSelectedIds(\n            doc.select(\":is(h1:has(p), h2:has(section), h3)\"),\n            \"1\");\n\n        assertSelectedIds(\n            doc.select(\":is(h1, h2, h3):has(p)\"),\n            \"1\", \"4\");\n\n        String query = \"div :is(h1, h2)\";\n        Evaluator parse = QueryParser.parse(query);\n        assertEquals(query, parse.toString());\n    }\n\n    @Test public void orAfterClass() {\n        // see also QueryParserTest#parsesOrAfterAttribute\n        // https://github.com/jhy/jsoup/issues/2073\n        Document doc = Jsoup.parse(\"<div id=parent><span class=child></span><span class=child></span><span class=child></span></div>\");\n        String q = \"#parent [class*=child], .some-other-selector .nested\";\n        assertEquals(\"(Or (And (AttributeWithValueContaining '[class*=child]')(Ancestor (Id '#parent')))(And (Class '.nested')(Ancestor (Class '.some-other-selector'))))\", sexpr(q));\n        Elements els = doc.select(q);\n        assertEquals(3, els.size());\n    }\n\n    @Test public void emptyAttributePrefix() {\n        // https://github.com/jhy/jsoup/issues/2079\n        // Discovered feature: [^] should find elements with any attribute (any prefix)\n        String html = \"<p one>One<p one two>Two<p>Three\";\n        Document doc = Jsoup.parse(html);\n\n        Elements els = doc.select(\"[^]\");\n        assertSelectedOwnText(els, \"One\", \"Two\");\n\n        Elements emptyAttr = doc.select(\"p:not([^])\");\n        assertSelectedOwnText(emptyAttr, \"Three\");\n    }\n\n    @Test public void anyAttribute() {\n        // https://github.com/jhy/jsoup/issues/2079\n        String html = \"<div id=1><p one>One<p one two>Two<p>Three\";\n        Document doc = Jsoup.parse(html);\n\n        Elements els = doc.select(\"p[*]\");\n        assertSelectedOwnText(els, \"One\", \"Two\");\n\n        Elements emptyAttr = doc.select(\"p:not([*])\");\n        assertSelectedOwnText(emptyAttr, \"Three\");\n    }\n\n    @Test void divHasSpanPreceding() {\n        // https://github.com/jhy/jsoup/issues/2187\n        String html = \"<div><span>abc</span><a>def</a></div>\";\n        String q = \"div:has(span + a)\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(q);\n        assertEquals(1, els.size());\n        assertEquals(\"div\", els.first().normalName());\n    }\n\n    @Test void divHasDivPreceding() {\n        // https://github.com/jhy/jsoup/issues/2131\n        String html = \"<div id=1>\\n\" +\n            \"<div 1><span>hello</span></div>\\n\" +\n            \"<div 2><span>there</span></div>\\n\" +\n            \"\\n\" +\n            \"</div>\";\n\n        String q = \"div:has(>div + div)\";\n\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.select(q);\n        assertEquals(1, els.size());\n        assertEquals(\"div\", els.first().normalName());\n        assertEquals(\"1\", els.first().id());\n    }\n\n    @Test void nestedMultiHas() {\n        // https://github.com/jhy/jsoup/issues/2131\n        String html =\n            \"<html>\" +\n                \"<head></head>\" +\n                \"<body>\" +\n                \"<div id=o>\" +\n                \"<div id=i1><span id=s1>hello</span></div>\" +\n                \"<div id=i2><span id=s2>world</span></div>\" +\n                \"</div>\" +\n                \"</body></html>\";\n        Document document = Jsoup.parse(html);\n\n        String q = \"div:has(> div:has(> span) + div:has(> span))\";\n        Elements els = document.select(q);\n        assertEquals(1, els.size());\n        assertEquals(\"o\", els.get(0).id());\n    }\n\n    @Test void negativeNthChild() {\n        // https://github.com/jhy/jsoup/issues/1147\n        String html = \"<p>1</p> <p>2</p> <p>3</p> <p>4</p>\";\n        Document doc = Jsoup.parse(html);\n\n        // Digitless\n        Elements pos = doc.select(\"p:nth-child(n+2)\");\n        assertSelectedOwnText(pos, \"2\", \"3\", \"4\");\n\n        Elements neg = doc.select(\"p:nth-child(-n+2)\");\n        assertSelectedOwnText(neg, \"1\", \"2\");\n\n        Elements combo = doc.select(\"p:nth-child(n+2):nth-child(-n+2)\");\n        assertSelectedOwnText(combo, \"2\");\n\n        // Digitful, 2n+2 or -1n+2\n        Elements pos2 = doc.select(\"p:nth-child(2n+2)\");\n        assertSelectedOwnText(pos2, \"2\", \"4\");\n\n        Elements neg2 = doc.select(\"p:nth-child(-1n+2)\");\n        assertSelectedOwnText(neg2, \"1\", \"2\");\n    }\n\n    // Tests that nested structural and combining evaluators get reset\n    private static class ResetTracker extends Evaluator {\n        boolean resetCalled = false;\n        @Override\n        public boolean matches(Element root, Element element) {\n            return true;\n        }\n\n        @Override\n        protected void reset() {\n            resetCalled = true;\n            super.reset();\n        }\n    }\n\n    @Test void notResetCascades() {\n        ResetTracker track = new ResetTracker();\n        StructuralEvaluator.Not structEval = new StructuralEvaluator.Not(track);\n\n        Document doc = Jsoup.parse(\"<div><p>Test</p></div>\");\n        Element p = doc.expectFirst(\"p\");\n        structEval.matches(doc, p);\n\n        assertFalse(structEval.threadMemo.get().isEmpty());\n        assertFalse(track.resetCalled);\n\n        structEval.reset();\n        assertTrue(structEval.threadMemo.get().isEmpty());\n        assertTrue(track.resetCalled);\n    }\n\n    @Test void testImmediateParentRunCascades() {\n        ResetTracker child = new ResetTracker();\n        ResetTracker parent = new ResetTracker();\n\n        StructuralEvaluator.ImmediateParentRun run = new StructuralEvaluator.ImmediateParentRun(child);\n        run.add(parent);\n\n        Document doc = Jsoup.parse(\"<div><p><span>Test</span></p></div>\");\n        Element span = doc.expectFirst(\"span\");\n        assertTrue(run.matches(doc, span));\n\n        run.reset();\n        assertTrue(child.resetCalled);\n        assertTrue(parent.resetCalled);\n    }\n\n    @Test\n    public void testAncestorChain() {\n        ResetTracker grandParent = new ResetTracker();\n        ResetTracker parent = new ResetTracker();\n        ResetTracker child = new ResetTracker();\n\n        StructuralEvaluator.Ancestor b_needs_a = new StructuralEvaluator.Ancestor(grandParent);\n        StructuralEvaluator.Ancestor c_needs_b = new StructuralEvaluator.Ancestor(parent);\n        CombiningEvaluator.And chain = new CombiningEvaluator.And(child, c_needs_b, b_needs_a);\n\n        Document doc = Jsoup.parse(\"<div class='A'><p class='B'><span class='C'>Test</span></p></div>\");\n        Element span = doc.expectFirst(\"span\");\n        assertTrue(chain.matches(doc, span), \"Should match span in correct ancestor chain\");\n\n        chain.reset();\n        assertTrue(grandParent.resetCalled);\n        assertTrue(parent.resetCalled);\n        assertTrue(child.resetCalled);\n        assertTrue(b_needs_a.threadMemo.get().isEmpty());\n        assertTrue(c_needs_b.threadMemo.get().isEmpty());\n    }\n\n    @Test void hexDigitUnescape() {\n        // tests the select component of https://github.com/jhy/jsoup/pull/2297, with per-spec escapes\n        // literal is: #\\30 \\%\\ Platform\\ Image\n        String html = \"<img id='0% Platform Image'>\";\n        String q = \"#\\\\30 \\\\%\\\\ Platform\\\\ Image\";\n\n        Document doc = Jsoup.parse(html);\n        Element img = doc.expectFirst(q);\n        assertEquals(\"img\", img.tagName());\n    }\n\n    @Test void escapeCssIdentifier() {\n        // thorough tests are in TokenQueue\n        assertEquals(\"-\\\\30 a\", Selector.escapeCssIdentifier(\"-0a\"));\n        assertEquals(\"a0b\", Selector.escapeCssIdentifier(\"a0b\"));\n    }\n\n    @Test void unescapeCssIdentifier() {\n        // thorough tests are in TokenQueue\n        assertEquals(\"-0a\", Selector.unescapeCssIdentifier(\"-\\\\30 a\"));\n        assertEquals(\"a0b\", Selector.unescapeCssIdentifier(\"a0b\"));\n    }\n\n    @Test void evaluatorOf() {\n        Evaluator eval = Selector.evaluatorOf(\"div > p\");\n        assertEquals(\"div > p\", eval.toString());\n    }\n\n    @Test void hasComment() {\n        Document doc = Jsoup.parse(\"<div id=1>One</div><div id=2>Two <!-- foo --></div>\");\n        Elements els = doc.select(\"div:has(::comment)\");\n        assertSelectedIds(els, \"2\");\n    }\n\n    @Test void hasCommentWithText() {\n        Document doc = Jsoup.parse(\"<div id=1>One <!-- qux bar --></div><div id=2>Two <!-- foo qux --></div>\");\n        Elements els = doc.select(\"div:has(::comment:contains(foo):contains(qux))\");\n        assertSelectedIds(els, \"2\");\n    }\n\n    @Test void descendantComment() {\n        Document doc = Jsoup.parse(\"<div id=1><div id=2><!-- comment2 --><div id=3><!-- comment3 --></div></div></div><div id=4><div id=5><div id=6>Not</div></div></div>\");\n\n        String q = \"div > div:has(::comment)\";\n        assertEquals(\"(ImmediateParentRun (Tag 'div')(And (Tag 'div')(Has (InstanceType '::comment'))))\", sexpr(q));\n        Elements els1 = doc.select(q);\n        assertSelectedIds(els1, \"2\", \"3\");\n\n        String q2 = \"div div:has(>::comment:contains(comment3))\";\n        assertEquals(\"(And (Ancestor (Tag 'div'))(And (Tag 'div')(Has (ImmediateParentRun (Root '>')(And (InstanceType '::comment')(ContainsValue ':contains(comment3)'))))))\", sexpr(q2));\n        Elements els2 = doc.select(q2);\n        assertSelectedIds(els2, \"3\");\n\n        String q3 = \"div:has(>::comment) div\";\n        assertEquals(\"(And (Tag 'div')(Ancestor (And (Tag 'div')(Has (ImmediateParentRun (Root '>')(InstanceType '::comment'))))))\", sexpr(q3));\n        Elements els3 = doc.select(q3);\n        assertSelectedIds(els3, \"3\");\n    }\n\n    @Test void nodeWithElementAncestor() {\n        Document doc = Jsoup.parse(\"<div id=1><div id=2><p> <!-- comment --></p></div></div>\");\n        String q = \"div:has(p ::comment)\";\n        assertEquals(\"(And (Tag 'div')(Has (And (InstanceType '::comment')(Ancestor (Tag 'p')))))\", sexpr(q));\n        Elements els = doc.select(q);\n        assertSelectedIds(els, \"1\", \"2\");\n    }\n\n    @Test void precedingComment() {\n        Document doc = Jsoup.parse(\"<div><!-- comment --><p id=1><p id=2></div><div><p id=3><p id=4>\");\n\n        String q = \"::comment ~ p\";\n        assertEquals(\"(And (Tag 'p')(PreviousSibling (InstanceType '::comment')))\", sexpr(q));\n        Elements els1 = doc.select(q);\n        assertSelectedIds(els1, \"1\", \"2\");\n\n        String q2 = \"::comment + p\";\n        assertEquals(\"(And (Tag 'p')(ImmediatePreviousSibling (InstanceType '::comment')))\", sexpr(q2));\n        Elements els2 = doc.select(q2);\n        assertSelectedIds(els2, \"1\");\n    }\n\n    @Test void datanode() {\n        Document doc = Jsoup.parse(\"<div id=1> <!-- foo --> </div> <div id=2> <script>foo</script> </div> <div><script>bar></script>\");\n        String q = \"div:has(::data:contains(foo))\";\n        assertEquals(\"(And (Tag 'div')(Has (And (InstanceType '::data')(ContainsValue ':contains(foo)'))))\", sexpr(q));\n        Elements els = doc.select(q);\n        assertSelectedIds(els, \"2\");\n    }\n\n    @Test void leafNode() {\n        Document doc = Jsoup.parse(\"<div id=1></div><div id=2> </div>\");\n        String q = \"div:has(::leafnode)\";\n        assertEquals(\"(And (Tag 'div')(Has (InstanceType '::leafnode')))\", sexpr(q));\n        Elements els = doc.select(q);\n        assertSelectedIds(els, \"2\");\n    }\n\n    @Test void leafNodeContains() {\n        Document doc = Jsoup.parse(\"<div id=1>foo</div><div id=2><!-- bar --></div><div id=3>Bar</div><div id=4><script id=5> Bar </script></div>\");\n        String q = \"div:has(::leafnode:contains(Bar))\";\n        assertEquals(\"(And (Tag 'div')(Has (And (InstanceType '::leafnode')(ContainsValue ':contains(bar)'))))\", sexpr(q));\n        Elements els = doc.select(q);\n        assertSelectedIds(els, \"2\", \"3\", \"4\");\n    }\n\n    @Test void nodeContains() {\n        Document doc = Jsoup.parse(\"<div><p>One</p></div><div>Two</div>\");\n        String q = \"div ::node:contains(One)\";\n        Nodes<Node> nodes = doc.selectNodes(Selector.evaluatorOf(q));\n        // should have the P and the Text (because nodeValue is ownText)\n        assertEquals(2, nodes.size());\n        assertEquals(\"One\", nodes.get(0).nodeValue());\n        assertEquals(\"p\", nodes.get(0).nodeName());\n        assertEquals(\"One\", nodes.get(1).nodeValue());\n        assertEquals(\"#text\", nodes.get(1).nodeName());\n    }\n\n    @Test void selectComment() {\n        Document doc = Jsoup.parse(\"<div><!-- find this --></div><!-- and this --><p><!-- not that --></p>\");\n        String q = \"::comment:contains(this)\";\n        assertEquals(\"(And (InstanceType '::comment')(ContainsValue ':contains(this)'))\", sexpr(q));\n        Nodes<Comment> comments = doc.selectNodes(q, Comment.class);\n\n        assertEquals(2, comments.size());\n        assertEquals(\" find this \", comments.get(0).getData());\n        assertEquals(\" find this \", comments.get(0).nodeValue());\n        assertEquals(\" and this \", comments.get(1).getData());\n\n        Nodes<Node> nodes = doc.selectNodes(\"::comment\");\n        assertEquals(3, nodes.size());\n        assertEquals(\" find this \", nodes.get(0).nodeValue());\n        assertEquals(\" and this \", nodes.get(1).nodeValue());\n        assertEquals(\" not that \", nodes.get(2).nodeValue());\n    }\n\n    @Test void selectTextNodes() {\n        Document doc = Jsoup.parse(\"<p>One</p> <p>Two</p>\");\n        Nodes<TextNode> text = doc.selectNodes(\"p ::text\", TextNode.class);\n        assertEquals(2, text.size());\n        assertEquals(\"One\", text.get(0).getWholeText());\n        assertEquals(\"Two\", text.get(1).getWholeText());\n    }\n\n    @Test void elementsViaNodeInterface() {\n        Document doc = Jsoup.parse(\"<p>One</p> <p>Two</p>\");\n        Nodes<Element> ps = doc.selectNodes(\"p\", Element.class);\n        assertEquals(2, ps.size());\n        assertEquals(\"One\", ps.get(0).text());\n        assertEquals(\"Two\", ps.get(1).text());\n    }\n\n    @Test void blankNodes() {\n        Document doc = Jsoup.parse(\"<p> </p><p><!--  --><!----></p><p>\\n</p><p>One</p><p><!-- two --></p>\");\n\n        Nodes<Node> nodes = doc.selectNodes(\"::node:blank\", Node.class);\n        assertEquals(12, nodes.size());\n        assertEquals(\"#document\", nodes.get(0).nodeName());\n        assertEquals(\"html\", nodes.get(1).nodeName());\n        assertEquals(\"head\", nodes.get(2).nodeName());\n        assertEquals(\"body\", nodes.get(3).nodeName());\n        assertEquals(\"p\", nodes.get(4).nodeName());\n        assertEquals(\"#text\", nodes.get(5).nodeName());\n        assertEquals(\"p\", nodes.get(6).nodeName());\n        assertEquals(\"#comment\", nodes.get(7).nodeName());\n        assertEquals(\"#comment\", nodes.get(8).nodeName());\n        assertEquals(\"p\", nodes.get(9).nodeName());\n        assertEquals(\"#text\", nodes.get(10).nodeName());\n        assertEquals(\"p\", nodes.get(11).nodeName());\n\n        Nodes<Comment> comments = doc.selectNodes(\"::comment:blank\", Comment.class);\n        assertEquals(2, comments.size());\n        assertEquals(\"  \", comments.get(0).getData());\n        assertEquals(\"\", comments.get(1).getData());\n\n        String notBlank = \"::comment:not(:blank)\";\n        Evaluator notBlankEval = QueryParser.parse(notBlank);\n        assertEquals(\"(And (InstanceType '::comment')(Not (BlankValue ':blank')))\", sexpr(notBlankEval));\n        Nodes<Comment> commentsWithData = doc.selectNodes(notBlankEval, Comment.class);\n        assertEquals(1, commentsWithData.size());\n        assertEquals(\" two \", commentsWithData.get(0).getData());\n    }\n\n    @Test void blankElements() {\n        Document doc = Jsoup.parse(\"<p id=1>  </p><p id=2>One</p><p id=3><span>One</span></p>\");\n        Elements els = doc.select(\"p:blank\");\n        assertSelectedIds(els, \"1\", \"3\");\n    }\n\n    @Test void nonBlankText() {\n        Document doc = Jsoup.parse(\"<p id=1>  </p><p id=2>One</p><p id=3><span id=4>Two</span></p>\");\n        Elements els = doc.select(\":not(:blank)\");\n        assertSelectedIds(els, \"2\", \"4\");\n\n        Nodes<TextNode> text = doc.selectNodes(\"::text:not(:blank)\", TextNode.class);\n        assertEquals(2, text.size());\n        assertEquals(\"One\", text.get(0).getWholeText());\n        assertEquals(\"Two\", text.get(1).getWholeText());\n    }\n\n    @Test void nodeMatches() {\n        Document doc = Jsoup.parse(\"<p>1234</p> <p>123</p> <p>12</p> <p>1</p> <!--4321--> <!--432--> <!-- 43 -->\");\n\n        String regex = \"::leafnode:matches(\\\\d{3,4})\";\n        Evaluator eval = Selector.evaluatorOf(regex);\n        assertEquals(\"(And (InstanceType '::leafnode')(MatchesValue ':matches(\\\\d{3,4})'))\", sexpr(eval));\n\n        Nodes<Node> nodes = doc.selectNodes(eval);\n        assertEquals(4, nodes.size());\n        assertEquals(\"1234\", nodes.get(0).nodeValue());\n        assertEquals(\"123\", nodes.get(1).nodeValue());\n        assertEquals(\"4321\", nodes.get(2).nodeValue());\n        assertEquals(\"432\", nodes.get(3).nodeValue());\n    }\n\n    @Test void cdataNodes() {\n        String xml = \"<body><![CDATA[One]]><p>Two</p><![CDATA[Three]]><x><![CDATA[ ]]></body>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        // via leafnode:\n        Nodes<CDataNode> leafnodes = doc.selectNodes(\"::leafnode\", CDataNode.class);\n        assertEquals(3, leafnodes.size());\n\n        // cdata via unfiltered\n        Nodes<Node> nodes = doc.selectNodes(\"::cdata\");\n        assertEquals(3, nodes.size());\n\n        // (not) blank:\n        Nodes<CDataNode> notBlanks = doc.selectNodes(\"::cdata:not(:blank)\", CDataNode.class);\n        assertEquals(2, notBlanks.size());\n        assertEquals(\"One\", notBlanks.get(0).nodeValue());\n        assertEquals(\"Three\", notBlanks.get(1).nodeValue());\n\n        // contains:\n        Nodes<CDataNode> contains = doc.selectNodes(\"::cdata:contains(One)\", CDataNode.class);\n        assertEquals(1, contains.size());\n        assertEquals(\"One\", contains.get(0).nodeValue());\n\n        // matches:\n        Nodes<CDataNode> matches = doc.selectNodes(\"::cdata:matches(re)\", CDataNode.class);\n        assertEquals(1, matches.size());\n        assertEquals(\"Three\", matches.get(0).nodeValue());\n    }\n\n    @Test void unknownPseudoNodeSelectError() {\n        Exception ex = assertThrows(\n            Selector.SelectorParseException.class,\n            () -> Selector.evaluatorOf(\"::unknown:contains(foo)\")\n        );\n        assertEquals(\n            \"Could not parse query '::unknown:contains(foo)': unknown node type '::unknown'\",\n            ex.getMessage()\n        );\n    }\n\n    @Test void attributeSelectorQuotedWhitespace() {\n        // https://github.com/jhy/jsoup/issues/2380\n        Document doc = Jsoup.parse(\n            \"<div id=1 data=foobar></div>\" +\n                \"<div id=2 data=' foobar '></div>\" +\n                \"<div id=3 data='xfoobarx'></div>\"\n        );\n\n        // match: literal compare (no trimming)\n        assertSelectedIds(doc.select(\"div[data=\\\"foobar\\\"]\"), \"1\");\n        assertSelectedIds(doc.select(\"div[data=\\\" foobar \\\"]\"), \"2\");\n\n        // prefix\n        assertSelectedIds(doc.select(\"div[data^=\\\"foo\\\"]\"), \"1\");\n        assertSelectedIds(doc.select(\"div[data^=\\\" foo\\\"]\"), \"2\");\n\n        // suffix\n        assertSelectedIds(doc.select(\"div[data$=\\\"bar\\\"]\"), \"1\");\n        assertSelectedIds(doc.select(\"div[data$=\\\"bar \\\"]\"), \"2\");\n\n        // contains\n        assertSelectedIds(doc.select(\"div[data*=\\\"foobar\\\"]\"), \"1\", \"2\", \"3\");\n        assertSelectedIds(doc.select(\"div[data*=\\\" foobar \\\"]\"), \"2\");\n    }\n\n    @Test void canSelectBlankAttribute() {\n        Document doc = Jsoup.parse(\n            \"<div id=1 data=''></div>\" +\n                \"<div id=2 data></div>\" +\n                \"<div id=3 data=one></div>\"\n        );\n\n        assertSelectedIds(doc.select(\"div[data]\"), \"1\", \"2\", \"3\");\n        assertSelectedIds(doc.select(\"div[data='']\"), \"1\", \"2\");\n        assertSelectedIds(doc.select(\"div[data=]\"), \"1\", \"2\");\n\n        assertSelectedIds(doc.select(\"div[data^='']\"), \"1\", \"2\", \"3\");\n        assertSelectedIds(doc.select(\"div[data$='']\"), \"1\", \"2\", \"3\");\n        assertSelectedIds(doc.select(\"div[data*='']\"), \"1\", \"2\", \"3\");\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"[abs:!=]\", \"[ abs:^=]\"})\n    void parseExceptionOnEmptyAbsKey(String query) {\n        Selector.SelectorParseException ex = assertThrows(\n            Selector.SelectorParseException.class,\n            () -> Selector.evaluatorOf(query)\n        );\n        assertEquals(\"Absolute attribute key must have a name\", ex.getMessage());\n    }\n\n    @Test void parseExceptionOnEmptyKeyVal() {\n        // was previously firing at match time, not eval time\n        String q = \"[\\\"=\\\"]\";\n        boolean threw = false;\n        try {\n            Evaluator e = Selector.evaluatorOf(q);\n        } catch (Selector.SelectorParseException ex) {\n            threw = true;\n            assertEquals(\"Quoted value must have content\", ex.getMessage());\n        }\n        assertTrue(threw);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/StructuralEvaluatorTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass StructuralEvaluatorTest {\n    private static final String Html =\n        \"<div id=outer>\" +\n        \"  <div class=a>\" +\n        \"    <p class=p1>One</p>\" +\n        \"    <p class=p2>Two</p>\" +\n        \"  </div>\" +\n        \"  <div class=b>\" +\n        \"    <span>Span1</span>\" +\n        \"    <a href=# class=link>Link</a>\" +\n        \"  </div>\" +\n        \"  <div class=c>\" +\n        \"    <div class=inner>\" +\n        \"      <p class=target>Target</p>\" +\n        \"    </div>\" +\n        \"  </div>\" +\n        \"</div>\";\n\n    @ParameterizedTest\n    @MethodSource(\"selectorMemoData\")\n    void selectorMemoIsClearedOnReset(String selector, boolean expectMemos) {\n        // test that the structural evaluator memos are used, and are reset\n\n        Document doc = Jsoup.parse(Html);\n        Evaluator evaluator = Selector.evaluatorOf(selector);\n\n        // collect all StructuralEvaluator instances from the parsed evaluator tree\n        List<StructuralEvaluator> structuralEvals = new ArrayList<>();\n        collectEvals(evaluator, structuralEvals);\n\n        // use Collector.stream vs Selector.select(), as the later is able to reset after executing\n        Collector.stream(evaluator, doc).count(); // consume stream to populate memos\n        assertFalse(structuralEvals.isEmpty());\n\n        boolean hadMemos = false;\n        for (StructuralEvaluator se : structuralEvals) {\n            if (!se.threadMemo.get().isEmpty()) {\n                hadMemos = true;\n                break;\n            }\n        }\n\n        evaluator.reset();\n\n        // verify all structural evaluator thread-local maps are cleared\n        for (StructuralEvaluator se : structuralEvals) {\n            assertTrue(se.threadMemo.get().isEmpty());\n        }\n\n        assertEquals(expectMemos, hadMemos);\n    }\n\n    private static Stream<Arguments> selectorMemoData() {\n        return Stream.of(\n            Arguments.of(\"div:not(.b)\", true),       // Not (uses memoMatches)\n            Arguments.of(\"div p\", true),             // Ancestor (ancestor chain checks)\n            Arguments.of(\"span ~ a\", true),          // PreviousSibling\n            Arguments.of(\"span + a\", true),          // ImmediatePreviousSibling\n            Arguments.of(\"div > span > a\", false),   // ImmediateParentRun does not use memoMatches\n            Arguments.of(\"div:has(p)\", false)        // Has (coverage; does not use memo for these inputs)\n        );\n    }\n\n    private static void collectEvals(Evaluator evaluator, List<StructuralEvaluator> out) {\n        // recursive traversal of evaluator trees to find StructuralEvaluator instances\n        if (evaluator instanceof CombiningEvaluator) {\n            CombiningEvaluator ce = (CombiningEvaluator) evaluator;\n            for (Evaluator inner : ce.evaluators) {\n                collectEvals(inner, out);\n            }\n            return;\n        }\n\n        if (evaluator instanceof StructuralEvaluator.ImmediateParentRun) {\n            StructuralEvaluator.ImmediateParentRun run = (StructuralEvaluator.ImmediateParentRun) evaluator;\n            out.add(run);\n            for (Evaluator inner : run.evaluators) {\n                collectEvals(inner, out);\n            }\n            return;\n        }\n\n        if (evaluator instanceof StructuralEvaluator) {\n            StructuralEvaluator se = (StructuralEvaluator) evaluator;\n            out.add(se);\n            collectEvals(se.evaluator, out);\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/TraversorTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.TextUtil;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport java.util.HashSet;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.jsoup.nodes.NodeIteratorTest.trackSeen;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class TraversorTest {\n    // Note: NodeTraversor.traverse(new NodeVisitor) is tested in\n    // ElementsTest#traverse()\n\n    @Test\n    public void filterVisit() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        NodeTraversor.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n        }, doc.select(\"div\"));\n        assertEquals(\"<div><p><#text></#text></p></div><div><#text></#text></div>\", accum.toString());\n    }\n\n    @Test\n    public void filterSkipChildren() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        NodeTraversor.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n                // OMIT contents of p:\n                return (\"p\".equals(node.nodeName())) ? FilterResult.SKIP_CHILDREN : FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n        }, doc.select(\"div\"));\n        assertEquals(\"<div><p></p></div><div><#text></#text></div>\", accum.toString());\n    }\n\n    @Test\n    public void filterSkipEntirely() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        NodeTraversor.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                // OMIT p:\n                if (\"p\".equals(node.nodeName()))\n                    return FilterResult.SKIP_ENTIRELY;\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n        }, doc.select(\"div\"));\n        assertEquals(\"<div></div><div><#text></#text></div>\", accum.toString());\n    }\n\n    @Test\n    public void filterRemove() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There be <b>bold</b></div>\");\n        NodeTraversor.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                // Delete \"p\" in head:\n                return (\"p\".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                // Delete \"b\" in tail:\n                return (\"b\".equals(node.nodeName())) ? FilterResult.REMOVE : FilterResult.CONTINUE;\n            }\n        }, doc.select(\"div\"));\n        assertEquals(\"<div></div>\\n<div>There be</div>\", doc.select(\"body\").html());\n    }\n\n    @Test\n    public void filterStop() {\n        Document doc = Jsoup.parse(\"<div><p>Hello</p></div><div>There</div>\");\n        final StringBuilder accum = new StringBuilder();\n        NodeTraversor.filter(new NodeFilter() {\n            @Override\n            public FilterResult head(Node node, int depth) {\n                accum.append(\"<\").append(node.nodeName()).append(\">\");\n                return FilterResult.CONTINUE;\n            }\n\n            @Override\n            public FilterResult tail(Node node, int depth) {\n                accum.append(\"</\").append(node.nodeName()).append(\">\");\n                // Stop after p.\n                return (\"p\".equals(node.nodeName())) ? FilterResult.STOP : FilterResult.CONTINUE;\n            }\n        }, doc.select(\"div\"));\n        assertEquals(\"<div><p><#text></#text></p>\", accum.toString());\n    }\n\n    @Test public void replaceElement() {\n        // https://github.com/jhy/jsoup/issues/1289\n        // test we can replace an element during traversal\n        String html = \"<div><p>One <i>two</i> <i>three</i> four.</p></div>\";\n        Document doc = Jsoup.parse(html);\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override\n            public void head(Node node, int depth) {\n                if (node instanceof Element) {\n                    Element el = (Element) node;\n                    if (el.nameIs(\"i\")) {\n                        Element u = new Element(\"u\").insertChildren(0, el.childNodes());\n                        el.replaceWith(u);\n                    }\n                }\n            }\n\n            @Override\n            public void tail(Node node, int depth) {}\n        }, doc);\n\n        Element p = doc.selectFirst(\"p\");\n        assertNotNull(p);\n        assertEquals(\"<p>One <u>two</u> <u>three</u> four.</p>\", p.outerHtml());\n    }\n\n    @Test public void canAddChildren() {\n        Document doc = Jsoup.parse(\"<div><p></p><p></p></div>\");\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            int i = 0;\n            @Override\n            public void head(Node node, int depth) {\n                if (node.nodeName().equals(\"p\")) {\n                    Element p = (Element) node;\n                    p.append(\"<span>\" + i++ + \"</span>\");\n                }\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n                if (node.nodeName().equals(\"p\")) {\n                    Element p = (Element) node;\n                    p.append(\"<span>\" + i++ + \"</span>\");\n                }\n            }\n        }, doc);\n\n        assertEquals(\"<div>\\n\" +\n            \" <p><span>0</span><span>1</span></p>\\n\" +\n            \" <p><span>2</span><span>3</span></p>\\n\" +\n            \"</div>\", doc.body().html());\n    }\n\n    @Test public void canSpecifyOnlyHead() {\n        // really, a compilation test - works as a lambda if just head\n        Document doc = Jsoup.parse(\"<div><p>One</p></div>\");\n        final int[] count = {0};\n        NodeTraversor.traverse((node, depth) -> count[0]++, doc);\n        assertEquals(7, count[0]);\n    }\n\n    @Test public void canRemoveDuringHead() {\n        Document doc = Jsoup.parse(\"<div><p id=1>Zero<p id=1>One<p id=2>Two<p>Three</div>\");\n        NodeTraversor.traverse((node, depth) -> {\n            if (node.attr(\"id\").equals(\"1\"))\n                node.remove();\n            else if (node instanceof TextNode && ((TextNode) node).text().equals(\"Three\"))\n                node.remove();\n        }, doc);\n\n        assertEquals(\"<div><p id=\\\"2\\\">Two</p><p></p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void replacementInHeadCanTailReplacementAndVisitChildren() {\n        // if head() replaces the current node, the replacement should be tailed and its children still traversed\n        Document doc = Jsoup.parseBodyFragment(\"<div><i>two</i></div>\");\n        Element div = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override\n            public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (\"i\".equals(node.nodeName())) {\n                    Element replacement = new Element(\"u\").insertChildren(0, node.childNodes());\n                    node.replaceWith(replacement);\n                }\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, div);\n\n        assertEquals(\"div;i;two;\", headOrder.toString());\n        assertEquals(\"two;u;div;\", tailOrder.toString());\n        assertEquals(\"<div><u>two</u></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @ParameterizedTest\n    @EnumSource(CurrentPositionMutation.class)\n    void supportsCurrentNodeMutationDuringHead(CurrentPositionMutation mutation) {\n        // supported head() mutations at the current cursor should terminate cleanly and continue traversal in order\n        // https://github.com/jhy/jsoup/issues/2472\n        Document doc = Jsoup.parse(\"<div><p>One</p>a<em>Two</em></div>\");\n        AtomicInteger visits = new AtomicInteger();\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                if (visits.incrementAndGet() > MaxHeadVisits)\n                    fail(String.format(\"Likely loop when applying %s in head()\", mutation));\n                trackSeen(node, headOrder);\n                if (node instanceof TextNode && ((TextNode) node).text().equals(\"a\"))\n                    mutation.apply(node);\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, doc);\n\n        assertEquals(\"#root;html;head;body;div;p;One;a;em;Two;\", headOrder.toString());\n        assertEquals(mutation.expectedTailOrder, tailOrder.toString());\n        assertEquals(mutation.expectedHtml, TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    private static final int MaxHeadVisits = 20;\n\n    private enum CurrentPositionMutation {\n        Remove(\"head;One;p;Two;em;div;body;html;#root;\", \"<div><p>One</p><em>Two</em></div>\") {\n            @Override void apply(Node node) {\n                node.remove();\n            }\n        },\n        Replace(\"head;One;p;b;Two;em;div;body;html;#root;\", \"<div><p>One</p>b<em>Two</em></div>\") {\n            @Override void apply(Node node) {\n                node.replaceWith(new TextNode(\"b\"));\n            }\n        },\n        BeforeRemove(\"head;One;p;b;Two;em;div;body;html;#root;\", \"<div><p>One</p>b<em>Two</em></div>\") {\n            @Override void apply(Node node) {\n                node.before(new TextNode(\"b\"));\n                node.remove();\n            }\n        },\n        AfterRemove(\"head;One;p;b;Two;em;div;body;html;#root;\", \"<div><p>One</p>b<em>Two</em></div>\") {\n            @Override void apply(Node node) {\n                node.after(new TextNode(\"b\"));\n                node.remove();\n            }\n        };\n\n        final String expectedTailOrder;\n        final String expectedHtml;\n\n        CurrentPositionMutation(String expectedTailOrder, String expectedHtml) {\n            this.expectedTailOrder = expectedTailOrder;\n            this.expectedHtml = expectedHtml;\n        }\n\n        abstract void apply(Node node);\n    }\n\n    @ParameterizedTest\n    @EnumSource(SiblingInsertion.class)\n    void siblingInsertionsOnlyVisitFutureNodesDuringHead(SiblingInsertion insertion) {\n        // nodes inserted before the current cursor are not visited; nodes inserted after it are still ahead and are\n        Document doc = Jsoup.parseBodyFragment(\"<div>a</div>\");\n        StringBuilder headTexts = new StringBuilder();\n        StringBuilder tailTexts = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                if (node instanceof TextNode) {\n                    trackSeen(node, headTexts);\n                    insertion.apply((TextNode) node);\n                }\n            }\n\n            @Override public void tail(Node node, int depth) {\n                if (node instanceof TextNode)\n                    trackSeen(node, tailTexts);\n            }\n        }, doc);\n\n        assertEquals(insertion.expectedTexts, headTexts.toString());\n        assertEquals(insertion.expectedTexts, tailTexts.toString());\n        assertEquals(insertion.expectedHtml, TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    private enum SiblingInsertion {\n        Before(\"a;\", \"<div>ba</div>\") {\n            @Override void apply(TextNode node) {\n                if (node.text().equals(\"a\"))\n                    node.before(new TextNode(\"b\"));\n            }\n        },\n        After(\"a;b;\", \"<div>ab</div>\") {\n            @Override void apply(TextNode node) {\n                if (node.text().equals(\"a\"))\n                    node.after(new TextNode(\"b\"));\n            }\n        };\n\n        final String expectedTexts;\n        final String expectedHtml;\n\n        SiblingInsertion(String expectedTexts, String expectedHtml) {\n            this.expectedTexts = expectedTexts;\n            this.expectedHtml = expectedHtml;\n        }\n\n        abstract void apply(TextNode node);\n    }\n\n    @Test\n    void visitsChildrenInsertedInHead() {\n        // when the current node remains attached, children inserted in head() are visited in the same traversal\n        Document doc = Jsoup.parseBodyFragment(\"<div><p></p></div>\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node instanceof Element && node.nameIs(\"p\"))\n                    ((Element) node).append(\"<span>child</span>\");\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, doc.body());\n\n        assertEquals(\"body;div;p;span;child;\", headOrder.toString());\n        assertEquals(\"child;span;p;div;body;\", tailOrder.toString());\n        assertEquals(\"<div><p><span>child</span></p></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void removingTraversalRootInHeadDoesNotEscapeOriginalSubtree() {\n        Document doc = Jsoup.parseBodyFragment(\"<div>a</div><p>b</p>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node == root)\n                    node.remove();\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;\", headOrder.toString());\n        assertEquals(\"\", tailOrder.toString());\n        assertEquals(\"<p>b</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void removingOnlyTraversalRootInHeadStopsWhenOriginalSlotIsEmpty() {\n        Document doc = Jsoup.parseBodyFragment(\"<div>a</div>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node == root)\n                    node.remove();\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;\", headOrder.toString());\n        assertEquals(\"\", tailOrder.toString());\n        assertEquals(\"\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void replacingTraversalRootInHeadStaysWithinReplacementSubtree() {\n        Document doc = Jsoup.parseBodyFragment(\"<div><span>x</span></div><p>y</p>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node == root) {\n                    Element replacement = new Element(\"section\").insertChildren(0, node.childNodes());\n                    node.replaceWith(replacement);\n                }\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;span;x;\", headOrder.toString());\n        assertEquals(\"x;span;section;\", tailOrder.toString());\n        assertEquals(\"<section><span>x</span></section><p>y</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void unwrappingTraversalRootInHeadVisitsExposedTopLevelNodesUntilOriginalBoundary() {\n        Document doc = Jsoup.parseBodyFragment(\"<div><b>x</b><i>y</i></div><p>z</p>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node == root)\n                    node.unwrap();\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;x;i;y;\", headOrder.toString());\n        assertEquals(\"x;b;y;i;\", tailOrder.toString());\n        assertEquals(\"<b>x</b><i>y</i><p>z</p>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void unwrapInHeadContinuesFromExposedChildren() {\n        Document doc = Jsoup.parseBodyFragment(\"<div><span><b>x</b><i>y</i></span><u>z</u></div>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node instanceof Element && node.nameIs(\"span\"))\n                    node.unwrap();\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;span;x;i;y;u;z;\", headOrder.toString());\n        assertEquals(\"x;b;y;i;z;u;div;\", tailOrder.toString());\n        assertEquals(\"<div><b>x</b><i>y</i><u>z</u></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void removingCurrentAndOriginalNextInHeadTailsParent() {\n        Document doc = Jsoup.parseBodyFragment(\"<div>a<em>Two</em></div>\");\n        Element root = doc.expectFirst(\"div\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node instanceof TextNode && ((TextNode) node).text().equals(\"a\")) {\n                    node.nextSibling().remove();\n                    node.remove();\n                }\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, root);\n\n        assertEquals(\"div;a;\", headOrder.toString());\n        assertEquals(\"div;\", tailOrder.toString());\n        assertEquals(\"<div></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test\n    void beforeAfterRemoveInHeadTailsCurrentSlotAndHeadsFutureSiblings() {\n        Document doc = Jsoup.parse(\"<div><p>One</p>a<em>Two</em></div>\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (node instanceof TextNode && ((TextNode) node).text().equals(\"a\")) {\n                    node.before(new TextNode(\"b\"));\n                    node.after(new TextNode(\"c\"));\n                    node.remove();\n                }\n            }\n\n            @Override public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, doc);\n\n        assertEquals(\"#root;html;head;body;div;p;One;a;c;em;Two;\", headOrder.toString());\n        assertEquals(\"head;One;p;b;c;Two;em;div;body;html;#root;\", tailOrder.toString());\n        assertEquals(\"<div><p>One</p>bc<em>Two</em></div>\", TextUtil.stripNewlines(doc.body().html()));\n    }\n\n    @Test void elementFunctionalTraverse() {\n        Document doc = Jsoup.parse(\"<div><p>1<p>2<p>3\");\n        Element body = doc.body();\n\n        AtomicInteger seenCount = new AtomicInteger();\n        AtomicInteger deepest = new AtomicInteger();\n        body.traverse((node, depth) -> {\n            seenCount.incrementAndGet();\n            if (depth > deepest.get()) deepest.set(depth);\n        });\n\n        assertEquals(8, seenCount.get()); // body and contents\n        assertEquals(3, deepest.get());\n    }\n\n    @Test void seesDocRoot() {\n        Document doc = Jsoup.parse(\"<p>One\");\n        AtomicBoolean seen = new AtomicBoolean(false);\n        doc.traverse((node, depth) -> {\n            if (node.equals(doc))\n                seen.set(true);\n        });\n        assertTrue(seen.get());\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"em\", \"b\"})\n    void doesntVisitAgainAfterRemoving(String removeTag) {\n        // https://github.com/jhy/jsoup/issues/2355\n        Document doc = Jsoup.parse(\"<div id=1><div><em>first</em><b>last</b></div></div>\");\n        HashSet<Node> visited = new HashSet<>();\n        NodeTraversor.traverse((node, depth) -> {\n            if (!visited.add(node))\n                fail(String.format(\"node '%s' is being visited for the second time\", node));\n            if (removeTag.equals(node.nodeName()))\n                node.remove();\n        }, doc);\n    }\n\n\n    @Test\n    void traversesOnceInOrderAfterRemove() {\n        Document doc = Jsoup.parse(\"<div><p>Text <em>emphasized</em> and <b>bold</b></p></div>\");\n        StringBuilder headOrder = new StringBuilder();\n        StringBuilder tailOrder = new StringBuilder();\n\n        NodeTraversor.traverse(new NodeVisitor() {\n            @Override\n            public void head(Node node, int depth) {\n                trackSeen(node, headOrder);\n                if (\"b\".equals(node.nodeName())) // remove the b, causes a cascade of last childs, don't miss any\n                    node.remove();\n            }\n\n            @Override\n            public void tail(Node node, int depth) {\n                trackSeen(node, tailOrder);\n            }\n        }, doc);\n\n        // check\n        assertEquals(\"#root;html;head;body;div;p;Text ;em;emphasized; and ;b;\", headOrder.toString());\n        assertEquals(\"head;Text ;emphasized;em; and ;p;div;body;html;#root;\", tailOrder.toString());\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/jsoup/select/XpathTest.java",
    "content": "package org.jsoup.select;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.nodes.Element;\nimport org.jsoup.nodes.Node;\nimport org.jsoup.nodes.TextNode;\nimport org.jsoup.parser.Parser;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport javax.xml.xpath.XPath;\nimport javax.xml.xpath.XPathFactory;\nimport javax.xml.xpath.XPathFactoryConfigurationException;\nimport javax.xml.xpath.XPathFunctionResolver;\nimport javax.xml.xpath.XPathVariableResolver;\nimport java.util.List;\nimport java.util.stream.Stream;\n\nimport static org.jsoup.helper.W3CDom.XPathFactoryProperty;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class XpathTest {\n\n    @Test\n    public void supportsXpath() {\n        String html = \"<body><div><p>One</div><div><p>Two</div><div>Three</div>\";\n        Document doc = Jsoup.parse(html);\n\n        Elements els = doc.selectXpath(\"//div/p\");\n        assertEquals(2, els.size());\n        assertEquals(\"One\", els.get(0).text());\n        assertEquals(\"Two\", els.get(1).text());\n    }\n\n    @Test public void supportsXpathFromElement() {\n        String html = \"<body><div><p>One</div><div><p>Two</div><div>Three</div>\";\n        Document doc = Jsoup.parse(html);\n\n        Element div = doc.selectFirst(\"div\");\n        assertNotNull(div);\n        Element w3cDiv = div.selectXpath(\".\").first(); // self\n        assertSame(div, w3cDiv);\n\n        Elements els = div.selectXpath(\"p\");\n        assertEquals(1, els.size());\n        assertEquals(\"One\", els.get(0).text());\n        assertEquals(\"p\", els.get(0).tagName());\n\n        assertEquals(1, div.selectXpath(\"//body\").size()); // the whole document is visible on the div context\n        assertEquals(1, doc.selectXpath(\"//body\").size());\n    }\n\n    @Test public void emptyElementsIfNoResults() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two\");\n        assertEquals(0, doc.selectXpath(\"//div\").size());\n    }\n\n    @Test\n    public void throwsSelectException() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two\");\n        boolean threw = false;\n        try {\n            doc.selectXpath(\"//???\");\n        } catch (Selector.SelectorParseException e) {\n            threw = true;\n            // checks exception message within jsoup's control, rest may be JDK impl specific\n            // was - Could not evaluate XPath query [//???]: javax.xml.transform.TransformerException: A location step was expected following the '/' or '//' token.\n            assertTrue(e.getMessage().startsWith(\"Could not evaluate XPath query [//???]:\"));\n\n            // check we have a root cause\n            Throwable cause = e.getCause();\n            assertNotNull(cause);\n            assertNotSame(cause, e);\n        }\n        assertTrue(threw);\n    }\n\n    @Test\n    public void supportsLocalname() {\n        String xhtml = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        Document doc = Jsoup.parse(xhtml, Parser.xmlParser());\n        Elements elements = doc.selectXpath(\"//*[local-name()='body']\");\n        assertEquals(1, elements.size());\n        assertEquals(\"One\", elements.first().id());\n    }\n\n    @Test\n    public void canDitchNamespaces() {\n        String xhtml = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        Document doc = Jsoup.parse(xhtml, Parser.xmlParser());\n        doc.select(\"[xmlns]\").removeAttr(\"xmlns\");\n        Elements elements = doc.selectXpath(\"//*[local-name()='body']\");\n        assertEquals(1, elements.size());\n\n        elements = doc.selectXpath(\"//body\");\n        assertEquals(1, elements.size());\n        assertEquals(\"One\", elements.first().id());\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"provideEvaluators\")\n    void cssAndXpathEquivalents(Document doc, String css, String xpath) {\n        Elements fromCss = doc.select(css);\n        Elements fromXpath = doc.selectXpath(xpath);\n\n        assertTrue(fromCss.size() >= 1);\n        assertTrue(fromXpath.size() >= 1);\n        // tests same size, order, and contents\n        assertEquals(fromCss, fromXpath);\n    }\n\n    private static Stream<Arguments> provideEvaluators() {\n        String html = \"<div id=1><div id=2><p class=foo>Hello</p></div></div><DIV id=3>\";\n        Document doc = Jsoup.parse(html);\n\n        return Stream.of(\n           Arguments.of(doc, \"DIV\", \"//div\"),\n           Arguments.of(doc, \"div > p.foo\", \"//div/p[@class]\"),\n           Arguments.of(doc, \"div + div\", \"//div/following-sibling::div[1]\"),\n           Arguments.of(doc, \"p:containsOwn(Hello)\", \"//p[contains(text(),\\\"Hello\\\")]\")\n        );\n    }\n\n    @Test void canSelectTextNodes() {\n        String html = \"<div><p>One<p><a>Two</a><p>Three and some more\";\n        Document doc = Jsoup.parse(html);\n\n        //  as text nodes:\n        List<TextNode> text = doc.selectXpath(\"//body//p//text()\", TextNode.class);\n        assertEquals(3, text.size());\n        assertEquals(\"One\", text.get(0).text());\n        assertEquals(\"Two\", text.get(1).text());\n        assertEquals(\"Three and some more\", text.get(2).text());\n\n        //  as just nodes:\n        List<Node> nodes = doc.selectXpath(\"//body//p//text()\", Node.class);\n        assertEquals(3, nodes.size());\n        assertEquals(\"One\", nodes.get(0).outerHtml());\n        assertEquals(\"Two\", nodes.get(1).outerHtml());\n        assertEquals(\"Three and some more\", nodes.get(2).outerHtml());\n    }\n\n    @Test void selectByAttribute() {\n        Document doc = Jsoup.parse(\"<p><a href='/foo'>Foo</a><a href='/bar'>Bar</a><a>None</a>\");\n        List<String> hrefs = doc.selectXpath(\"//a[@href]\").eachAttr(\"href\");\n        assertEquals(2, hrefs.size());\n        assertEquals(\"/foo\", hrefs.get(0));\n        assertEquals(\"/bar\", hrefs.get(1));\n    }\n\n    @Test void selectOutsideOfElementTree() {\n        Document doc = Jsoup.parse(\"<p>One<p>Two<p>Three\");\n        Elements ps = doc.selectXpath(\"//p\");\n        assertEquals(3, ps.size());\n\n        Element p1 = ps.get(0);\n        assertEquals(\"One\", p1.text());\n\n        Elements sibs = p1.selectXpath(\"following-sibling::p\");\n        assertEquals(2, sibs.size());\n        assertEquals(\"Two\", sibs.get(0).text());\n        assertEquals(\"Three\", sibs.get(1).text());\n    }\n\n    @Test void selectAncestorsOnContextElement() {\n        // https://github.com/jhy/jsoup/issues/1652\n        Document doc = Jsoup.parse(\"<div><p>Hello\");\n        Element p = doc.selectFirst(\"p\");\n        assertNotNull(p);\n        Elements chain = p.selectXpath(\"ancestor-or-self::*\");\n        assertEquals(4, chain.size());\n        assertEquals(\"html\", chain.get(0).tagName());\n        assertEquals(\"p\", chain.get(3).tagName());\n    }\n\n    @Test\n    public void canSupplyAlternateFactoryImpl() {\n        // previously we had a test to load Saxon and do an XPath 2.0 query. But we know Saxon works and so that's\n        // redundant - really just need to test that an alternate XPath factory can be used\n\n        System.setProperty(XPathFactoryProperty, AlternateXpathFactory.class.getName());\n\n        String xhtml = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        boolean threw = false;\n        try {\n            Document doc = Jsoup.parse(xhtml, Parser.xmlParser());\n            Elements elements = doc.selectXpath(\"//*:body\");\n\n        } catch (IllegalArgumentException e) {\n            assertTrue(e.getMessage().contains(\"Sorry, no can do!\"));\n            threw = true;\n        }\n        assertTrue(threw);\n        System.clearProperty(XPathFactoryProperty);\n    }\n\n    @Test\n    public void notNamespaceAware() {\n        String xhtml = \"<html xmlns='http://www.w3.org/1999/xhtml'><body id='One'><div>hello</div></body></html>\";\n        Document doc = Jsoup.parse(xhtml, Parser.xmlParser());\n        Elements elements = doc.selectXpath(\"//body\");\n        assertEquals(1, elements.size());\n        assertEquals(\"One\", elements.first().id());\n    }\n\n    @Test\n    public void supportsPrefixes() {\n        // example from https://www.w3.org/TR/xml-names/\n        String xml = \"<?xml version=\\\"1.0\\\"?>\\n\" +\n            \"<bk:book xmlns:bk='urn:loc.gov:books'\\n\" +\n            \"         xmlns:isbn='urn:ISBN:0-395-36341-6'>\\n\" +\n            \"    <bk:title>Cheaper by the Dozen</bk:title>\\n\" +\n            \"    <isbn:number>1568491379</isbn:number>\\n\" +\n            \"</bk:book>\";\n        Document doc = Jsoup.parse(xml, Parser.xmlParser());\n\n        //Elements elements = doc.selectXpath(\"//bk:book/bk:title\");\n        Elements elements = doc.selectXpath(\"//book/title\");\n        assertEquals(1, elements.size());\n        assertEquals(\"Cheaper by the Dozen\", elements.first().text());\n\n        // with prefix\n        Elements byPrefix = doc.selectXpath(\"//*[name()='bk:book']/*[name()='bk:title']\");\n        assertEquals(1, byPrefix.size());\n        assertEquals(\"Cheaper by the Dozen\", byPrefix.first().text());\n\n        Elements byLocalName = doc.selectXpath(\"//*[local-name()='book']/*[local-name()='title']\");\n        assertEquals(1, byLocalName.size());\n        assertEquals(\"Cheaper by the Dozen\", byLocalName.first().text());\n\n        Elements isbn = doc.selectXpath(\"//book/number\");\n        assertEquals(1, isbn.size());\n        assertEquals(\"1568491379\", isbn.first().text());\n    }\n\n    @Test void withSemiInAttributeName() {\n        // https://github.com/jhy/jsoup/issues/2244\n        String html = \"<div grading?&quot;=foo>One</div>\";\n        Document doc = Jsoup.parse(html);\n        Elements els = doc.selectXpath(\"//div\");\n        assertEquals(1, els.size());\n    }\n\n    // minimal, no-op implementation class to verify users can load a factory to support XPath 2.0 etc\n    public static class AlternateXpathFactory extends XPathFactory {\n        public AlternateXpathFactory() {\n            super();\n        }\n\n        @Override\n        public boolean isObjectModelSupported(String objectModel) {\n            return true;\n        }\n\n        @Override\n        public void setFeature(String name, boolean value) throws XPathFactoryConfigurationException {\n\n        }\n\n        @Override\n        public boolean getFeature(String name) throws XPathFactoryConfigurationException {\n            return true;\n        }\n\n        @Override\n        public void setXPathVariableResolver(XPathVariableResolver resolver) {\n\n        }\n\n        @Override\n        public void setXPathFunctionResolver(XPathFunctionResolver resolver) {\n\n        }\n\n        @Override\n        public XPath newXPath() {\n            throw new IllegalArgumentException(\"Sorry, no can do!\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/helper/HttpClientExecutorTest.java",
    "content": "package org.jsoup.helper;\n\nimport org.jsoup.internal.SharedConstants;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class HttpClientExecutorTest {\n    @Test void loadsMultiReleaseHttpClientExecutor() {\n        // sanity check that the test is resolving the packaged Java 11 override, not a copy on the test classpath\n        String resource = HttpClientTestAccess.executorClassResource().toExternalForm();\n        assertTrue(resource.contains(\"/META-INF/versions/11/\"), resource);\n    }\n\n    @Test void getsHttpClient() {\n        try {\n            enableHttpClient();\n            RequestExecutor executor = RequestDispatch.get(new HttpConnection.Request(), null);\n            assertTrue(HttpClientTestAccess.isHttpClientExecutor(executor));\n        } finally {\n            disableHttpClient(); // reset to previous default for JDK8 compat tests\n        }\n    }\n\n    @Test void getsHttpClientByDefault() {\n        System.clearProperty(SharedConstants.UseHttpClient);\n        RequestExecutor executor = RequestDispatch.get(new HttpConnection.Request(), null);\n        assertTrue(HttpClientTestAccess.isHttpClientExecutor(executor));\n    }\n\n    @Test void downgradesSocksProxyToUrlConnectionExecutor() {\n        try {\n            enableHttpClient();\n            HttpConnection.Request request = new HttpConnection.Request();\n            request.proxy(new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(\"localhost\", 1080)));\n\n            // SOCKS handling only matters on the Java 11+ path where HttpClient would otherwise be selected (and just bypasses)\n            RequestExecutor executor = RequestDispatch.get(request, null);\n            assertInstanceOf(UrlConnectionExecutor.class, executor);\n        } finally {\n            disableHttpClient(); // reset to previous default for JDK8 compat tests\n        }\n    }\n\n    public static void enableHttpClient() {\n        System.setProperty(SharedConstants.UseHttpClient, \"true\");\n    }\n\n    public static void disableHttpClient() {\n        System.setProperty(SharedConstants.UseHttpClient, \"false\");\n    }\n\n    @Test void proxyWrapUsesSystemDefaultProxySelector() {\n        ProxySelector originalSelector = ProxySelector.getDefault();\n        InetSocketAddress defaultProxy = new InetSocketAddress(\"system.proxy\", 8080);\n\n        try {\n            ProxySelector.setDefault(new ProxySelector() {\n                @Override\n                public List<Proxy> select(URI uri) {\n                    return Collections.singletonList(\n                        new Proxy(Proxy.Type.HTTP, defaultProxy)\n                    );\n                }\n                \n                @Override\n                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {}\n            });\n\n            ProxySelector wrap = HttpClientTestAccess.newProxyWrap();\n            List<Proxy> proxies = wrap.select(URI.create(\"http://example.com\"));\n\n            assertEquals(1, proxies.size());\n            assertSame(defaultProxy, proxies.get(0).address());\n        } finally {\n            ProxySelector.setDefault(originalSelector);\n        }\n    }\n\n    @Test void proxyWrapConnectFailedOnlyForSystemProxy() {\n        try {\n            ProxySelector wrap = HttpClientTestAccess.newProxyWrap();\n            HttpClientTestAccess.setPerRequestProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(\"custom\", 9090)));\n            wrap.connectFailed(URI.create(\"http://example.com\"),\n                new InetSocketAddress(\"custom\", 9090),\n                new IOException(\"test\"));\n        } finally {\n            HttpClientTestAccess.clearPerRequestProxy();\n        }\n    }\n\n    @Test\n    void perRequestProxyOverridesSystemDefault() {\n        ProxySelector original = ProxySelector.getDefault();\n        InetSocketAddress sysProxy = new InetSocketAddress(\"system.proxy\", 8080);\n        InetSocketAddress perReqProxy = new InetSocketAddress(\"per.request\", 9999);\n        try {\n            ProxySelector.setDefault(new ProxySelector() {\n                @Override\n                public List<Proxy> select(URI uri) {\n                    return Collections.singletonList(\n                        new Proxy(Proxy.Type.HTTP, sysProxy));\n                }\n                @Override\n                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {}\n            });\n\n            HttpClientTestAccess.setPerRequestProxy(\n                new Proxy(Proxy.Type.HTTP, perReqProxy));\n\n            ProxySelector wrap = HttpClientTestAccess.newProxyWrap();\n            List<Proxy> proxies = wrap.select(URI.create(\"http://example.com\"));\n            assertSame(perReqProxy, proxies.get(0).address());\n        } finally {\n            HttpClientTestAccess.clearPerRequestProxy();\n            ProxySelector.setDefault(original);\n        }\n    }\n\n    @Test void connectFailedDelegatesToSystemDefault() {\n        ProxySelector original = ProxySelector.getDefault();\n        final boolean[] called = {false};\n        try {\n            ProxySelector.setDefault(new ProxySelector() {\n                @Override\n                public List<Proxy> select(URI uri) { return Collections.singletonList(Proxy.NO_PROXY); }\n                @Override\n                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { called[0] = true; }\n            });\n            HttpClientTestAccess.newProxyWrap()\n                .connectFailed(URI.create(\"http://example.com\"), new InetSocketAddress(\"x\", 80), new IOException(\"x\"));\n            assertTrue(called[0]);\n        } finally {\n            ProxySelector.setDefault(original);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/helper/HttpClientTestAccess.java",
    "content": "package org.jsoup.helper;\n\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.net.Proxy;\nimport java.net.ProxySelector;\nimport java.net.URL;\n\n/**\n Test access shim for the Java 11 multi-release classes.\n <p>These tests need to exercise the Java 11 implementation as loaded from\n {@code META-INF/versions/11}. Using reflection here keeps them bound to that\n packaged implementation, without adding the Java 11 sources to the test\n compile path.</p>\n */\nfinal class HttpClientTestAccess {\n    private static final String ExecutorClassName = \"org.jsoup.helper.HttpClientExecutor\";\n    private static final String ProxyWrapClassName = ExecutorClassName + \"$ProxyWrap\";\n    private static final String ExecutorClassResource = \"org/jsoup/helper/HttpClientExecutor.class\";\n\n    private HttpClientTestAccess() {}\n\n    static boolean isHttpClientExecutor(RequestExecutor executor) {\n        return executorClass().isInstance(executor);\n    }\n\n    static URL executorClassResource() {\n        URL resource = HttpClientTestAccess.class.getClassLoader().getResource(ExecutorClassResource);\n        if (resource == null)\n            throw new IllegalStateException(\"Could not load \" + ExecutorClassResource);\n        return resource;\n    }\n\n    static ProxySelector newProxyWrap() {\n        try {\n            Constructor<?> constructor = loadClass(ProxyWrapClassName).getDeclaredConstructor();\n            constructor.setAccessible(true);\n            return (ProxySelector) constructor.newInstance();\n        } catch (ReflectiveOperationException e) {\n            throw new IllegalStateException(\"Could not construct HttpClientExecutor.ProxyWrap\", e);\n        }\n    }\n\n    static void setPerRequestProxy(Proxy proxy) {\n        perRequestProxy().set(proxy);\n    }\n\n    static void clearPerRequestProxy() {\n        perRequestProxy().remove();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static ThreadLocal<Proxy> perRequestProxy() {\n        try {\n            Field field = executorClass().getDeclaredField(\"perRequestProxy\");\n            field.setAccessible(true);\n            return (ThreadLocal<Proxy>) field.get(null);\n        } catch (ReflectiveOperationException e) {\n            throw new IllegalStateException(\"Could not access HttpClientExecutor.perRequestProxy\", e);\n        }\n    }\n\n    private static Class<?> executorClass() {\n        return loadClass(ExecutorClassName);\n    }\n\n    private static Class<?> loadClass(String className) {\n        try {\n            return Class.forName(className);\n        } catch (ClassNotFoundException e) {\n            throw new IllegalStateException(\"Could not load \" + className, e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/integration/HttpClientConnectIT.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.helper.HttpClientExecutorTest;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\n\npublic class HttpClientConnectIT extends ConnectIT {\n    @BeforeAll\n    static void useHttpClient() {\n        HttpClientExecutorTest.enableHttpClient();\n    }\n\n    @AfterAll\n    static void resetClient() {\n        HttpClientExecutorTest.disableHttpClient();\n    }\n\n    @Override @Disabled\n    public void canInterruptBodyStringRead() throws InterruptedException {\n        // noop; can't interrupt the client via the calling thread; probably not required as timeouts are robust\n    }\n\n    @Override @Disabled\n    public void canInterruptDocumentRead() throws InterruptedException {\n    }\n\n    @Override @Disabled\n    public void canInterruptThenJoinASpawnedThread() throws InterruptedException {\n    }\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/integration/HttpClientConnectTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.helper.HttpClientExecutorTest;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class HttpClientConnectTest extends ConnectTest {\n    @BeforeAll\n    static void useHttpClient() {\n        HttpClientExecutorTest.enableHttpClient();\n    }\n\n    @AfterAll\n    static void resetClient() {\n        HttpClientExecutorTest.disableHttpClient();\n    }\n\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/integration/HttpClientSessionIT.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.helper.HttpClientExecutorTest;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class HttpClientSessionIT extends SessionIT {\n    @BeforeAll\n    static void useHttpClient() {\n        HttpClientExecutorTest.enableHttpClient();\n    }\n\n    @AfterAll\n    static void resetClient() {\n        HttpClientExecutorTest.disableHttpClient();\n    }\n}\n"
  },
  {
    "path": "src/test/java11/org/jsoup/integration/HttpClientSessionTest.java",
    "content": "package org.jsoup.integration;\n\nimport org.jsoup.helper.HttpClientExecutorTest;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class HttpClientSessionTest extends SessionTest {\n    @BeforeAll\n    static void useHttpClient() {\n        HttpClientExecutorTest.enableHttpClient();\n    }\n\n    @AfterAll\n    static void resetClient() {\n        HttpClientExecutorTest.disableHttpClient();\n    }\n}\n"
  },
  {
    "path": "src/test/resources/bomtests/bom_utf8.html",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html>\n<html>\n<head>\n  <title>OK</title>\n</head>\n<body>\nThere is a UTF8 BOM at the top (before the XML decl). If not read correctly, will look like a non-joining space.\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/fuzztests/ex-inselect16.html",
    "content": "<template><<tr><sA><template>b<b>><table><t><a><b><d><ii><i><dl><a>*</b></templatE></head><opTion></ousemap<opthon></option><select /><select /><select /><select /><select /><select /><select/><select <option></option></><select /><select /><select /><select /><select /><select /><select /><select /><select><keygen><select /><select /><select /><select /><select /><select /><select /><select /><select /><select /><select /><select /><select /><select /optio><select><keygen><snelect /><select /><select /><select /><select /><select /><select t /><select /><select><keygen><select /><select /><select /><select >/><spelect /><select /><select /><select /><select /><select/><select /><select /><select /><select /><select><keygen>"
  },
  {
    "path": "src/test/resources/fuzztests/garble.html",
    "content": "<table>em><a>1r<b>e\u001e<b><><cW l><b><i֮>show><i><i>onfge <i><dl>(\n<a></dl>0zl><i><+>i><dl><a></b>⺾</b>þ<i><c><<col>å>c<html\not<asü><<col>þ</>fa><i><¾><>cﭰ<ۯсf<html\no^n=\n"
  },
  {
    "path": "src/test/resources/htmltests/README",
    "content": "Note\n====\n\nThe HTML files in this directory (htmltests) are intended to be used for testing the Jsoup parser and improving its\ninteroperability with real world published HTML. These files are not distributed in the core Jsoup library.\n\nThese files remain the copyright of the original owner.\n\nIf you are the copyright holder and do not wish your works to be used in this manner, please contact Jonathan Hedley\n(jonathan@hedley.net) and your works will be removed from this test-suite.\n\nSources\n========\n\n* yahoo-article-1.html    http://news.yahoo.com/s/nm/20100831/bs_nm/us_gm_china 1-Sep-2010\n* smh-biz-article-1.html  http://www.smh.com.au/business/the-boards-next-fear-the-female-quota-20100106-lteq.html\n* news-com-au-home.html   http://www.news.com.au/\t11-Jan-2010\n* google-ipod.html\t\t  http://www.google.com/search?hl=en&q=ipod&aq=f&oq=&aqi=g10\t11-Jan-2010\n* yahoo-jp.html\t\t\t  http://www.yahoo.co.jp/index.html\t12-Jan-2010\n* baidu-cn-home.html\t  http://www.baidu.com/ 15-Jul-2010\n* nyt-article-1.html      http://www.nytimes.com/2010/07/26/business/global/26bp.html?hp\n* xwiki-1324.html         https://github.com/jhy/jsoup/issues/1324  15-Feb-2020\n"
  },
  {
    "path": "src/test/resources/htmltests/adopt-1.html",
    "content": "    <b>\n        <b>\n            <p>\n                TEXT-AAA\n<div>\n    <div>\n        <div>\n            <div>\n                <span>\n                    <div>\n                        <div>\n                            <span>\n                                <span>\n                                    <span>\n                                        <div>\n                                            <span>\n                                                <span>\n                                                    <b style=\"\">\n                                                        <span>\n                                                        </span>\n                                                        <b style=\"box-sizing:border-box;font-weight:600\">\n                                                            <span>\n                                                                <span>\n                                                                    <span>\n                                                                        <b style=\"\">\n                                                                            <b style=\"box-sizing:border-box;font-weight:600\">\n                                                                                <span>\n                                                                                    <span>\n                                                                                        <span>\n                                                                                            <b>\n                                                                                                <b>\n                                                                                                    </b>\n                                                                                            </b>\n                                                                                        </span>\n                                                                                        <span>TEXT-BBB</span>\n                                                                                        <span>\n                                                                                            <b style=\"box-sizing:border-box;font-weight:600\">\n                                                                                                <b style=\"box-sizing:border-box;font-weight:600\">\n                                                                                                </b>\n                                                                                            </b>\n                                                                                        </span>\n                                                                                    </span>\n                                                                                </span>\n                                                                            </b>\n                                                                        </b>\n                                                                    </span>\n                                                                </span>\n                                                            </span>\n                                                            <span>\n                                                                <br>\n                                                                <span>\n                                                                    <span>TEXT-CCC</span>\n                                                                </span>\n                                                            </span>\n                                                        </b></b>\n                                                </span>\n                                            </span>\n                                        </div>\n                                        <div>\n                                            TEXT-DDD\n                                        </div>\n                                    </span>\n                                </span>\n                            </span>\n                </span>\n            </div>\n        </div>\n        </span>\n    </div>\n"
  },
  {
    "path": "src/test/resources/htmltests/basehref.html",
    "content": "<html>\n<head>\n  <title>Base HREF test</title>\n  <base href=\"https://example.com/path/file.html?query\">\n</head>\n<body>\n<a href=\"./anotherfile.html\">Link</a>.\n</body>\n</html>"
  },
  {
    "path": "src/test/resources/htmltests/charset-base.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n  <base href=\"http://example.com\" />\n</head>\n<body>\n<img src=\"/foo.jpg\" />\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/comments.html",
    "content": "﻿\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- so -->\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<!-- what -->\n<html xml:lang=\"en\" lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n<!-- now -->\n<head>\n  <!-- then -->\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"/>\n  <title>A Certain Kind of Test</title>\n</head>\n<body><h1>Hello</h1></body>\n(There is a UTF8 hidden BOM at the top of this file.)\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/escapes-across-buffer.html",
    "content": "<html>\n<body>\n&aaaa;&aaab;&aaac;&aaad;&aaae;&aaaf;&aaag;&aaah;&aaai;&aaba;&aabb;&aabc;&aabd;&aabe;&aabf;&aabg;&aabh;&aabi;&aaca;&aacb;&aacc;&aacd;&aace;&aacf;&aacg;&aach;&aaci;&aada;&aadb;&aadc;&aadd;&aade;&aadf;&aadg;&aadh;&aadi;&aaea;&aaeb;&aaec;&aaed;&aaee;&aaef;&aaeg;&aaeh;&aaei;&aafa;&aafb;&aafc;&aafd;&aafe;&aaff;&aafg;&aafh;&aafi;&aaga;&aagb;&aagc;&aagd;&aage;&aagf;&aagg;&aagh;&aagi;&aaha;&aahb;&aahc;&aahd;&aahe;&aahf;&aahg;&aahh;&aahi;&aaia;&aaib;&aaic;&aaid;&aaie;&aaif;&aaig;&aaih;&aaii;&abaa;&abab;&abac;&abad;&abae;&abaf;&abag;&abah;&abai;&abba;&abbb;&abbc;&abbd;&abbe;&abbf;&abbg;&abbh;&abbi;&abca;&abcb;&abcc;&abcd;&abce;&abcf;&abcg;&abch;&abci;&abda;&abdb;&abdc;&abdd;&abde;&abdf;&abdg;&abdh;&abdi;&abea;&abeb;&abec;&abed;&abee;&abef;&abeg;&abeh;&abei;&abfa;&abfb;&abfc;&abfd;&abfe;&abff;&abfg;&abfh;&abfi;&abga;&abgb;&abgc;&abgd;&abge;&abgf;&abgg;&abgh;&abgi;&abha;&abhb;&abhc;&abhd;&abhe;&abhf;&abhg;&abhh;&abhi;&abia;&abib;&abic;&abid;&abie;&abif;&abig;&abih;&abii;&acaa;&acab;&acac;&acad;&acae;&acaf;&acag;&acah;&acai;&acba;&acbb;&acbc;&acbd;&acbe;&acbf;&acbg;&acbh;&acbi;&acca;&accb;&accc;&accd;&acce;&accf;&accg;&acch;&acci;&acda;&acdb;&acdc;&acdd;&acde;&acdf;&acdg;&acdh;&acdi;&acea;&aceb;&acec;&aced;&acee;&acef;&aceg;&aceh;&acei;&acfa;&acfb;&acfc;&acfd;&acfe;&acff;&acfg;&acfh;&acfi;&acga;&acgb;&acgc;&acgd;&acge;&acgf;&acgg;&acgh;&acgi;&acha;&achb;&achc;&achd;&ache;&achf;&achg;&achh;&achi;&acia;&acib;&acic;&acid;&acie;&acif;&acig;&acih;&acii;&adaa;&adab;&adac;&adad;&adae;&adaf;&adag;&adah;&adai;&adba;&adbb;&adbc;&adbd;&adbe;&adbf;&adbg;&adbh;&adbi;&adca;&adcb;&adcc;&adcd;&adce;&adcf;&adcg;&adch;&adci;&adda;&addb;&addc;&addd;&adde;&addf;&addg;&addh;&addi;&adea;&adeb;&adec;&aded;&adee;&adef;&adeg;&adeh;&adei;&adfa;&adfb;&adfc;&adfd;&adfe;&adff;&adfg;&adfh;&adfi;&adga;&adgb;&adgc;&adgd;&adge;&adgf;&adgg;&adgh;&adgi;&adha;&adhb;&adhc;&adhd;&adhe;&adhf;&adhg;&adhh;&adhi;&adia;&adib;&adic;&adid;&adie;&adif;&adig;&adih;&adii;&aeaa;&aeab;&aeac;&aead;&aeae;&aeaf;&aeag;&aeah;&aeai;&aeba;&aebb;&aebc;&aebd;&aebe;&aebf;&aebg;&aebh;&aebi;&aeca;&aecb;&aecc;&aecd;&aece;&aecf;&aecg;&aech;&aeci;&aeda;&aedb;&aedc;&aedd;&aede;&aedf;&aedg;&aedh;&aedi;&aeea;&aeeb;&aeec;&aeed;&aeee;&aeef;&aeeg;&aeeh;&aeei;&aefa;&aefb;&aefc;&aefd;&aefe;&aeff;&aefg;&aefh;&aefi;&aega;&aegb;&aegc;&aegd;&aege;&aegf;&aegg;&aegh;&aegi;&aeha;&aehb;&aehc;&aehd;&aehe;&aehf;&aehg;&aehh;&aehi;&aeia;&aeib;&aeic;&aeid;&aeie;&aeif;&aeig;&aeih;&aeii;&afaa;&afab;&afac;&afad;&afae;&afaf;&afag;&afah;&afai;&afba;&afbb;&afbc;&afbd;&afbe;&afbf;&afbg;&afbh;&afbi;&afca;&afcb;&afcc;&afcd;&afce;&afcf;&afcg;&afch;&afci;&afda;&afdb;&afdc;&afdd;&afde;&afdf;&afdg;&afdh;&afdi;&afea;&afeb;&afec;&afed;&afee;&afef;&afeg;&afeh;&afei;&affa;&affb;&affc;&affd;&affe;&afff;&affg;&affh;&affi;&afga;&afgb;&afgc;&afgd;&afge;&afgf;&afgg;&afgh;&afgi;&afha;&afhb;&afhc;&afhd;&afhe;&afhf;&afhg;&afhh;&afhi;&afia;&afib;&afic;&afid;&afie;&afif;&afig;&afih;&afii;&agaa;&agab;&agac;&agad;&agae;&agaf;&agag;&agah;&agai;&agba;&agbb;&agbc;&agbd;&agbe;&agbf;&agbg;&agbh;&agbi;&agca;&agcb;&agcc;&agcd;&agce;&agcf;&agcg;&agch;&agci;&agda;&agdb;&agdc;&agdd;&agde;&agdf;&agdg;&agdh;&agdi;&agea;&ageb;&agec;&aged;&agee;&agef;&ageg;&ageh;&agei;&agfa;&agfb;&agfc;&agfd;&agfe;&agff;&agfg;&agfh;&agfi;&agga;&aggb;&aggc;&aggd;&agge;&aggf;&aggg;&aggh;&aggi;&agha;&aghb;&aghc;&aghd;&aghe;&aghf;&aghg;&aghh;&aghi;&agia;&agib;&agic;&agid;&agie;&agif;&agig;&agih;&agii;&ahaa;&ahab;&ahac;&ahad;&ahae;&ahaf;&ahag;&ahah;&ahai;&ahba;&ahbb;&ahbc;&ahbd;&ahbe;&ahbf;&ahbg;&ahbh;&ahbi;&ahca;&ahcb;&ahcc;&ahcd;&ahce;&ahcf;&ahcg;&ahch;&ahci;&ahda;&ahdb;&ahdc;&ahdd;&ahde;&ahdf;&ahdg;&ahdh;&ahdi;&ahea;&aheb;&ahec;&ahed;&ahee;&ahef;&aheg;&aheh;&ahei;&ahfa;&ahfb;&ahfc;&ahfd;&ahfe;&ahff;&ahfg;&ahfh;&ahfi;&ahga;&ahgb;&ahgc;&ahgd;&ahge;&ahgf;&ahgg;&ahgh;&ahgi;&ahha;&ahhb;&ahhc;&ahhd;&ahhe;&ahhf;&ahhg;&ahhh;&ahhi;&ahia;&ahib;&ahic;&ahid;&ahie;&ahif;&ahig;&ahih;&ahii;&aiaa;&aiab;&aiac;&aiad;&aiae;&aiaf;&aiag;&aiah;&aiai;&aiba;&aibb;&aibc;&aibd;&aibe;&aibf;&aibg;&aibh;&aibi;&aica;&aicb;&aicc;&aicd;&aice;&aicf;&aicg;&aich;&aici;&aida;&aidb;&aidc;&aidd;&aide;&aidf;&aidg;&aidh;&aidi;&aiea;&aieb;&aiec;&aied;&aiee;&aief;&aieg;&aieh;&aiei;&aifa;&aifb;&aifc;&aifd;&aife;&aiff;&aifg;&aifh;&aifi;&aiga;&aigb;&aigc;&aigd;&aige;&aigf;&aigg;&aigh;&aigi;&aiha;&aihb;&aihc;&aihd;&aihe;&aihf;&aihg;&aihh;&aihi;&aiia;&aiib;&aiic;&aiid;&aiie;&aiif;&aiig;&aiih;&aiii;&baaa;&baab;&baac;&baad;&baae;&baaf;&baag;&baah;&baai;&baba;&babb;&babc;&babd;&babe;&babf;&babg;&babh;&babi;&baca;&bacb;&bacc;&bacd;&bace;&bacf;&bacg;&bach;&baci;&bada;&badb;&badc;&badd;&bade;&badf;&badg;&badh;&badi;&baea;&baeb;&baec;&baed;&baee;&baef;&baeg;&baeh;&baei;&bafa;&bafb;&bafc;&bafd;&bafe;&baff;&bafg;&bafh;&bafi;&baga;&bagb;&bagc;&bagd;&bage;&bagf;&bagg;&bagh;&bagi;&baha;&bahb;&bahc;&bahd;&bahe;&bahf;&bahg;&bahh;&bahi;&baia;&baib;&baic;&baid;&baie;&baif;&baig;&baih;&baii;&bbaa;&bbab;&bbac;&bbad;&bbae;&bbaf;&bbag;&bbah;&bbai;&bbba;&bbbb;&bbbc;&bbbd;&bbbe;&bbbf;&bbbg;&bbbh;&bbbi;&bbca;&bbcb;&bbcc;&bbcd;&bbce;&bbcf;&bbcg;&bbch;&bbci;&bbda;&bbdb;&bbdc;&bbdd;&bbde;&bbdf;&bbdg;&bbdh;&bbdi;&bbea;&bbeb;&bbec;&bbed;&bbee;&bbef;&bbeg;&bbeh;&bbei;&bbfa;&bbfb;&bbfc;&bbfd;&bbfe;&bbff;&bbfg;&bbfh;&bbfi;&bbga;&bbgb;&bbgc;&bbgd;&bbge;&bbgf;&bbgg;&bbgh;&bbgi;&bbha;&bbhb;&bbhc;&bbhd;&bbhe;&bbhf;&bbhg;&bbhh;&bbhi;&bbia;&bbib;&bbic;&bbid;&bbie;&bbif;&bbig;&bbih;&bbii;&bcaa;&bcab;&bcac;&bcad;&bcae;&bcaf;&bcag;&bcah;&bcai;&bcba;&bcbb;&bcbc;&bcbd;&bcbe;&bcbf;&bcbg;&bcbh;&bcbi;&bcca;&bccb;&bccc;&bccd;&bcce;&bccf;&bccg;&bcch;&bcci;&bcda;&bcdb;&bcdc;&bcdd;&bcde;&bcdf;&bcdg;&bcdh;&bcdi;&bcea;&bceb;&bcec;&bced;&bcee;&bcef;&bceg;&bceh;&bcei;&bcfa;&bcfb;&bcfc;&bcfd;&bcfe;&bcff;&bcfg;&bcfh;&bcfi;&bcga;&bcgb;&bcgc;&bcgd;&bcge;&bcgf;&bcgg;&bcgh;&bcgi;&bcha;&bchb;&bchc;&bchd;&bche;&bchf;&bchg;&bchh;&bchi;&bcia;&bcib;&bcic;&bcid;&bcie;&bcif;&bcig;&bcih;&bcii;&bdaa;&bdab;&bdac;&bdad;&bdae;&bdaf;&bdag;&bdah;&bdai;&bdba;&bdbb;&bdbc;&bdbd;&bdbe;&bdbf;&bdbg;&bdbh;&bdbi;&bdca;&bdcb;&bdcc;&bdcd;&bdce;&bdcf;&bdcg;&bdch;&bdci;&bdda;&bddb;&bddc;&bddd;&bdde;&bddf;&bddg;&bddh;&bddi;&bdea;&bdeb;&bdec;&bded;&bdee;&bdef;&bdeg;&bdeh;&bdei;&bdfa;&bdfb;&bdfc;&bdfd;&bdfe;&bdff;&bdfg;&bdfh;&bdfi;&bdga;&bdgb;&bdgc;&bdgd;&bdge;&bdgf;&bdgg;&bdgh;&bdgi;&bdha;&bdhb;&bdhc;&bdhd;&bdhe;&bdhf;&bdhg;&bdhh;&bdhi;&bdia;&bdib;&bdic;&bdid;&bdie;&bdif;&bdig;&bdih;&bdii;&beaa;&beab;&beac;&bead;&beae;&beaf;&beag;&beah;&beai;&beba;&bebb;&bebc;&bebd;&bebe;&bebf;&bebg;&bebh;&bebi;&beca;&becb;&becc;&becd;&bece;&becf;&becg;&bech;&beci;&beda;&bedb;&bedc;&bedd;&bede;&bedf;&bedg;&bedh;&bedi;&beea;&beeb;&beec;&beed;&beee;&beef;&beeg;&beeh;&beei;&befa;&befb;&befc;&befd;&befe;&beff;&befg;&befh;&befi;&bega;&begb;&begc;&begd;&bege;&begf;&begg;&begh;&begi;&beha;&behb;&behc;&behd;&behe;&behf;&behg;&behh;&behi;&beia;&beib;&beic;&beid;&beie;&beif;&beig;&beih;&beii;&bfaa;&bfab;&bfac;&bfad;&bfae;&bfaf;&bfag;&bfah;&bfai;&bfba;&bfbb;&bfbc;&bfbd;&bfbe;&bfbf;&bfbg;&bfbh;&bfbi;&bfca;&bfcb;&bfcc;&bfcd;&bfce;&bfcf;&bfcg;&bfch;&bfci;&bfda;&bfdb;&bfdc;&bfdd;&bfde;&bfdf;&bfdg;&bfdh;&bfdi;&bfea;&bfeb;&bfec;&bfed;&bfee;&bfef;&bfeg;&bfeh;&bfei;&bffa;&bffb;&bffc;&bffd;&bffe;&bfff;&bffg;&bffh;&bffi;&bfga;&bfgb;&bfgc;&bfgd;&bfge;&bfgf;&bfgg;&bfgh;&bfgi;&bfha;&bfhb;&bfhc;&bfhd;&bfhe;&bfhf;&bfhg;&bfhh;&bfhi;&bfia;&bfib;&bfic;&bfid;&bfie;&bfif;&bfig;&bfih;&bfii;&bgaa;&bgab;&bgac;&bgad;&bgae;&bgaf;&bgag;&bgah;&bgai;&bgba;&bgbb;&bgbc;&bgbd;&bgbe;&bgbf;&bgbg;&bgbh;&bgbi;&bgca;&bgcb;&bgcc;&bgcd;&bgce;&bgcf;&bgcg;&bgch;&bgci;&bgda;&bgdb;&bgdc;&bgdd;&bgde;&bgdf;&bgdg;&bgdh;&bgdi;&bgea;&bgeb;&bgec;&bged;&bgee;&bgef;&bgeg;&bgeh;&bgei;&bgfa;&bgfb;&bgfc;&bgfd;&bgfe;&bgff;&bgfg;&bgfh;&bgfi;&bgga;&bggb;&bggc;&bggd;&bgge;&bggf;&bggg;&bggh;&bggi;&bgha;&bghb;&bghc;&bghd;&bghe;&bghf;&bghg;&bghh;&bghi;&bgia;&bgib;&bgic;&bgid;&bgie;&bgif;&bgig;&bgih;&bgii;&bhaa;&bhab;&bhac;&bhad;&bhae;&bhaf;&bhag;&bhah;&bhai;&bhba;&bhbb;&bhbc;&bhbd;&bhbe;&bhbf;&bhbg;&bhbh;&bhbi;&bhca;&bhcb;&bhcc;&bhcd;&bhce;&bhcf;&bhcg;&bhch;&bhci;&bhda;&bhdb;&bhdc;&bhdd;&bhde;&bhdf;&bhdg;&bhdh;&bhdi;&bhea;&bheb;&bhec;&bhed;&bhee;&bhef;&bheg;&bheh;&bhei;&bhfa;&bhfb;&bhfc;&bhfd;&bhfe;&bhff;&bhfg;&bhfh;&bhfi;&bhga;&bhgb;&bhgc;&bhgd;&bhge;&bhgf;&bhgg;&bhgh;&bhgi;&bhha;&bhhb;&bhhc;&bhhd;&bhhe;&bhhf;&bhhg;&bhhh;&bhhi;&bhia;&bhib;&bhic;&bhid;&bhie;&bhif;&bhig;&bhih;&bhii;&biaa;&biab;&biac;&biad;&biae;&biaf;&biag;&biah;&biai;&biba;&bibb;&bibc;&bibd;&bibe;&bibf;&bibg;&bibh;&bibi;&bica;&bicb;&bicc;&bicd;&bice;&bicf;&bicg;&bich;&bici;&bida;&bidb;&bidc;&bidd;&bide;&bidf;&bidg;&bidh;&bidi;&biea;&bieb;&biec;&bied;&biee;&bief;&bieg;&bieh;&biei;&bifa;&bifb;&bifc;&bifd;&bife;&biff;&bifg;&bifh;&bifi;&biga;&bigb;&bigc;&bigd;&bige;&bigf;&bigg;&bigh;&bigi;&biha;&bihb;&bihc;&bihd;&bihe;&bihf;&bihg;&bihh;&bihi;&biia;&biib;&biic;&biid;&biie;&biif;&biig;&biih;&biii;&caaa;&caab;&caac;&caad;&caae;&caaf;&caag;&caah;&caai;&caba;&cabb;&cabc;&cabd;&cabe;&cabf;&cabg;&cabh;&cabi;&caca;&cacb;&cacc;&cacd;&cace;&cacf;&cacg;&cach;&caci;&cada;&cadb;&cadc;&cadd;&cade;&cadf;&cadg;&cadh;&cadi;&caea;&caeb;&caec;&caed;&caee;&caef;&caeg;&caeh;&caei;&cafa;&cafb;&cafc;&cafd;&cafe;&caff;&cafg;&cafh;&cafi;&caga;&cagb;&cagc;&cagd;&cage;&cagf;&cagg;&cagh;&cagi;&caha;&cahb;&cahc;&cahd;&cahe;&cahf;&cahg;&cahh;&cahi;&caia;&caib;&caic;&caid;&caie;&caif;&caig;&caih;&caii;&cbaa;&cbab;&cbac;&cbad;&cbae;&cbaf;&cbag;&cbah;&cbai;&cbba;&cbbb;&cbbc;&cbbd;&cbbe;&cbbf;&cbbg;&cbbh;&cbbi;&cbca;&cbcb;&cbcc;&cbcd;&cbce;&cbcf;&cbcg;&cbch;&cbci;&cbda;&cbdb;&cbdc;&cbdd;&cbde;&cbdf;&cbdg;&cbdh;&cbdi;&cbea;&cbeb;&cbec;&cbed;&cbee;&cbef;&cbeg;&cbeh;&cbei;&cbfa;&cbfb;&cbfc;&cbfd;&cbfe;&cbff;&cbfg;&cbfh;&cbfi;&cbga;&cbgb;&cbgc;&cbgd;&cbge;&cbgf;&cbgg;&cbgh;&cbgi;&cbha;&cbhb;&cbhc;&cbhd;&cbhe;&cbhf;&cbhg;&cbhh;&cbhi;&cbia;&cbib;&cbic;&cbid;&cbie;&cbif;&cbig;&cbih;&cbii;&ccaa;&ccab;&ccac;&ccad;&ccae;&ccaf;&ccag;&ccah;&ccai;&ccba;&ccbb;&ccbc;&ccbd;&ccbe;&ccbf;&ccbg;&ccbh;&ccbi;&ccca;&cccb;&cccc;&cccd;&ccce;&cccf;&cccg;&ccch;&ccci;&ccda;&ccdb;&ccdc;&ccdd;&ccde;&ccdf;&ccdg;&ccdh;&ccdi;&ccea;&cceb;&ccec;&cced;&ccee;&ccef;&cceg;&cceh;&ccei;&ccfa;&ccfb;&ccfc;&ccfd;&ccfe;&ccff;&ccfg;&ccfh;&ccfi;&ccga;&ccgb;&ccgc;&ccgd;&ccge;&ccgf;&ccgg;&ccgh;&ccgi;&ccha;&cchb;&cchc;&cchd;&cche;&cchf;&cchg;&cchh;&cchi;&ccia;&ccib;&ccic;&ccid;&ccie;&ccif;&ccig;&ccih;&ccii;&cdaa;&cdab;&cdac;&cdad;&cdae;&cdaf;&cdag;&cdah;&cdai;&cdba;&cdbb;&cdbc;&cdbd;&cdbe;&cdbf;&cdbg;&cdbh;&cdbi;&cdca;&cdcb;&cdcc;&cdcd;&cdce;&cdcf;&cdcg;&cdch;&cdci;&cdda;&cddb;&cddc;&cddd;&cdde;&cddf;&cddg;&cddh;&cddi;&cdea;&cdeb;&cdec;&cded;&cdee;&cdef;&cdeg;&cdeh;&cdei;&cdfa;&cdfb;&cdfc;&cdfd;&cdfe;&cdff;&cdfg;&cdfh;&cdfi;&cdga;&cdgb;&cdgc;&cdgd;&cdge;&cdgf;&cdgg;&cdgh;&cdgi;&cdha;&cdhb;&cdhc;&cdhd;&cdhe;&cdhf;&cdhg;&cdhh;&cdhi;&cdia;&cdib;&cdic;&cdid;&cdie;&cdif;&cdig;&cdih;&cdii;&ceaa;&ceab;&ceac;&cead;&ceae;&ceaf;&ceag;&ceah;&ceai;&ceba;&cebb;&cebc;&cebd;&cebe;&cebf;&cebg;&cebh;&cebi;&ceca;&cecb;&cecc;&cecd;&cece;&cecf;&cecg;&cech;&ceci;&ceda;&cedb;&cedc;&cedd;&cede;&cedf;&cedg;&cedh;&cedi;&ceea;&ceeb;&ceec;&ceed;&ceee;&ceef;&ceeg;&ceeh;&ceei;&cefa;&cefb;&cefc;&cefd;&cefe;&ceff;&cefg;&cefh;&cefi;&cega;&cegb;&cegc;&cegd;&cege;&cegf;&cegg;&cegh;&cegi;&ceha;&cehb;&cehc;&cehd;&cehe;&cehf;&cehg;&cehh;&cehi;&ceia;&ceib;&ceic;&ceid;&ceie;&ceif;&ceig;&ceih;&ceii;&cfaa;&cfab;&cfac;&cfad;&cfae;&cfaf;&cfag;&cfah;&cfai;&cfba;&cfbb;&cfbc;&cfbd;&cfbe;&cfbf;&cfbg;&cfbh;&cfbi;&cfca;&cfcb;&cfcc;&cfcd;&cfce;&cfcf;&cfcg;&cfch;&cfci;&cfda;&cfdb;&cfdc;&cfdd;&cfde;&cfdf;&cfdg;&cfdh;&cfdi;&cfea;&cfeb;&cfec;&cfed;&cfee;&cfef;&cfeg;&cfeh;&cfei;&cffa;&cffb;&cffc;&cffd;&cffe;&cfff;&cffg;&cffh;&cffi;&cfga;&cfgb;&cfgc;&cfgd;&cfge;&cfgf;&cfgg;&cfgh;&cfgi;&cfha;&cfhb;&cfhc;&cfhd;&cfhe;&cfhf;&cfhg;&cfhh;&cfhi;&cfia;&cfib;&cfic;&cfid;&cfie;&cfif;&cfig;&cfih;&cfii;&cgaa;&cgab;&cgac;&cgad;&cgae;&cgaf;&cgag;&cgah;&cgai;&cgba;&cgbb;&cgbc;&cgbd;&cgbe;&cgbf;&cgbg;&cgbh;&cgbi;&cgca;&cgcb;&cgcc;&cgcd;&cgce;&cgcf;&cgcg;&cgch;&cgci;&cgda;&cgdb;&cgdc;&cgdd;&cgde;&cgdf;&cgdg;&cgdh;&cgdi;&cgea;&cgeb;&cgec;&cged;&cgee;&cgef;&cgeg;&cgeh;&cgei;&cgfa;&cgfb;&cgfc;&cgfd;&cgfe;&cgff;&cgfg;&cgfh;&cgfi;&cgga;&cggb;&cggc;&cggd;&cgge;&cggf;&cggg;&cggh;&cggi;&cgha;&cghb;&cghc;&cghd;&cghe;&cghf;&cghg;&cghh;&cghi;&cgia;&cgib;&cgic;&cgid;&cgie;&cgif;&cgig;&cgih;&cgii;&chaa;&chab;&chac;&chad;&chae;&chaf;&chag;&chah;&chai;&chba;&chbb;&chbc;&chbd;&chbe;&chbf;&chbg;&chbh;&chbi;&chca;&chcb;&chcc;&chcd;&chce;&chcf;&chcg;&chch;&chci;&chda;&chdb;&chdc;&chdd;&chde;&chdf;&chdg;&chdh;&chdi;&chea;&cheb;&chec;&ched;&chee;&chef;&cheg;&cheh;&chei;&chfa;&chfb;&chfc;&chfd;&chfe;&chff;&chfg;&chfh;&chfi;&chga;&chgb;&chgc;&chgd;&chge;&chgf;&chgg;&chgh;&chgi;&chha;&chhb;&chhc;&chhd;&chhe;&chhf;&chhg;&chhh;&chhi;&chia;&chib;&chic;&chid;&chie;&chif;&chig;&chih;&chii;&ciaa;&ciab;&ciac;&ciad;&ciae;&ciaf;&ciag;&ciah;&ciai;&ciba;&cibb;&cibc;&cibd;&cibe;&cibf;&cibg;&cibh;&cibi;&cica;&cicb;&cicc;&cicd;&cice;&cicf;&cicg;&cich;&cici;&cida;&cidb;&cidc;&cidd;&cide;&cidf;&cidg;&cidh;&cidi;&ciea;&cieb;&ciec;&cied;&ciee;&cief;&cieg;&cieh;&ciei;&cifa;&cifb;&cifc;&cifd;&cife;&ciff;&cifg;&cifh;&cifi;&ciga;&cigb;&cigc;&cigd;&cige;&cigf;&cigg;&cigh;&cigi;&ciha;&cihb;&cihc;&cihd;&cihe;&cihf;&cihg;&cihh;&cihi;&ciia;&ciib;&ciic;&ciid;&ciie;&ciif;&ciig;&ciih;&ciii;&daaa;&daab;&daac;&daad;&daae;&daaf;&daag;&daah;&daai;&daba;&dabb;&dabc;&dabd;&dabe;&dabf;&dabg;&dabh;&dabi;&daca;&dacb;&dacc;&dacd;&dace;&dacf;&dacg;&dach;&daci;&dada;&dadb;&dadc;&dadd;&dade;&dadf;&dadg;&dadh;&dadi;&daea;&daeb;&daec;&daed;&daee;&daef;&daeg;&daeh;&daei;&dafa;&dafb;&dafc;&dafd;&dafe;&daff;&dafg;&dafh;&dafi;&daga;&dagb;&dagc;&dagd;&dage;&dagf;&dagg;&dagh;&dagi;&daha;&dahb;&dahc;&dahd;&dahe;&dahf;&dahg;&dahh;&dahi;&daia;&daib;&daic;&daid;&daie;&daif;&daig;&daih;&daii;&dbaa;&dbab;&dbac;&dbad;&dbae;&dbaf;&dbag;&dbah;&dbai;&dbba;&dbbb;&dbbc;&dbbd;&dbbe;&dbbf;&dbbg;&dbbh;&dbbi;&dbca;&dbcb;&dbcc;&dbcd;&dbce;&dbcf;&dbcg;&dbch;&dbci;&dbda;&dbdb;&dbdc;&dbdd;&dbde;&dbdf;&dbdg;&dbdh;&dbdi;&dbea;&dbeb;&dbec;&dbed;&dbee;&dbef;&dbeg;&dbeh;&dbei;&dbfa;&dbfb;&dbfc;&dbfd;&dbfe;&dbff;&dbfg;&dbfh;&dbfi;&dbga;&dbgb;&dbgc;&dbgd;&dbge;&dbgf;&dbgg;&dbgh;&dbgi;&dbha;&dbhb;&dbhc;&dbhd;&dbhe;&dbhf;&dbhg;&dbhh;&dbhi;&dbia;&dbib;&dbic;&dbid;&dbie;&dbif;&dbig;&dbih;&dbii;&dcaa;&dcab;&dcac;&dcad;&dcae;&dcaf;&dcag;&dcah;&dcai;&dcba;&dcbb;&dcbc;&dcbd;&dcbe;&dcbf;&dcbg;&dcbh;&dcbi;&dcca;&dccb;&dccc;&dccd;&dcce;&dccf;&dccg;&dcch;&dcci;&dcda;&dcdb;&dcdc;&dcdd;&dcde;&dcdf;&dcdg;&dcdh;&dcdi;&dcea;&dceb;&dcec;&dced;&dcee;&dcef;&dceg;&dceh;&dcei;&dcfa;&dcfb;&dcfc;&dcfd;&dcfe;&dcff;&dcfg;&dcfh;&dcfi;&dcga;&dcgb;&dcgc;&dcgd;&dcge;&dcgf;&dcgg;&dcgh;&dcgi;&dcha;&dchb;&dchc;&dchd;&dche;&dchf;&dchg;&dchh;&dchi;&dcia;&dcib;&dcic;&dcid;&dcie;&dcif;&dcig;&dcih;&dcii;&ddaa;&ddab;&ddac;&ddad;&ddae;&ddaf;&ddag;&ddah;&ddai;&ddba;&ddbb;&ddbc;&ddbd;&ddbe;&ddbf;&ddbg;&ddbh;&ddbi;&ddca;&ddcb;&ddcc;&ddcd;&ddce;&ddcf;&ddcg;&ddch;&ddci;&ddda;&dddb;&dddc;&dddd;\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/form-tests.html",
    "content": "<title>Form test</title>\n\n<form id=\"login\" action=\"/CookieServlet\" method=\"post\">\n  <input name=\"setCookies\" value=\"1\" type=\"hidden\">\n  <input name=\"loc\" value=\"/EchoServlet\" type=\"hidden\">\n\n  Username: <input name=\"username\"><br>\n  Password: <input name=\"password\" type=\"password\">\n</form>\n\n<form id=\"login2\" action=\"/CookieServlet\" method=\"post\">\n  <input name=\"setCookies\" value=\"1\" type=\"hidden\">\n\n  Username: <input name=\"username\"><br>\n  Password: <input name=\"password\" type=\"password\">\n</form>\n"
  },
  {
    "path": "src/test/resources/htmltests/gzip.html",
    "content": "<title>Gzip test</title>\n\n<p>This is a gzipped HTML file.</p>\n\n"
  },
  {
    "path": "src/test/resources/htmltests/large.html",
    "content": "<html><head><title>Large HTML</title></head>\n\n<body>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p>\n\n<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p><b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut eu diam at pede suscipit sodales</b>. Nulla quis sem at nibh elementum imperdiet. <b>Maecenas aliquet mollis lectus</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Nulla ut felis in purus aliquam imperdiet</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Duis sagittis ipsum</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Ut fringilla</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p>\n\n<p>Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Nulla quam</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Sed cursus ante dapibus diam</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. <b>Mauris massa</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. <i>Maecenas mattis</i>. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Vestibulum sapien</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Proin sodales libero eget ante</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p>\n\n<p>Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p>\n\n<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. </p>\n\n<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p><b>Maecenas mattis</b>. Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <i>Pellentesque nibh</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p><b>Vestibulum sapien</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Sed convallis tristique sem</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Vestibulum tincidunt malesuada tellus</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p><b>Morbi mi</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <i>Morbi mi</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent libero</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Vestibulum lacinia arcu eget nulla</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Sed nisi</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Proin quam</b>. Praesent blandit dolor. <b>Proin quam</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. <b>Curabitur sit amet mauris</b>. Aenean laoreet. </p>\n\n<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p>\n\n<p><b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <i>Nulla ut felis in purus aliquam imperdiet</i>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p>\n\n<p>Aenean quam. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur tortor</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p>\n\n<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Ut fringilla</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p>\n\n<p><i>Fusce nec tellus sed augue semper porta</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Maecenas mattis</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p>Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Suspendisse potenti</b>. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Integer euismod lacus luctus magna</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p>\n\n<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <b>Duis sagittis ipsum</b>. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. </p>\n\n<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Nam nec ante</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. <i>Sed convallis tristique sem</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Sed non quam</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p><b>Morbi in dui quis est pulvinar ullamcorper</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>\n\n<p>Integer nec odio. Praesent libero. <i>Integer lacinia sollicitudin massa</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Curabitur sodales ligula in libero</i>. Integer euismod lacus luctus magna. </p>\n\n<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p><i>Nunc feugiat mi a tellus consequat imperdiet</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Cras metus</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Sed pretium blandit orci</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p><b>Fusce ac turpis quis ligula lacinia aliquet</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Aenean quam</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Praesent blandit dolor</i>. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Praesent blandit dolor</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Nulla quis sem at nibh elementum imperdiet</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Curabitur sodales ligula in libero</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Curabitur sodales ligula in libero</i>. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Ut fringilla</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p><b>Cras metus</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur sodales ligula in libero</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Integer euismod lacus luctus magna</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Ut eu diam at pede suscipit sodales</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur tortor</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Mauris massa</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Etiam ultrices</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Pellentesque nibh</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. </p>\n\n<p>Ut fringilla. <i>Nulla quis sem at nibh elementum imperdiet</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla facilisi</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. <b>Suspendisse potenti</b>. Morbi in ipsum sit amet pede facilisis laoreet. <b>Etiam ultrices</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p><b>In vel mi sit amet augue congue elementum</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p><b>Praesent libero</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Integer nec odio</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Maecenas mattis</b>. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Vestibulum tincidunt malesuada tellus</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Integer lacinia sollicitudin massa</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed cursus ante dapibus diam</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. <b>Vestibulum lacinia arcu eget nulla</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p>Vestibulum sapien. Proin quam. Etiam ultrices. <i>Mauris ipsum</i>. Suspendisse in justo eu magna luctus suscipit. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p>Proin sodales libero eget ante. Nulla quam. <b>Cras metus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Sed pretium blandit orci</i>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. <b>Duis sagittis ipsum</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Curabitur sodales ligula in libero</b>. Mauris ipsum. </p>\n\n<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Duis sagittis ipsum</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. </p>\n\n<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Integer euismod lacus luctus magna</i>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p>\n\n<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Nulla quam</i>. Curabitur tortor. </p>\n\n<p><b>Praesent libero</b>. Pellentesque nibh. Aenean quam. <b>Mauris massa</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p>\n\n<p><b>Pellentesque nibh</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p>\n\n<p>Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Vestibulum sapien</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p>\n\n<p>Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <b>Vestibulum lacinia arcu eget nulla</b>. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Curabitur sodales ligula in libero</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Suspendisse potenti</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed lectus</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Nulla facilisi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. <i>Curabitur sit amet mauris</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. <b>Sed cursus ante dapibus diam</b>. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. </p>\n\n<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p>\n\n<p><b>Integer lacinia sollicitudin massa</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. Sed cursus ante dapibus diam. </p>\n\n<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <i>Ut eu diam at pede suscipit sodales</i>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p>\n\n<p>Aenean quam. In scelerisque sem at dolor. <b>Vestibulum lacinia arcu eget nulla</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Curabitur sodales ligula in libero</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Curabitur sodales ligula in libero</i>. Nam nec ante. </p>\n\n<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Pellentesque nibh</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p>\n\n<p>Praesent blandit dolor. <i>Nulla facilisi</i>. Sed non quam. In vel mi sit amet augue congue elementum. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Sed non quam</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <b>Mauris massa</b>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <i>Nulla ut felis in purus aliquam imperdiet</i>. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Mauris massa</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Ut fringilla</i>. Integer lacinia sollicitudin massa. Cras metus. <i>Vestibulum sapien</i>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p>\n\n<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Ut ultrices ultrices enim</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p>\n\n<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. <i>Ut eu diam at pede suscipit sodales</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. </p>\n\n<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Nam nec ante</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p>\n\n<p>Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Sed cursus ante dapibus diam. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed nisi. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Mauris ipsum</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <i>Curabitur sodales ligula in libero</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p><b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p>\n\n<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Suspendisse in justo eu magna luctus suscipit</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Cras metus</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p>\n\n<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p>\n\n<p>Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. <i>Sed lectus</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Sed non quam</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Morbi mi</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Integer lacinia sollicitudin massa</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p>\n\n<p>Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Duis sagittis ipsum</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Quisque volutpat condimentum velit</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Curabitur sit amet mauris</b>. Nulla quam. </p>\n\n<p><b>Curabitur sit amet mauris</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Sed non quam</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Curabitur sit amet mauris</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p>\n\n<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p>Curabitur tortor. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Pellentesque nibh</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p>\n\n<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <i>Pellentesque nibh</i>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Mauris ipsum</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Morbi mi</b>. Ut eu diam at pede suscipit sodales. <b>Proin sodales libero eget ante</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Proin sodales libero eget ante</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Vestibulum lacinia arcu eget nulla</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <b>Maecenas mattis</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Aenean quam</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Mauris ipsum</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. Cras metus. <b>Sed non quam</b>. Sed aliquet risus a tortor. Integer id quam. <i>Ut fringilla</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Sed aliquet risus a tortor</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Sed non quam</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Sed pretium blandit orci</b>. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <i>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</i>. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. <b>Duis sagittis ipsum</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>In scelerisque sem at dolor</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p>\n\n<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Praesent blandit dolor</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>\n\n<p>Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent libero</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <i>Nulla ut felis in purus aliquam imperdiet</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Duis sagittis ipsum</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <i>Pellentesque nibh</i>. Proin quam. Etiam ultrices. <i>Curabitur tortor</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p>\n\n<p>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <i>Curabitur sit amet mauris</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Nulla quam</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Sed aliquet risus a tortor</i>. Integer nec odio. Praesent libero. <i>Sed aliquet risus a tortor</i>. Sed cursus ante dapibus diam. Sed nisi. </p>\n\n<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Sed cursus ante dapibus diam</i>. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p>\n\n<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed lectus</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. <i>Praesent blandit dolor</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Nulla facilisi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p><b>Duis sagittis ipsum</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <i>Praesent mauris</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p>\n\n<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Ut fringilla</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. <b>Praesent blandit dolor</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p><b>Cras metus</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p>\n\n<p><i>Cras metus</i>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Integer nec odio</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Nulla quis sem at nibh elementum imperdiet</b>. Pellentesque nibh. <b>Sed cursus ante dapibus diam</b>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Aenean quam</b>. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Aenean quam</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Etiam ultrices</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Morbi mi</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Aenean quam. In scelerisque sem at dolor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p><b>Curabitur sodales ligula in libero</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. </p>\n\n<p><b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Suspendisse in justo eu magna luctus suscipit</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Cras metus</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p>\n\n<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Praesent mauris</b>. Pellentesque nibh. </p>\n\n<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Fusce nec tellus sed augue semper porta</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Pellentesque nibh</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p>\n\n<p>Sed non quam. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Ut ultrices ultrices enim</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Sed convallis tristique sem</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p>\n\n<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. <i>Ut fringilla</i>. Ut ultrices ultrices enim. <b>Integer euismod lacus luctus magna</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. </p>\n\n<p>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Curabitur sit amet mauris</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Sed pretium blandit orci</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p><b>Maecenas mattis</b>. Quisque volutpat condimentum velit. <i>Sed dignissim lacinia nunc</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nam nec ante</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nam nec ante</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p><b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Sed aliquet risus a tortor</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <i>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</i>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Ut fringilla</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. <i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Maecenas aliquet mollis lectus</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Duis sagittis ipsum</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p>\n\n<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Proin ut ligula vel nunc egestas porttitor</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. Ut fringilla. <i>Nulla quis sem at nibh elementum imperdiet</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p>\n\n<p>Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer lacinia sollicitudin massa</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Proin sodales libero eget ante</i>. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Ut eu diam at pede suscipit sodales</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p><b>Integer nec odio</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Praesent libero</i>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p><i>Curabitur tortor</i>. Nam nec ante. <i>Curabitur tortor</i>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur tortor</i>. Ut fringilla. Suspendisse potenti. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Curabitur tortor</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Integer euismod lacus luctus magna</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. <i>Sed non quam</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Sed aliquet risus a tortor</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Ut eu diam at pede suscipit sodales</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Vestibulum sapien</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p><b>Ut ultrices ultrices enim</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Ut eu diam at pede suscipit sodales</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Sed pretium blandit orci</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. </p>\n\n<p><i>Duis sagittis ipsum</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <b>Sed convallis tristique sem</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Morbi in ipsum sit amet pede facilisis laoreet. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p><b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Praesent blandit dolor</i>. Nulla quam. Aenean laoreet. <b>Curabitur sit amet mauris</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Integer id quam</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. </p>\n\n<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Sed cursus ante dapibus diam</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <i>Pellentesque nibh</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Suspendisse potenti</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <b>Ut ultrices ultrices enim</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Integer nec odio. Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. <i>Aenean laoreet</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p><i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Ut eu diam at pede suscipit sodales</i>. Aenean quam. </p>\n\n<p>In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Aenean quam</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p>\n\n<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p>\n\n<p><b>Ut fringilla</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <b>Sed non quam</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. <i>Sed lectus</i>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Integer id quam</b>. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Duis sagittis ipsum. <i>Ut eu diam at pede suscipit sodales</i>. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. <b>Nulla quis sem at nibh elementum imperdiet</b>. Sed dignissim lacinia nunc. Curabitur tortor. <i>Nulla ut felis in purus aliquam imperdiet</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. <b>Praesent mauris</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. <i>Proin ut ligula vel nunc egestas porttitor</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer id quam</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. <i>Sed aliquet risus a tortor</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Vivamus consectetuer risus et tortor</b>. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Vestibulum lacinia arcu eget nulla</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. </p>\n\n<p>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. Etiam ultrices. </p>\n\n<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. </p>\n\n<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Morbi mi</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Mauris massa</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur sodales ligula in libero</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Quisque volutpat condimentum velit</b>. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Integer lacinia sollicitudin massa</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Maecenas aliquet mollis lectus</b>. Duis sagittis ipsum. Praesent mauris. <i>Nulla quam</i>. Fusce nec tellus sed augue semper porta. Mauris massa. <i>Aenean laoreet</i>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Fusce nec tellus sed augue semper porta</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p>Morbi in dui quis est pulvinar ullamcorper. <b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. Integer lacinia sollicitudin massa. <i>Etiam ultrices</i>. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Mauris massa</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Aenean quam</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p>\n\n<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <i>Proin quam</i>. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Proin sodales libero eget ante</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p>\n\n<p><b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Sed cursus ante dapibus diam</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur sodales ligula in libero</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Vestibulum sapien</i>. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Sed aliquet risus a tortor</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <i>Cras metus</i>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. </p>\n\n<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed cursus ante dapibus diam</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <i>Mauris massa</i>. Fusce ac turpis quis ligula lacinia aliquet. <i>Sed cursus ante dapibus diam</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. </p>\n\n<p>Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Mauris ipsum</b>. Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Vestibulum sapien</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Morbi mi</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. </p>\n\n<p>Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Nulla quis sem at nibh elementum imperdiet</b>. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. <i>Nulla ut felis in purus aliquam imperdiet</i>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p><b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Maecenas mattis</i>. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Ut ultrices ultrices enim. <i>Suspendisse potenti</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Ut ultrices ultrices enim</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p>\n\n<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Integer nec odio</b>. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Nulla quis sem at nibh elementum imperdiet</i>. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p>\n\n<p><i>Sed aliquet risus a tortor</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Curabitur sit amet mauris</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Proin sodales libero eget ante</i>. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p><b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed pretium blandit orci</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Vestibulum lacinia arcu eget nulla</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <b>Suspendisse in justo eu magna luctus suscipit</b>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <i>Morbi in dui quis est pulvinar ullamcorper</i>. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. </p>\n\n<p>Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <i>Duis sagittis ipsum</i>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Curabitur sodales ligula in libero</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p>\n\n<p>Praesent blandit dolor. <i>In scelerisque sem at dolor</i>. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Nam nec ante</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>In vel mi sit amet augue congue elementum</i>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p>\n\n<p><b>Aenean laoreet</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Maecenas aliquet mollis lectus</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. <b>Praesent libero</b>. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Praesent libero</i>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent libero</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Pellentesque nibh</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <i>Curabitur tortor</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Integer euismod lacus luctus magna. </p>\n\n<p><b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Ut fringilla</i>. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p>\n\n<p>Cras metus. Sed aliquet risus a tortor. <b>Vestibulum tincidunt malesuada tellus</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Integer id quam</b>. Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Sed pretium blandit orci</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. <i>Sed pretium blandit orci</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Duis sagittis ipsum</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Sed convallis tristique sem</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p>\n\n<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p><b>Proin sodales libero eget ante</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Proin sodales libero eget ante</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. <i>Sed nisi</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <i>Maecenas mattis</i>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <b>Nulla facilisi</b>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p><b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Suspendisse in justo eu magna luctus suscipit</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. <b>Vestibulum tincidunt malesuada tellus</b>. Aenean laoreet. <i>Suspendisse in justo eu magna luctus suscipit</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p>\n\n<p>Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Praesent mauris</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Pellentesque nibh</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. </p>\n\n<p>Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. </p>\n\n<p>Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Vestibulum sapien</i>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p>Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Integer id quam</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Proin sodales libero eget ante</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <i>Duis sagittis ipsum</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p><b>Maecenas mattis</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Mauris ipsum</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Nam nec ante</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p><b>Integer euismod lacus luctus magna</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p><i>Integer euismod lacus luctus magna</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. <b>Sed dignissim lacinia nunc</b>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p>Vestibulum sapien. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Etiam ultrices</b>. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p>\n\n<p><b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. <i>Ut ultrices ultrices enim</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Integer nec odio</b>. Fusce nec tellus sed augue semper porta. <i>Sed aliquet risus a tortor</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Sed nisi</b>. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. <b>Sed nisi</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Curabitur sodales ligula in libero</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Pellentesque nibh</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <i>Ut fringilla</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. </p>\n\n<p>Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Nulla facilisi</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p><b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <b>Sed dignissim lacinia nunc</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Ut fringilla</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Ut fringilla</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. </p>\n\n<p>Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Ut fringilla</i>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. <i>Etiam ultrices</i>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. <i>Integer id quam</i>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Ut fringilla. </p>\n\n<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Ut fringilla</b>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p>\n\n<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Etiam ultrices</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <b>Aenean laoreet</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Sed aliquet risus a tortor</i>. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. <b>Nulla ut felis in purus aliquam imperdiet</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <i>Nulla ut felis in purus aliquam imperdiet</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Nulla quis sem at nibh elementum imperdiet</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Mauris massa</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Ut fringilla</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <b>Suspendisse potenti</b>. Morbi in ipsum sit amet pede facilisis laoreet. <b>Vestibulum sapien</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Sed lectus</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. <b>Maecenas aliquet mollis lectus</b>. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p>Vestibulum sapien. Proin quam. Etiam ultrices. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. <b>Suspendisse in justo eu magna luctus suscipit</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. <i>Integer euismod lacus luctus magna</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>\n\n<p>Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Vivamus consectetuer risus et tortor</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <i>Nulla ut felis in purus aliquam imperdiet</i>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed dignissim lacinia nunc</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. <b>Mauris ipsum</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. </p>\n\n<p>Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Proin sodales libero eget ante</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. <b>Praesent libero</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p><i>Duis sagittis ipsum</i>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. </p>\n\n<p>Suspendisse potenti. <b>Sed convallis tristique sem</b>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Proin quam</i>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Nunc feugiat mi a tellus consequat imperdiet</i>. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p>Curabitur tortor. <b>Fusce nec tellus sed augue semper porta</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <b>Sed dignissim lacinia nunc</b>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Nulla facilisi</b>. Sed lectus. Integer euismod lacus luctus magna. </p>\n\n<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p><b>In vel mi sit amet augue congue elementum</b>. Nulla facilisi. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Praesent blandit dolor</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Nulla facilisi</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. </p>\n\n<p><i>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Integer id quam</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Sed pretium blandit orci</b>. Sed nisi. </p>\n\n<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Maecenas aliquet mollis lectus</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <b>Mauris massa</b>. Aenean quam. </p>\n\n<p>In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Fusce nec tellus sed augue semper porta</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. </p>\n\n<p>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. </p>\n\n<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nam nec ante</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. <b>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</b>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. </p>\n\n<p>Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p>\n\n<p>Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Sed cursus ante dapibus diam</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Nam nec ante</b>. Etiam ultrices. </p>\n\n<p>Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <i>Mauris ipsum</i>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Mauris ipsum</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Proin sodales libero eget ante. Nulla quam. <b>Cras metus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p><b>Sed aliquet risus a tortor</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Aenean laoreet</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p><b>Ut eu diam at pede suscipit sodales</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. <i>Sed cursus ante dapibus diam</i>. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Nulla facilisi</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. </p>\n\n<p>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <i>Sed non quam</i>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. </p>\n\n<p>Sed nisi. <i>Integer id quam</i>. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Vivamus consectetuer risus et tortor</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. <b>Duis sagittis ipsum</b>. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>In scelerisque sem at dolor</b>. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Vestibulum lacinia arcu eget nulla</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <i>Ut ultrices ultrices enim</i>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. </p>\n\n<p>Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p><b>Vivamus consectetuer risus et tortor</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p><i>Duis sagittis ipsum</i>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. <i>Mauris massa</i>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Sed convallis tristique sem</i>. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <i>Proin quam</i>. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. </p>\n\n<p>Aenean laoreet. <b>Curabitur sit amet mauris</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Integer lacinia sollicitudin massa</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Vivamus consectetuer risus et tortor. <i>Vestibulum tincidunt malesuada tellus</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. </p>\n\n<p>Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Nulla ut felis in purus aliquam imperdiet</b>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. Ut fringilla. <b>Mauris ipsum</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. </p>\n\n<p><b>Etiam ultrices</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. <b>Sed non quam</b>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. </p>\n\n<p><b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p>\n\n<p><b>Morbi mi</b>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <b>Ut eu diam at pede suscipit sodales</b>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Fusce nec tellus sed augue semper porta</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. </p>\n\n<p>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Maecenas mattis</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p>Etiam ultrices. <b>Nulla facilisi</b>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</i>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p>Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. <b>Cras metus</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. </p>\n\n<p>Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <b>Vivamus consectetuer risus et tortor</b>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. </p>\n\n<p>Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Nulla quis sem at nibh elementum imperdiet</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. </p>\n\n<p>Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. <b>Ut fringilla</b>. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Sed lectus</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. <i>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</i>. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. </p>\n\n<p><b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>\n\n<p>Integer nec odio. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. </p>\n\n<p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. <b>Vestibulum lacinia arcu eget nulla</b>. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <i>Vestibulum lacinia arcu eget nulla</i>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. </p>\n\n<p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <b>Maecenas mattis</b>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <b>Suspendisse potenti</b>. Integer euismod lacus luctus magna. </p>\n\n<p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Sed non quam</b>. Integer lacinia sollicitudin massa. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Cras metus. </p>\n\n<p>Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. </p>\n\n<p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Proin sodales libero eget ante</i>. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. <i>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</i>. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. </p>\n\n<p>Mauris massa. <b>Vivamus consectetuer risus et tortor</b>. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Duis sagittis ipsum</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p>Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Maecenas mattis</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <i>Sed cursus ante dapibus diam</i>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p><i>Aenean quam</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Mauris ipsum</i>. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. <i>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui</i>. Sed aliquet risus a tortor. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. <b>Integer id quam</b>. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. <b>Ut eu diam at pede suscipit sodales</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. <b>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</b>. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed cursus ante dapibus diam</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Duis sagittis ipsum</b>. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. <b>Curabitur tortor</b>. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. </p>\n\n<p>Nulla facilisi. <i>Duis sagittis ipsum</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <i>Curabitur sodales ligula in libero</i>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <i>In scelerisque sem at dolor</i>. Sed non quam. </p>\n\n<p>In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. </p>\n\n<p>Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. <b>Integer lacinia sollicitudin massa</b>. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p>\n\n<p>Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <b>Sed pretium blandit orci</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <b>Sed pretium blandit orci</b>. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p>Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <b>Sed nisi</b>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. </p>\n\n<p>Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Pellentesque nibh</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. <i>Sed convallis tristique sem</i>. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. </p>\n\n<p>Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. <b>Vestibulum sapien</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Nunc feugiat mi a tellus consequat imperdiet</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. </p>\n\n<p><i>Mauris ipsum</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <i>Vestibulum sapien</i>. Integer lacinia sollicitudin massa. Cras metus. <b>In vel mi sit amet augue congue elementum</b>. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <b>Integer lacinia sollicitudin massa</b>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. </p>\n\n<p>Sed pretium blandit orci. <i>Curabitur sit amet mauris</i>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Integer lacinia sollicitudin massa</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. </p>\n\n<p>Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Integer nec odio</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. <b>Praesent mauris</b>. Curabitur tortor. Pellentesque nibh. </p>\n\n<p>Aenean quam. In scelerisque sem at dolor. <b>Mauris massa</b>. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <b>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</b>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. </p>\n\n<p>Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. </p>\n\n<p>Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. <i>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</i>. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <i>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</i>. Integer id quam. </p>\n\n<p>Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. <i>Suspendisse in justo eu magna luctus suscipit</i>. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Integer id quam</b>. Ut eu diam at pede suscipit sodales. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. </p>\n\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. <i>Integer id quam</i>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. <i>Ut eu diam at pede suscipit sodales</i>. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <b>Fusce ac turpis quis ligula lacinia aliquet</b>. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. </p>\n\n<p>Sed lectus. Integer euismod lacus luctus magna. <b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Etiam ultrices</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. </p>\n\n<p>Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Proin sodales libero eget ante</i>. Duis sagittis ipsum. </p>\n\n<p>Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. </p>\n\n<p>Sed convallis tristique sem. <b>Praesent mauris</b>. Proin ut ligula vel nunc egestas porttitor. <i>Duis sagittis ipsum</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. <b>Maecenas mattis</b>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. </p>\n\n<p><b>Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh</b>. Vestibulum sapien. Proin quam. Etiam ultrices. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Vestibulum sapien</b>. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. </p>\n\n<p>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. <i>Proin quam</i>. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <i>Morbi in ipsum sit amet pede facilisis laoreet</i>. Nulla quam. </p>\n\n<p><b>Vestibulum tincidunt malesuada tellus</b>. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. <i>Praesent blandit dolor</i>. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. <i>Integer lacinia sollicitudin massa</i>. Integer nec odio. </p>\n\n<p>Praesent libero. <b>Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo</b>. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. </p>\n\n<p>Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>In scelerisque sem at dolor</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. <b>Curabitur tortor</b>. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p>\n\n<p>Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. <i>In scelerisque sem at dolor</i>. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. </p>\n\n<p>Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <i>Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa</i>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Suspendisse in justo eu magna luctus suscipit</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p>\n\n<p>Cras metus. <b>Donec lacus nunc, viverra nec, blandit vel, egestas et, augue</b>. Sed aliquet risus a tortor. Integer id quam. <b>Sed non quam</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. <b>Ut ultrices ultrices enim</b>. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. <b>Sed aliquet risus a tortor</b>. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. </p>\n\n<p>Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <i>Ut ultrices ultrices enim</i>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. </p>\n\n<p><b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. <b>Sed nisi</b>. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. </p>\n\n<p>Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. <i>Fusce nec tellus sed augue semper porta</i>. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. </p>\n\n<p>Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. <i>Curabitur sodales ligula in libero</i>. Integer euismod lacus luctus magna. <i>Maecenas mattis</i>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. <i>Nam nec ante</i>. Vestibulum tincidunt malesuada tellus. </p>\n\n<p>Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. <b>Vestibulum tincidunt malesuada tellus</b>. Sed aliquet risus a tortor. Integer id quam. <b>Morbi in ipsum sit amet pede facilisis laoreet</b>. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n<p>Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. <b>Integer lacinia sollicitudin massa</b>. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Maecenas aliquet mollis lectus. <b>Nulla quam</b>. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. </p>\n\n<p>Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. <b>Nulla ut felis in purus aliquam imperdiet</b>. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. </p>\n\n<p>Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. <b>Curabitur sodales ligula in libero</b>. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. </p>\n\n<p>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. <b>Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos</b>. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. </p>\n\n<p><b>Nam nec ante</b>. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <i>Sed convallis tristique sem</i>. Praesent blandit dolor. Sed non quam. <b>Etiam ultrices</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. <b>Proin quam</b>. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. </p>\n\n<p>Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. </p>\n\n<p>Nulla ut felis in purus aliquam imperdiet. <b>Aenean laoreet</b>. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. <b>Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede</b>. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. <i>Integer lacinia sollicitudin massa</i>. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. </p>\n\n<p><b>Lorem ipsum dolor sit amet, consectetur adipiscing elit</b>. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. <b>Duis sagittis ipsum</b>. Maecenas mattis. Sed convallis tristique sem. <b>Sed dignissim lacinia nunc</b>. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. </p>\n\n<p><i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. <i>Curabitur sodales ligula in libero</i>. Nulla facilisi. <i>Vestibulum lacinia arcu eget nulla</i>. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. <b>Nam nec ante</b>. Proin quam. </p>\n\n<p id=xy>Ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. <b>Ut fringilla</b>. Sed non quam. <b>Ut fringilla</b>. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. </p>\n\n<p>VESTIBULUM tincidunt malesuada tellus. Ut ultrices ultrices enim. <i>Proin quam</i>. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. <b>Sed non quam</b>. Integer id quam. <b>Curabitur sit amet mauris</b>. Morbi mi. <b>In vel mi sit amet augue congue elementum</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. </p>\n\n</body></html>\n"
  },
  {
    "path": "src/test/resources/htmltests/lowercase-charset-test.html",
    "content": "<!doctype html>\n<html lang=en>\n<meta charset=utf-8>\n<title>FormData: has</title>\n<script src=\"../../resources/testharness.js\"></script>\n<script src=\"../../resources/testharnessreport.js\"></script>\n    <link rel=\"help\" href=\"https://xhr.spec.whatwg.org/#dom-formdata-get\" />\n    <link rel=\"help\" href=\"https://xhr.spec.whatwg.org/#dom-formdata-getall\" />\n<div id=\"log\"></div>\n<form id=\"form\">\n    <input type=\"hidden\" name=\"key\" value=\"value1\">\n    <input type=\"hidden\" name=\"key\" value=\"value2\">\n</form>\n<form id=\"empty-form\" />\n"
  },
  {
    "path": "src/test/resources/htmltests/medium.html",
    "content": "<html>\n <head>\n  <title>Medium HTML</title>\n </head>\n <body>\n  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.</p>\n  <p>Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. <i>Lorem ipsum dolor sit amet, consectetur adipiscing elit</i>. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. <b>Aenean quam</b>. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh.</p>\n  <p>Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. <b>Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis</b>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna.</p>\n  <p>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. <b>Suspendisse in justo eu magna luctus suscipit</b>. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi.</p>\n  <p>Integer lacinia sollicitudin massa. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. <b>Morbi in dui quis est pulvinar ullamcorper</b>. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. <b>Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue</b>. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque.</p>\n  <p>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum.</p>\n  <p><i>Proin sodales libero eget ante</i>. Praesent mauris. Fusce nec tellus sed augue semper porta. <i>Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula</i>. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis.</p>\n  <p>Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. <b>Sed convallis tristique sem</b>. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet.</p>\n  <p>Vestibulum sapien. Proin quam. <i>Fusce ac turpis quis ligula lacinia aliquet</i>. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. <b>Suspendisse potenti</b>. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue.</p>\n  <p>Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. <b>Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam</b>. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam.</p>\n  <p>Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio.</p>\n </body>\n</html>"
  },
  {
    "path": "src/test/resources/htmltests/meta-charset-1.html",
    "content": "<html>\n<head><meta charset=\"gb2312\"></head>\n<body></body>\n</html>"
  },
  {
    "path": "src/test/resources/htmltests/meta-charset-2.html",
    "content": "<html>\n<head></head>\n<body></body>\n</html>"
  },
  {
    "path": "src/test/resources/htmltests/meta-charset-3.html",
    "content": "<html>\n<head></head>\n<body>新</body>\n</html>"
  },
  {
    "path": "src/test/resources/htmltests/namespaces.xhtml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\" xml:lang=\"en\" lang=\"en\">\n\t<head>\n\t\t<title>Cover</title>\n\t\t<style type=\"text/css\">\n\t\t\timg{\n\t\t\t\tmax-width:100%;\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t<figure id=\"cover-image\">\n\t\t<img src=\"covers/9781449328030_lrg.jpg\" alt=\"First Edition\"/>\n\t</figure>\n\t<epub:title id=\"epubTitle\">Check</epub:title>\n\t<x:section xmlns:x=\"urn:test\">\n\t\t<x:title id=\"xTitle\">Another</x:title>\n\t\tSection Text.\n\t</x:section>\n\n\t<svg xmlns=\"http://www.w3.org/2000/svg\">\n\t\t<path>\n\t\t\t123\n\t\t\t<clip xmlns=\"http://example.com/clip\">456</clip>\n\t\t</path>\n\t</svg>\n\t<picture><img /></picture>\n\n\t</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/table-invalid-elements.html",
    "content": "<html>\n<body>\n<table>\n  <tr>\n    <td>\n      <table>\n        <tr>\n          <!--Comment-->\n          <table>\n            <p>Why am I here?</p>\n            </tr>\n          </table>\n          </td>\n        </tr>\n      </table>\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/table-polymer-template.html",
    "content": "<table style=\"width:100%\">\n  <thead>\n    <th></th>\n    <template is=\"dom-repeat\" items=\"{{fields_}}\" as=\"field\">\n      <th class$=\"[[cssClassForCell_(field.key, showAll_)]]\">{{field.name}}</th>\n    </template>\n  </thead>\n  <tbody>\n    <tr></tr>\n    <template is=\"dom-repeat\" items=\"{{volumes_}}\" as=\"volume\">\n      <tr class$=\"[[cssClassForVolume_(volume.inactive, showInactive_)]]\">\n        <td style=\"background-color: white !important;\">\n          <paper-button on-tap=\"onRetrieveButtonClick_\" raised>Retrieve</paper-button>\n        </td>\n        <template is=\"dom-repeat\" items=\"{{volume.data}}\" as=\"field\">\n          <td class$=\"[[cssClassForCell_(field.key, showAll_)]]\">\n            {{field.value}}\n            <template is=\"dom-if\" if=\"{{field.edit_link}}\">\n              <iron-icon\n                icon=\"icons:create\" on-click=\"changeOwner_\"\n                data-id$=\"{{volume.id}}\" data-owner$=\"{{field.value}}\">\n              </iron-icon>\n            </template>\n          </td>\n        </template>\n      </tr>\n    </template>\n  </tbody>\n</table>\n"
  },
  {
    "path": "src/test/resources/htmltests/test-rss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<rss version=\"2.0\">\n\n  <channel>\n    <title>jsoup RSS news</title>\n    <link>https://jsoup.org/</link>\n    <description>jsoup HTML Parser News</description>\n    <item>\n      <title>jsoup Java HTML Parser release 1.12.2</title>\n      <link>https://jsoup.org/news/release-1.12.2</link>\n      <description>jsoup 1.12.2 is out now, with a great set of improvements to connections, W3C interoperability, speed, and many bug fixes.</description>\n    </item>\n    <item>\n      <title>jsoup Java HTML Parser release 1.12.1</title>\n      <link>https://jsoup.org/news/release-1.12.1</link>\n      <description>jsoup 1.12.1 is out now, with a great set of improvements to connections, W3C interoperability, speed, and many bug fixes.</description>\n    </item>\n  </channel>\n\n</rss>\n"
  },
  {
    "path": "src/test/resources/htmltests/upload-form.html",
    "content": "<html>\n<head>\n  <title>Upload Form Test</title>\n</head>\n<body>\n<h1>Upload Form Test</h1>\n\n<form action=\"/EchoServlet\" method=\"POST\" enctype=\"multipart/form-data\" name=\"tidy\" onsubmit=\"return verifySubmit()\">\n  <input type=\"hidden\" name=\"_function\" value=\"tidy\">\n  <label for=\"_file\">Upload:</label>\n  <input name=\"_file\" id=\"_file\" type=\"file\" size=\"40\">\n  <input type=\"submit\">\n</form>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/htmltests/xml-charset.xml",
    "content": "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<data></data>\n"
  },
  {
    "path": "src/test/resources/htmltests/xml-test.xml",
    "content": "<doc><val>One<val>Two</val>Three</val></doc>\n"
  },
  {
    "path": "src/test/resources/local-cert/README.md",
    "content": "This directory contains resources for a self-signed TLS certificate, used in jsoup's local integration tests.\n\nCreate the certificate:\n\n```sh\nopenssl genrsa 2048 > server.key\nchmod 400 server.key\nopenssl req -new -x509 -config cert.conf -nodes -sha256 -days 36135 -key server.key -out server.crt\n```\n\nCreate the Java key store. Used by server, and trusted by client, in `TestServer.java`:\n```sh\nopenssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name jsoup -passout pass:hunter2\nkeytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.pfx -deststoretype PKCS12  -srcstorepass hunter2 -deststorepass hunter2\n```\n"
  },
  {
    "path": "src/test/resources/local-cert/cert.conf",
    "content": "[ req ]\ndistinguished_name  = subject\nx509_extensions     = x509_ext\nprompt              = no\n\n[ subject ]\ncommonName          = jsoup test server\n\n[ x509_ext ]\nsubjectAltName      = @alternate_names\n\n[ alternate_names ]\nDNS.1               = localhost\n"
  },
  {
    "path": "src/test/resources/local-cert/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC/zCCAeegAwIBAgIUKEHmb0P5j+5mNjNk/PTdW6t9UTcwDQYJKoZIhvcNAQEL\nBQAwHDEaMBgGA1UEAwwRanNvdXAgdGVzdCBzZXJ2ZXIwIBcNMjMxMTAyMjM0OTE1\nWhgPMjEyMjEwMDkyMzQ5MTVaMBwxGjAYBgNVBAMMEWpzb3VwIHRlc3Qgc2VydmVy\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvkvtYwy7jnSPYM59EVsR\nSjTO8WsXHVs/UJ+Ns+7RiTeb2hUOd4lh38TOh9Yri/7WI5Ejif64FL6b1KEWRe9+\n60QKIOB0+7DUpnXomisD6TytwV8R8BSEZ4vLbMUVizr95Ze+w6SzMPshSvHBMIbU\nRimtmY1jBglHytETRBjO1etG120R1M45GJfxV8rIDOgM6FksOnWLQeKzeGKBf0vs\n5MlTz/GDs/YpXydg779QOmJAQWj78EMdetwmUPwnpC0kaO3dnlD+mzDrfeSkorrp\n5UKij1k4s2tG+E/VIskGyuc/MSU6dc8/ECzuK7c/UjpUz9ohSfLwhSGdjnx0qjXm\nkwIDAQABozcwNTAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFAWRk6Jd\nPJrlw3uJKEG7JLku9SwsMA0GCSqGSIb3DQEBCwUAA4IBAQAxEXk5d0ACzaxtOF9+\n/XF3Zt8X/eXxyoQUaG2PyfJkN1rnO7zyx/oPIIAckaZev0eFVwOk3M5K4xxYar/Y\nDqdioKwH8qAy4kk7sdCnTU8jlkUMcFqYCt7rLcDviugjg0VO6bYLrq++oeOuDybs\nM7J3CgzPAppSpRoTgss3bGzHt87rWJ2XcHxbE8Gg2GtoZnFpcSHkx40EdlDWN8dm\n/mZlMxjVFdktz9dpqtR4Q4cAbHETomJOHC2AnhEi3PjuYhGHMbIRgtIg0XX4H/0u\neHVvkb9xJ3SmmdidYTDlOFzLon8NqSZmmt6EDpDio62bDem49jUtnYmxJKXAxhL0\njnwQ\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/local-cert/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+S+1jDLuOdI9g\nzn0RWxFKNM7xaxcdWz9Qn42z7tGJN5vaFQ53iWHfxM6H1iuL/tYjkSOJ/rgUvpvU\noRZF737rRAog4HT7sNSmdeiaKwPpPK3BXxHwFIRni8tsxRWLOv3ll77DpLMw+yFK\n8cEwhtRGKa2ZjWMGCUfK0RNEGM7V60bXbRHUzjkYl/FXysgM6AzoWSw6dYtB4rN4\nYoF/S+zkyVPP8YOz9ilfJ2Dvv1A6YkBBaPvwQx163CZQ/CekLSRo7d2eUP6bMOt9\n5KSiuunlQqKPWTiza0b4T9UiyQbK5z8xJTp1zz8QLO4rtz9SOlTP2iFJ8vCFIZ2O\nfHSqNeaTAgMBAAECggEACY0zFaEqetyD49aJdYkOJZzf9EMtTlZpp6jSioEGuG33\nnysmZj6ZkItG2I+Z8PVyFyfuUjtcTwJAPRx2yzzZsIJiRcMubAG0ssRBUBevoxHe\nINIeSuAkwzPDmqqLycjEvLTwqM5IBkHcqm/XBBIIbpsh8Q6lNUTa+yWiY20hWKBX\n7I+mNg9qTsGkYCthZVBgkpmg3DCCX4l8hraHhev3KgdpaILaDSVqjd1IBwJ9ynJc\nmJ0/pvIVO7dwxJ7t7b+vNp8iJQjPlOZmz6hWKyFMhxnkOcri3OBYcr1JMkVZ38RD\nOjKhaaCnhhSH+IxwLxQQAs//S+EN3l6kOngN5cZ/aQKBgQDiEqp7kT7nAPRMq9Af\nokomKnQIpAuEfOauzH02PGkVYawCulWmr+FqdUZxz5SgPEp55IyTfD6iPaSb6QcO\nQuH3PvtZyVQv1ZrExquvd/3lS/cQwaDzV4YG46fBbw9K72BHkVV7dkxm+0p4Imid\n2XLRqT86difx1etovb7fzMXsCwKBgQDXfNh5Gk250Upyh3+7FDYr7bOvc0l9y/Xn\neODM/yRI3MLaGTUXu90MK50AsOqxedvs4x5NvqG/n2Cr536b9C0tr09CfHeGsOMG\nOEfzxMrRv78ItBF7vLELYz1szi6JEZCeK1whgJ1osrTGWAhWkMTIErh3UOfZGgYG\nqFQGRFP8mQKBgG7FlqNVV+z4mru2tBPMAWkSBCj3uG0ChkXADNo2X4cKhK4Rf0Zd\nh6YSMKIzhC+/Wv6+7eKWTlpQugdq9voV64KqaZ5k98s4bs1cS2N+9/kSb8zWE3co\nu5NEmT4+nM+q2xI2NBx6qpULLEIRGhG+KnRw6XpLyubEWsTHtG8UdyZhAoGAVdm5\nbNYb7VICtQpiyyfMRUgYdGgb+XBO8f9ooINt81Fwl++/BUulT3n4vRO/DSIdio0Z\nv6OZUXyvyQ0blgp8DV1w2G46OIE0kX/OusHGhDY+Z7tF0+RjLMRG7pheVeGXmkxw\nEjDphZLdDsB34fUfUQ6US4UCOa5yhCiAAVcrltECgYEAlYNAELPKAcmWd+4G8Fr6\n07dIgJHZ7W45eZwwUwva9t09J/9d4wq7X4GaX98Jejdeh4nTHnBWX49m6EgQ0ccH\n4jcIvTj61aBuDNiW8p85O5gpBrCneFowFHsPElhG2nFSFhGtIST8fkiy5sBwxMFM\n1nauFIaX8tP0NxQDw+PvdDc=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/printertests/input-1.html",
    "content": "\n<!DOCTYPE html>\n<!-- Some formatting tests -->\n\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>HTML output test</title>\n    <!-- Another comment -->\n</head>\n\n\n<body>\n\n<header>\n    <h1>H1 text</h1> other text <a href=\"/foo/\">link</a> text\n    <div>&nbsp;</div>\n</header>\n\n<section><header><h1>Header</h1></header>Text after header.</section>\n\n  <div>Standalone text in block</div>\n<div><span>content</span></div>\n<div><span></span></div>\n  <div>Text with <b>inline</b> <b>elements</b> in a div.</div>\n  <div>\n      <p>In a para</p>\n      <p>\n          Other in a para.\n      </p>\n      <!-- comment in a div -->\n\n  </div>\n\n<p>\n    Some text.\n</p>\n\n<p>Some text with a<br>line break.</p>\n<p>Some text with a couple<br>\n    <br>line breaks.</p>\n<p>Some text with a couple<br><br>line breaks.</p>\n\n<a><p>A becomes block</p><div>If it has block children.</div></a>\n\n<div>\n    <p><a href=\"/example\"><b>A bold link.</b></a></p>\n    <p>Something <unknown>custom</unknown>.</p>\n</div>\n\n<script>\n    let foo = 2;\n    foo++;\n</script>\n\n<div>\n    <pre>\n        Some preformatted text.\n\n        <span>\n            A span in pre.\n            <pre>\n                Deeper.\n                Still.\n\n            </pre>\n\n        </span>\n    </pre>\n\n</div>\n\n<div>This is some <span>spanned</span> text. <p>Then in a P.</p>And out.</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/printertests/outline-1.html",
    "content": "<!doctype html>\n<!-- Some formatting tests -->\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <title>HTML output test</title>\n  <!-- Another comment -->\n </head>\n <body>\n  <header>\n   <h1>H1 text</h1>\n   other text\n   <a href=\"/foo/\">link</a>\n   text\n   <div>&nbsp;</div>\n  </header>\n  <section>\n   <header>\n    <h1>Header</h1>\n   </header>\n   Text after header.\n  </section>\n  <div>Standalone text in block</div>\n  <div>\n   <span>content</span>\n  </div>\n  <div>\n   <span></span>\n  </div>\n  <div>\n   Text with\n   <b>inline</b>\n   <b>elements</b>\n   in a div.\n  </div>\n  <div>\n   <p>In a para</p>\n   <p>Other in a para.</p>\n   <!-- comment in a div -->\n  </div>\n  <p>Some text.</p>\n  <p>\n   Some text with a\n   <br>\n   line break.\n  </p>\n  <p>\n   Some text with a couple\n   <br>\n   <br>\n   line breaks.\n  </p>\n  <p>\n   Some text with a couple\n   <br>\n   <br>\n   line breaks.\n  </p>\n  <a>\n   <p>A becomes block</p>\n   <div>If it has block children.</div>\n  </a>\n  <div>\n   <p>\n    <a href=\"/example\">\n     <b>A bold link.</b>\n    </a>\n   </p>\n   <p>\n    Something\n    <unknown>custom</unknown>\n    .\n   </p>\n  </div>\n  <script>\n    let foo = 2;\n    foo++;\n</script>\n  <div>\n   <pre>        Some preformatted text.\n\n        <span>\n            A span in pre.\n            <pre>                Deeper.\n                Still.\n\n            </pre>\n\n        </span>\n    </pre>\n  </div>\n  <div>\n   This is some\n   <span>spanned</span>\n   text.\n   <p>Then in a P.</p>\n   And out.\n  </div>\n </body>\n</html>"
  },
  {
    "path": "src/test/resources/printertests/passthru-1.html",
    "content": "<!doctype html>\n<!-- Some formatting tests -->\n\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>HTML output test</title>\n    <!-- Another comment -->\n</head>\n\n\n<body>\n\n<header>\n    <h1>H1 text</h1> other text <a href=\"/foo/\">link</a> text\n    <div>&nbsp;</div>\n</header>\n\n<section><header><h1>Header</h1></header>Text after header.</section>\n\n  <div>Standalone text in block</div>\n<div><span>content</span></div>\n<div><span></span></div>\n  <div>Text with <b>inline</b> <b>elements</b> in a div.</div>\n  <div>\n      <p>In a para</p>\n      <p>\n          Other in a para.\n      </p>\n      <!-- comment in a div -->\n\n  </div>\n\n<p>\n    Some text.\n</p>\n\n<p>Some text with a<br>line break.</p>\n<p>Some text with a couple<br>\n    <br>line breaks.</p>\n<p>Some text with a couple<br><br>line breaks.</p>\n\n<a><p>A becomes block</p><div>If it has block children.</div></a>\n\n<div>\n    <p><a href=\"/example\"><b>A bold link.</b></a></p>\n    <p>Something <unknown>custom</unknown>.</p>\n</div>\n\n<script>\n    let foo = 2;\n    foo++;\n</script>\n\n<div>\n    <pre>        Some preformatted text.\n\n        <span>\n            A span in pre.\n            <pre>                Deeper.\n                Still.\n\n            </pre>\n\n        </span>\n    </pre>\n\n</div>\n\n<div>This is some <span>spanned</span> text. <p>Then in a P.</p>And out.</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/test/resources/printertests/pretty-1.html",
    "content": "<!doctype html>\n<!-- Some formatting tests -->\n<html lang=\"en\">\n <head>\n  <meta charset=\"UTF-8\">\n  <title>HTML output test</title>\n  <!-- Another comment -->\n </head>\n <body>\n  <header>\n   <h1>H1 text</h1>\n   other text <a href=\"/foo/\">link</a> text\n   <div>&nbsp;</div>\n  </header>\n  <section>\n   <header>\n    <h1>Header</h1>\n   </header>\n   Text after header.\n  </section>\n  <div>Standalone text in block</div>\n  <div>\n   <span>content</span>\n  </div>\n  <div>\n   <span></span>\n  </div>\n  <div>\n   Text with <b>inline</b> <b>elements</b> in a div.\n  </div>\n  <div>\n   <p>In a para</p>\n   <p>Other in a para.</p>\n   <!-- comment in a div -->\n  </div>\n  <p>Some text.</p>\n  <p>Some text with a\n   <br>\n   line break.</p>\n  <p>Some text with a couple\n   <br>\n   <br>\n   line breaks.</p>\n  <p>Some text with a couple\n   <br>\n   <br>\n   line breaks.</p>\n  <a>\n   <p>A becomes block</p>\n   <div>If it has block children.</div>\n  </a>\n  <div>\n   <p><a href=\"/example\"><b>A bold link.</b></a></p>\n   <p>Something <unknown>custom</unknown>.</p>\n  </div>\n  <script>\n    let foo = 2;\n    foo++;\n</script>\n  <div>\n   <pre>        Some preformatted text.\n\n        <span>\n            A span in pre.\n            <pre>                Deeper.\n                Still.\n\n            </pre>\n\n        </span>\n    </pre>\n  </div>\n  <div>\n   This is some <span>spanned</span> text.\n   <p>Then in a P.</p>\n   And out.\n  </div>\n </body>\n</html>"
  }
]