Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
5f61a37
refactor(html)!: Rewrite html module
James-LG Jul 6, 2024
c1aec34
feat(html): First few insertion modes
James-LG Jul 15, 2024
eba64cd
feat(html): Parser first pass
James-LG Jul 18, 2024
cf049dd
fix(html): Distinguish start and end tags
James-LG Jul 19, 2024
8987c13
fix(html): Fix first pass
James-LG Jul 20, 2024
9a8a32a
feat(html): First test passing!
James-LG Jul 20, 2024
929c738
test(html): Create html_basic_tests
James-LG Jul 20, 2024
d18f64c
fix(html): Second test passing
James-LG Jul 21, 2024
ce79b5a
feat(html): Support character references
James-LG Jul 28, 2024
be58f1e
feat(html): Handle attributes
James-LG Aug 2, 2024
375024a
fix(html): Add document node
James-LG Aug 4, 2024
a62f774
fix(html): Fixed duplicate first character in attribtues
James-LG Aug 4, 2024
6d64faa
fix(html): Fix named character references bug
James-LG Aug 4, 2024
d7348cb
refactor(test): Reogranize tests into sub dirs
James-LG Aug 4, 2024
a8d489d
feat(html): Support some basic DOCTYPE declarations
James-LG Aug 4, 2024
6b55d3e
fix: Tracked down intermittent error caused by Hash collision in Inde…
James-LG Aug 5, 2024
ad9e333
fix(html): Fix bug in before_html_insertion_mode
James-LG Aug 7, 2024
7e138fe
feat(html): Implement scripts and comments
James-LG Aug 11, 2024
99cb51c
feat(html): Implement RCData tokenizer states
James-LG Sep 1, 2024
2752ee6
feat(html): Lots of in body insertion mode cases
James-LG Sep 2, 2024
b483c61
feat(html): GitHub sample parses
James-LG Sep 8, 2024
3f2a68e
feat(html): Doctype implementations and devcontainer updates
James-LG Sep 9, 2024
08ad632
fix: Fix misnesting issue in script tags
James-LG Nov 10, 2024
56cd170
fix: using_the_rules_for does not temporarily change the insertion mode
James-LG Dec 7, 2024
ea3be15
chore: Add CLAUDE.md
James-LG Feb 26, 2026
0c415be
fix(html): Fix some bugs and create Raw display mode
James-LG Feb 26, 2026
b21fc7a
fix: Complete asserts for html_large_tests
James-LG Feb 26, 2026
6adaea1
fix: All tests passing
James-LG Feb 26, 2026
7c6f87b
chore: Features backlog
James-LG Feb 26, 2026
157f29a
feat: Complete remaining todods
James-LG Feb 26, 2026
c66405f
feat: Foster parenting algorithm
James-LG Feb 26, 2026
c2e1a1c
feat: InHeadNoscript Insertion Mode
James-LG Feb 26, 2026
5b84bfd
feat: Complete Table Insertion Mode Suite
James-LG Feb 26, 2026
e68dab6
feat: InSelect & InSelectInTable Insertion Modes
James-LG Feb 26, 2026
ce0ac46
feat: Frameset Insertion Modes
James-LG Feb 26, 2026
50b68c1
feat: BeforeHtml Insertion Mode
James-LG Feb 26, 2026
32aa752
feat: BeforeHead Insertion Mode
James-LG Feb 26, 2026
ace60e6
feat: InHead insertion mode
James-LG Feb 26, 2026
7932da1
feat: AfterHead insertion mode
James-LG Feb 26, 2026
6d73cdd
fix: Remove shadow root conditional
James-LG Feb 26, 2026
eb7b4a5
feat: InBody NULL character handling (parse error, ignore)
James-LG Feb 26, 2026
76016a5
feat: InBody DOCTYPE handling (parse error, ignore)
James-LG Feb 26, 2026
94e0f60
feat: InBody frameset start tag handling
James-LG Feb 26, 2026
88484af
feat: InBody pre/listing start tags and skip-next-LF mechanism
James-LG Feb 26, 2026
d0e547b
feat: InBody dd/dt start tag handling
James-LG Feb 26, 2026
fabb26b
feat: InBody plaintext start tag handling
James-LG Feb 26, 2026
136a14c
feat: InBody dd/dt end tag handling
James-LG Feb 26, 2026
7a8d248
feat: InBody sarcasm end tag handling
James-LG Feb 26, 2026
886c118
feat: InBody nobr start tag handling
James-LG Feb 26, 2026
d534b09
feat: InBody applet/marquee/object start and end tags
James-LG Feb 26, 2026
2851cae
feat: InBody param/source/track, hr, image, xmp, iframe, noembed/nosc…
James-LG Feb 26, 2026
271d064
feat: InBody rb/rtc, rp/rt, math, and table-related start tags
James-LG Feb 26, 2026
33ec2d5
feat: XPath or and and logical operator evaluation
James-LG Feb 26, 2026
449afc1
feat: XPath value comparisons and node comparisons
James-LG Feb 26, 2026
6ac2e7a
feat: XPath arithmetic expression evaluation (+, -, *, div, idiv, mod…
James-LG Feb 26, 2026
e8eb622
feat: XPath string concatenation operator (||)
James-LG Feb 26, 2026
b2cf27f
feat: XPath for-expression evaluation and variable references
James-LG Feb 26, 2026
6bb4750
feat: XPath let-expression evaluation
James-LG Feb 26, 2026
a77f9dd
feat: XPath quantified expression evaluation (some/every ... satisfies)
James-LG Feb 26, 2026
fe84e2e
feat: XPath if-expression evaluation (if/then/else)
James-LG Feb 26, 2026
7029cc1
feat: XPath arrow operator evaluation (=>)
James-LG Feb 27, 2026
061e4ee
feat: XPath simple map operator evaluation (!)
James-LG Feb 27, 2026
27d0cd6
feat: XPath range operator evaluation (to)
James-LG Feb 27, 2026
ad687c7
feat: XPath union, intersect, and except operator evaluation
James-LG Feb 27, 2026
592f07a
feat: XPath instance-of operator evaluation
James-LG Feb 27, 2026
71b466c
feat: XPath cast-as and castable-as operator evaluation
James-LG Feb 27, 2026
ff99596
feat: XPath postfix expression predicate evaluation
James-LG Feb 27, 2026
40c7376
feat: XPath forward and reverse axis evaluation
James-LG Feb 27, 2026
2128a5e
feat: fn:data/fn:string for PI and comment nodes, comment() kind test
James-LG Feb 27, 2026
e60267d
feat: function item expressions (named refs, inline functions)
James-LG Feb 27, 2026
6be36d5
feat: XPath map constructor evaluation and map-as-function calling
James-LG Feb 27, 2026
e2211c5
feat: XPath array constructor evaluation (square and curly)
James-LG Feb 27, 2026
35b1ef1
feat: XPath unary and postfix lookup operators (?key)
James-LG Feb 27, 2026
ff6f224
feat: URI-qualified function names and unified dispatch
James-LG Feb 27, 2026
dce68d7
feat: XPath name test and wildcard evaluation for node tests
James-LG Feb 27, 2026
353e4d3
feat: XPath kind test evaluation, attribute matching, and display for…
James-LG Feb 27, 2026
621df99
feat: XPath sequence type matching with occurrence indicators
James-LG Feb 27, 2026
b41879a
feat: XPath partial function application via argument placeholders
James-LG Feb 27, 2026
8e07b6d
chore: Context and clear features backlog
James-LG Feb 27, 2026
2881696
chore: New features backlog
James-LG Feb 27, 2026
391e150
feat(html): implement text insertion mode EOF handling per WHATWG 13.…
James-LG Feb 27, 2026
a0bdda5
feat(html): implement comment/doctype handling in after-body insertio…
James-LG Feb 27, 2026
4ff6e76
feat(xpath): support VarRef and ParenthesizedExpr arrow function spec…
James-LG Feb 27, 2026
5bf0525
feat(xpath): support URI-qualified type names in cast/castable expres…
James-LG Feb 27, 2026
666c396
docs: mark cast as URI-qualified type names as done in backlog
James-LG Feb 27, 2026
c2195bc
refactor(xpath): remove stale sorting TODO in apply()
James-LG Feb 27, 2026
4ddb376
docs: mark item sorting as done in backlog
James-LG Feb 27, 2026
1fe2fda
fix(xpath): remove stale TODO for parent:: on attributes, add test
James-LG Feb 27, 2026
253e2de
feat(xpath): implement 30+ built-in functions across all categories
James-LG Feb 27, 2026
90236a5
docs: update backlog with 30+ newly implemented functions
James-LG Feb 27, 2026
793902a
feat(xpath): implement regex string, sequence, and aggregate built-in…
James-LG Feb 27, 2026
a77af36
docs: update backlog with 13 newly implemented functions
James-LG Feb 27, 2026
7ae7c27
feat(xpath): implement numeric, string, and sequence built-in functions
James-LG Feb 27, 2026
f8b7937
docs: update backlog with 14 newly implemented functions
James-LG Feb 27, 2026
6b28fbd
feat(xpath): implement node, accessor, and higher-order built-in func…
James-LG Feb 27, 2026
d94dab6
docs: update backlog with 18 newly implemented functions
James-LG Feb 27, 2026
01eb565
feat(xpath): implement map, array, and math namespace functions
James-LG Feb 27, 2026
a6e654b
docs: update backlog with 42 newly implemented map/array/math functions
James-LG Feb 27, 2026
c48558d
feat(xpath): implement format-number, normalize-unicode, innermost, o…
James-LG Feb 27, 2026
b6486a1
docs: update backlog with 6 newly implemented fn: functions
James-LG Feb 27, 2026
5dd9d44
fix(xpath): improve error handling, add fn:error/fn:trace, rename is_…
James-LG Feb 27, 2026
027c977
docs: update backlog to reflect iteration 12 completions
James-LG Feb 27, 2026
9cfddc7
feat(xpath): implement PINode data model, document-node(element-test)…
James-LG Feb 27, 2026
75ec62d
feat(html): implement HTML fragment parsing algorithm (WHATWG 13.4)
James-LG Feb 27, 2026
ec7e724
feat(html): implement foreign content attribute adjustments (WHATWG 1…
James-LG Feb 27, 2026
14286bf
fix(html): add missing early return in CDATA section state transition
James-LG Feb 27, 2026
dcfdd4d
feat(html): implement SVG element name case correction (WHATWG 13.2.6.5)
James-LG Feb 27, 2026
e744078
feat(html): implement foreign content parsing mode (WHATWG 13.2.6.5)
James-LG Feb 27, 2026
2674478
feat(html): implement foreign attribute namespace assignment (WHATWG …
James-LG Feb 27, 2026
7b400f9
fix(html): implement adoption agency algorithm Step 4.6 scope check (…
James-LG Feb 27, 2026
a7220f1
feat(html): implement duplicate attribute deduplication (WHATWG 13.2.…
James-LG Feb 27, 2026
f442b29
feat(xpath): implement typed function/map/array test runtime validation
James-LG Feb 27, 2026
3303742
feat(xpath): Static function calls
James-LG Feb 28, 2026
2354463
feat(html): implement quirks mode determination and fix tokenizer bugs
James-LG Feb 28, 2026
b11d92b
fix(xpath): implement CommentNode string value and parent navigation
James-LG Feb 28, 2026
55b902d
fix(xpath): instance of and treat as bugs
James-LG Feb 28, 2026
63cfa6d
fix(xpath): func_data func_string error propagation
James-LG Feb 28, 2026
a05c6ce
fix(xpath): DoctypeNode correctness
James-LG Feb 28, 2026
1729cc9
fix(xpath): HtmlNode fixes
James-LG Feb 28, 2026
4bea1b9
fix(xpath): Name tests
James-LG Feb 28, 2026
ed3a8b1
feat(xpath): fn:function_lookup
James-LG Feb 28, 2026
5377c1d
chore: Add lxml tests
James-LG Feb 28, 2026
b968b16
chore: More lxml tests
James-LG Feb 28, 2026
06b1e7f
fix(xpath): ElementNode::text
James-LG Feb 28, 2026
b0f454c
fix(xpath): general_comp
James-LG Feb 28, 2026
d57193e
fix(xpath): AxisStep::eval
James-LG Feb 28, 2026
8efc63e
fix(xpath): NameTest:eval
James-LG Feb 28, 2026
34d1049
fix: lxml tests
James-LG Feb 28, 2026
a5aa9f6
fix: string(@class)
James-LG Mar 2, 2026
4fbd7c4
fix: Final cleanup
James-LG Mar 2, 2026
445d0d4
fix: GitHub action
James-LG Mar 2, 2026
c454b96
perf: Optimizations
James-LG Mar 2, 2026
a6ead57
refactor: ValueComp
James-LG Mar 2, 2026
2b57f88
perf: More optimizations
James-LG Mar 2, 2026
7305973
perf: Even more optimizations
James-LG Mar 2, 2026
a2493a2
perf: Optimus Prime
James-LG Mar 2, 2026
253a239
perf: Batch character insertion
James-LG Mar 2, 2026
75849c5
perf: Tag character improvements
James-LG Mar 2, 2026
0dada4a
perf: Incremental Hash lookup
James-LG Mar 2, 2026
3f79564
perf: HashSet duplicate attribute set
James-LG Mar 2, 2026
ab10e3a
perf: Lots of html parsing improvements
James-LG Mar 2, 2026
9fd723b
perf: XPath evaluation 10-25x faster for // queries
James-LG Mar 2, 2026
4ea423c
perf: Optimize predicate evaluation with constant position fast path
James-LG Mar 3, 2026
2b895c6
chore: Docs
James-LG Mar 3, 2026
e1e45ac
fix: Regression tests and fixes
James-LG Mar 4, 2026
9ae472a
fix: Regression tests
James-LG Mar 6, 2026
8b426d6
fix: Feedback
James-LG Mar 6, 2026
5d8449e
fix: Feedback
James-LG Mar 6, 2026
62c010b
fix: Feedback
James-LG Mar 6, 2026
15c6c85
fix: Feedback
James-LG Mar 6, 2026
7cb856c
fix: Address 14 accepted code review findings
James-LG Mar 6, 2026
bd5dd74
fix: Address 14 accepted code review findings with regression tests
James-LG Mar 8, 2026
2c42a2c
fix: Address 3 accepted code review findings with regression tests
James-LG Mar 8, 2026
55c4001
fix: Address 14 accepted code review findings with regression tests
James-LG Mar 8, 2026
a2bc99d
fix: Address 3 accepted code review findings with regression tests
James-LG Mar 8, 2026
e863ce9
fix: Address 9 accepted code review findings with regression tests
James-LG Mar 10, 2026
a9dbcf5
fix: Address 5 accepted code review findings with regression tests
James-LG Mar 15, 2026
e28b013
fix: Address 12 accepted code review findings with regression tests
James-LG Mar 15, 2026
044484b
fix: Address 13 accepted code review findings with regression tests
James-LG Mar 16, 2026
5527290
fix: Address 24 accepted code review findings with regression tests
James-LG Mar 16, 2026
bbf561d
fix: Address 20 accepted code review findings with regression tests
James-LG Mar 16, 2026
c939da8
fix: Address 15 accepted code review findings with regression tests
James-LG Mar 17, 2026
2cab40e
fix: Address 17 accepted code review findings with regression tests
James-LG Mar 17, 2026
ffdbeae
fix: Address 9 accepted code review findings
James-LG Apr 3, 2026
c7e5b07
fix: Address 11 accepted code review findings with regression tests
James-LG Apr 3, 2026
6c959be
fix: Resolve remaining TODO comments across codebase
James-LG Apr 8, 2026
95d0be8
docs: Update README to reflect current XPath feature coverage
James-LG May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/rust/.devcontainer/base.Dockerfile
# See here for image contents: https://github.com/devcontainers/images/tree/main/src/rust

FROM mcr.microsoft.com/devcontainers/rust:1-1-bullseye
FROM mcr.microsoft.com/devcontainers/rust:1-1-bookworm

# [Optional] Uncomment this section to install additional packages.
# Install Python and uv for lxml reference tests
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends python3-lxml
&& apt-get -y install --no-install-recommends python3-full

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

COPY postCreateCommand.sh /tmp/postCreateCommand.sh
RUN chmod +x /tmp/postCreateCommand.sh

USER vscode

# Install nightly rust
RUN rustup toolchain install nightly

# Install pip
ENV PATH="${PATH}:/home/vscode/.local/bin"
RUN curl https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py \
&& python3 /tmp/get-pip.py \
&& rm /tmp/get-pip.py
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"matklad.rust-analyzer",
"bungcip.better-toml",
"tamasfe.even-better-toml",
"vadimcn.vscode-lldb",
"mutantdino.resourcemonitor",
"ms-azuretools.vscode-docker"
Expand All @@ -33,7 +33,7 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pip install -r tests/lxml_tests/requirements.txt",
"postCreateCommand": "/tmp/postCreateCommand.sh",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}
5 changes: 5 additions & 0 deletions .devcontainer/postCreateCommand.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

# Set up the Python venv for lxml reference tests using uv.
# The Rust lxml tests expect the venv at tests/lxml_tests/.venv/bin/python.
cd /workspaces/Skyscraper/tests/lxml_tests && uv sync
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/target
Cargo.lock
test.html
.env/
.venv/
log.txt
.context/
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
],
}
96 changes: 96 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Skyscraper is a Rust library for scraping HTML documents with XPath expressions. It has its own HTML parser (following the WHATWG spec) and XPath parser/evaluator. Published on crates.io as `skyscraper` (v0.7.0-beta.2, MIT license). Many XPath features still have `todo!()` stubs.

## Build & Test Commands

```sh
cargo build # Build
cargo test # Run all tests
cargo test <test_name> # Run a single test by name
cargo test --test <file> # Run a specific test file (e.g. --test html_tests)
cargo bench # Run all benchmarks (criterion)
```

Stack overflow regression tests (Windows-specific, run in CI on windows-latest):
```sh
cargo test --test run_stack_overflow_tests -- --include-ignored --test-threads=1
```

Reference testing against Python lxml (managed via uv):
```sh
cd tests/lxml_tests && uv sync # Install Python deps (first time / after changes)
cat tests/samples/James-LG_Skyscraper.html | uv run --directory tests/lxml_tests python xpath.py "//div"
```
The Rust lxml tests (`tests/xpath_tests/lxml_tests.rs`) automatically use the venv at `tests/lxml_tests/.venv/bin/python` when available.

Feature flag: `debug_prints` enables debug output during parsing.

## Architecture

The crate has two core modules (`src/html/` and `src/xpath/`) plus a small `vecpointer` utility. The `#![warn(missing_docs)]` lint is enabled at the crate root.

### HTML Module (`src/html/`)

Parses HTML text into an arena-based tree (`indextree` crate).

- **`HtmlDocument`** — owns the arena tree; has a `root_node: DocumentNode`
- **`DocumentNode`** — wrapper around `indextree::NodeId`; provides `children()`, `parent()`, `get_text()`, `get_all_text()`
- **`HtmlNode`** — enum: `Tag(HtmlTag)` | `Text(HtmlText)`. Text nodes are separate to preserve ordering in mixed content.
- **Grammar subsystem** (`src/html/grammar/`):
- **Tokenizer** (`tokenizer/`) — state-machine HTML tokenizer per WHATWG spec, including named character references
- **Parser** (`mod.rs`) — main parsing logic with WHATWG insertion modes
- **Document builder** (`document_builder.rs`) — constructs the tree from token stream
- **Insertion modes** (`insertion_mode_impls/`) — WHATWG tree construction insertion modes (in_body is the largest)
- Key constants: `VOID_TAGS`, `SPECIAL_ELEMENTS`, `GENERATE_IMPLIED_END_TAG_TYPES`, `ELEMENT_IN_SCOPE_TYPES` (defined via `once_cell::Lazy`)

### XPath Module (`src/xpath/`)

Parses XPath strings and evaluates them against an `XpathItemTree` (converted from `HtmlDocument`).

- **`Xpath`** — compiled XPath expression; call `.apply(&XpathItemTree)` to evaluate
- **`XpathItemTree`** — tree structure mirroring the HTML document for XPath evaluation; created via `XpathItemTree::from(&document)`
- **Grammar subsystem** (`src/xpath/grammar/`, ~70 files):
- `expressions/` — all expression types (path, logical, comparison, arithmetic, conditional, for/let, quantified, postfix, etc.)
- `expressions/path_expressions/steps/axes/` — forward axes (child, descendant, attribute, descendant-or-self) and reverse axes (parent)
- `data_model/` — XPath node types (document, element, text, attribute, comment, PI)
- `types/` — XPath type system (sequence types, element/attribute tests)
- `terminal_symbols.rs` — token definitions
- `recipes.rs`, `whitespace_recipes.rs` — nom parser combinator helpers
- `xml_names.rs` — XML name validation

### Parsing Approach

Both modules use **nom** parser combinators extensively. The `recipes.rs` and `whitespace_recipes.rs` modules provide reusable combinator patterns. The HTML tokenizer uses a state machine pattern, while XPath parsing is a recursive descent grammar built from nom combinators.

## Test Structure

- `tests/html_tests/` — HTML parsing correctness
- `tests/xpath_tests/` — XPath parsing and evaluation (many sub-files: predicates, axes, functions, type matching, etc.)
- `tests/test_framework/` — shared test utilities for document comparison
- `tests/samples/` — sample HTML files for integration tests
- `tests/lxml_tests/` — Python reference tests comparing against lxml
- `tests/stack_overflow_tests/` — separate Cargo project for Windows stack overflow regression

Tests use `indoc` for readable multi-line HTML strings and `proptest` for property-based testing.

## Reference Specifications (`.context/`)

The `.context/` directory (gitignored) contains downloaded W3C specifications for offline reference. New resources can be added here as needed to avoid refetching.

Current contents:
- `xpath-31-spec.html` — [XPath 3.1](https://www.w3.org/TR/2017/REC-xpath-31-20170321/) (W3C Recommendation)
- `xpath-datamodel-31-spec.html` — [XPath Data Model 3.1](https://www.w3.org/TR/2017/REC-xpath-datamodel-31-20170321/) (W3C Recommendation)
- `xpath-functions-31-spec.html` — [XPath Functions and Operators 3.1](https://www.w3.org/TR/2017/REC-xpath-functions-31-20170321/) (W3C Recommendation)

## Features Backlog

[`docs/features-backlog.md`](docs/features-backlog.md) tracks spec gaps, known limitations, and design decisions for both the HTML parser and XPath evaluator. Check it before implementing new features to avoid duplicating known issues.

## CI

GitHub Actions (`.github/workflows/rust.yml`): runs `cargo test` in a devcontainer on ubuntu-latest, plus stack overflow tests on windows-latest with Rust nightly.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ indexmap = "2.0.0"
log = "0.4.19"
nom = "7.1.3"
ordered-float = "4.2.0"
once_cell = "1.19.0"
enum-extract-macro = "0.1.1"
enum-extract-error = "0.1.1"
regex = "1.10.3"
unicode-normalization = "0.1.25"

[dev-dependencies]
criterion = "0.5.1"
Expand All @@ -32,7 +32,11 @@ proptest = "1.3.1"
regex = "1.10.3"
serde_json = "1.0.113"
serde = "1.0.196"
itertools = "0.13.0"

[[bench]]
name = "benchmarks"
harness = false

[features]
debug_prints = []
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

Rust library to scrape HTML documents with XPath expressions.

> This library is major-version 0 because there are still `todo!` calls for many xpath features.
>If you encounter one that you feel should be prioritized, open an issue on [GitHub](https://github.com/James-LG/Skyscraper/issues).
>
> This library is major-version 0 as the API is still evolving.
> See the [Supported XPath Features](#supported-xpath-features) section for details.

## HTML Parsing
Expand Down Expand Up @@ -51,6 +49,10 @@ assert_eq!(parent_node, parent_of_child0);
assert_eq!(parent_node, parent_of_child1);
```

### WHATWG Compliance Note

Skyscraper's HTML parser follows the [WHATWG parsing specification](https://html.spec.whatwg.org/multipage/parsing.html). One notable consequence is **implicit `<tbody>` insertion**: when `<tr>`, `<td>`, or `<th>` elements appear as direct children of `<table>`, the parser automatically wraps them in a `<tbody>` element (per [WHATWG §13.2.6.4.9](https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intable)). This matches browser behavior but differs from parsers like Python's lxml, which does not insert `<tbody>`. As a result, XPath expressions like `//table/*` or `//table//*` may return different results than lxml for the same input HTML. To avoid this discrepancy, use explicit `<tbody>` tags in your HTML or account for the implicit element in your XPath expressions.

## XPath Expressions

Skyscraper is capable of parsing XPath strings and applying them to HTML documents.
Expand Down Expand Up @@ -106,20 +108,24 @@ Below is a non-exhaustive list of all the features that are currently supported.
1. Predicates:
1. Attributes: `//div[@class='hi']`
1. Indexing: `//div[1]`
1. Functions:
1. `fn:root()`
1. `contains(haystack, needle)`
1. Forward axes:
1. Child: `child::*`
1. Descendant: `descendant::*`
1. Attribute: `attribute::*`
1. DescendentOrSelf: `descendant-or-self::*`
1. (more coming soon)
1. Reverse axes:
1. Parent: `parent::*`
1. (more coming soon)
1. Treat expressions: `/html treat as node()`

This should cover most XPath use-cases.
1. Arbitrary expressions: `//div[contains(@class, 'hi')]`
1. Forward axes: `child::`, `descendant::`, `attribute::`, `self::`, `descendant-or-self::`, `following-sibling::`, `following::`, `namespace::`
1. Reverse axes: `parent::`, `ancestor::`, `preceding-sibling::`, `preceding::`, `ancestor-or-self::`
1. Operators:
1. Logical: `and`, `or`
1. Comparison: `=`, `!=`, `<`, `>`, `<=`, `>=`, `eq`, `ne`, `lt`, `gt`, `le`, `ge`
1. Arithmetic: `+`, `-`, `*`, `div`, `idiv`, `mod`
1. String concatenation: `||`
1. Sequence: `union`/`|`, `intersect`, `except`, `to`
1. Simple map: `!`
1. Arrow: `=>`
1. Node comparison: `is`, `<<`, `>>`
1. Expressions: `if`/`then`/`else`, `for`, `let`, `some`/`every` (quantified)
1. Type expressions: `instance of`, `cast as`, `castable as`, `treat as`
1. Functions (100+): string (`contains`, `starts-with`, `ends-with`, `substring`, `concat`, `normalize-space`, `upper-case`, `lower-case`, `translate`, `matches`, `replace`, `tokenize`, ...), numeric (`round`, `floor`, `ceiling`, `abs`, `sum`, `avg`, `min`, `max`, ...), boolean (`not`, `true`, `false`, `boolean`), sequence (`count`, `empty`, `exists`, `reverse`, `sort`, `distinct-values`, `head`, `tail`, `subsequence`, ...), node (`name`, `local-name`, `root`, `path`, `has-children`, `data`, ...), higher-order (`for-each`, `filter`, `fold-left`, `fold-right`, ...), and more
1. Maps and arrays construction and access

If your use case requires an unimplemented feature,
please open an issue on [GitHub](https://github.com/James-LG/Skyscraper/issues).

See [`docs/features-backlog.md`](docs/features-backlog.md) for a detailed list of spec gaps, known limitations, and design decisions.
13 changes: 12 additions & 1 deletion benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ use criterion::{criterion_group, criterion_main};

mod html_benchmark;
mod xpath_benchmark;
mod xpath_eval_benchmark;

use crate::html_benchmark::*;
use crate::xpath_benchmark::*;
use crate::xpath_eval_benchmark::*;

criterion_group!(benches, benchmark_html_parse, benchmark_xpath_parse);
criterion_group!(
benches,
benchmark_html_parse,
benchmark_xpath_parse,
benchmark_axes,
benchmark_predicates,
benchmark_functions,
benchmark_expressions,
benchmark_real_world,
);
criterion_main!(benches);
Loading
Loading