Skip to content

Releases

Frostwork 0.2.2

Choose a tag to compare

@github-actions github-actions released this 05 Nov 14:47

Performance

  • navigation() no longer takes quadratic time on a document with many unclosed <meta tags, e.g.
    "<meta " * 80000, which took over 30 seconds in 0.2.1.

Frostwork 0.2.1

Choose a tag to compare

@github-actions github-actions released this 05 Nov 12:11

Correctness

  • navigation() no longer reads a <base> or meta refresh written after a self-closed element whose
    content browsers read as text, e.g. <script/><base href="…">: unless it is an SVG or MathML
    element, browsers ignore the / of <script/>, <style/>, <title/>, <textarea/>,
    <noscript/> (with scripting enabled), … and read the markup up to the matching end tag as text.

Performance

  • navigation() no longer tokenizes a document that cannot hold what was asked for: a <base> for
    the base URL, a <meta> with an http-equiv attribute for the meta refresh.
  • frostwork.encoding.bom_encoding(), codec_name() and decode() have less call overhead.

Frostwork 0.2.0

Choose a tag to compare

@github-actions github-actions released this 05 Nov 08:53

Backward-incompatible changes

  • A document with no BOM, label or declaration is no longer assumed to be UTF-8: it is UTF-8 if it is
    valid UTF-8, and otherwise its encoding is autodetected, as Firefox does. Pass the response URL as
    url to extract, extract_grouped, detect_encoding, Plan, Page.extract and
    frostwork.encoding.autodetect_encoding so that its top-level domain informs the guess (.jp
    favours Shift_JIS, .ru windows-1251, …), as it does in browsers.

  • In Rust, extract, extract_grouped, detect_encoding, Plan::extract,
    Page::extract_with_encoding and CompiledPage::extract_with_encoding take the response URL as a new
    url: Option<&str> argument after the encoding. Pass None when there is no URL.

  • bgsound, embed, keygen, source, track and wbr are now void elements, matching libxml2
    2.15, the oracle Frostwork now targets. Markup after one of them is no longer its child, e.g.
    <div><source><span>x</span></div> matches div > span instead of source span.

Python API and tooling

  • frostwork.encoding exposes each step of detect_encoding() (bom_encoding(),
    declared_encoding(), autodetect_encoding()), plus decode(), which decodes as browsers do where
    Python's codecs differ (e.g. euc_jp), and codec_name(), which gives the Python codec name of any
    label resolve_label() resolves. It lets a library that keeps its own encoding API, such as w3lib,
    give browser answers. Importing it registers Python codecs for x-user-defined and replacement.
    See PYTHON.md.

  • frostwork.navigation() reads the base URL and meta refresh of a document as a browser does, and
    Page.extract_with_navigation() reads them in the same pass as the fields; in Rust, navigation()
    and Plan::extract_with_navigation(). See
    PYTHON.md.

  • The refusal reason of a selector with an unsupported pseudo-class or pseudo-element now names it,
    e.g. pseudo-class `:hover` is unsupported, also when it sits next to a supported one
    (li:nth-child(2):hover).

  • frostwork.page.detect_encoding() and frostwork.page.resolve_label() are deprecated; use
    frostwork.detect_encoding() and frostwork.resolve_label().

Correctness

  • The <meta> prescan skips other tags together with their attributes, as browsers do, so a
    <meta charset> inside an attribute value (<img alt="<meta charset=big5>">) no longer declares the
    encoding.

  • A selector deferred on an ancestor (:has(), :contains(), a reverse position such as :last-child
    or [last()]) followed by a descendant step that the ancestor itself could match, such as * or its
    own tag, no longer returns that ancestor, e.g. li:last-child * returned the <li> as well as its
    descendants, and //li[last()]//*/@class its class.

Frostwork 0.1.6

Choose a tag to compare

@github-actions github-actions released this 23 Sep 13:35

Python API and tooling

  • frostwork.resolve_label(label) returns the WHATWG name of the encoding a charset label names, or
    None. It accepts the same labels as extract, Python codec names included (cp874 →
    windows-874), so a caller can check whether a Python-side encoding such as Scrapy's
    response.encoding is the one Frostwork will decode with.

Frostwork 0.1.5

Choose a tag to compare

@github-actions github-actions released this 23 Sep 11:18

Python API and tooling

  • Selectors can declare their syntax instead of being routed by prefix: pass syntax="css" or
    syntax="xpath" to extract, extract_grouped, check and every Page field, many and one
    (where it covers the container and its sub-fields); in Rust, pass a Query wherever a selector
    string is accepted. Without a declaration the prefix rule is unchanged — /, ./ and
    normalize-space( are XPath, everything else is CSS.

    Relative XPath is what needs it. Declared as XPath, h1 or td/text() is refused as unsupported,
    like every relative path; read as CSS, h1 matches every <h1> instead, which is a wrong value
    rather than a refusal. frostwork-audit now audits each call site in the language of the call it
    came from, so a .css() site is explained as CSS and an .xpath() site as XPath.

  • Charset labels accept the Python codec names of WHATWG encodings — cp932, euc_jp, big5hkscs,
    cp949, utf-16-le and the rest — which is what w3lib.encoding.resolve_encoding, and therefore
    Scrapy's response.encoding, returns from a Content-Type header. Such a label was previously
    ignored and the document sniffed instead, so a Shift_JIS page passed as cp932 could decode wrongly.
    Every Python entry point resolves labels the same way now, Plan and the web-poet integration
    included. A label naming a real encoding WHATWG has no equivalent for (utf-7, cp437) is still
    ignored, the way a browser ignores it, and sniffing continues.

Frostwork 0.1.4

Choose a tag to compare

@github-actions github-actions released this 05 Sep 14:24

Correctness

  • Sibling selectors retain document-level history across multiple HTML roots. Content after </html>
    can now match html + html and html ~ html, including preceding :has()/:contains() predicates.
    The minimized malformed-HTML regression is covered by the rule audit and both language suites.
  • Python and Rust Item.get_all() now consistently respect the field declaration: a single-valued
    field returns zero or one raw match, while field_all and field_join return every raw match.
    Previously, adding an unrelated field could expose additional matches by disabling early exit.
    Declare field_all when every match is needed; single-valued schemas retain their early-exit behavior.
  • The corpus gate now uses the main differential's verdict without discarding empty or whitespace-only
    values. Regressions that drop an empty attribute or change value count can no longer pass as
    whitespace-only differences. Command-level regression tests cover the verdict, report and exit status.
  • CSS class and ID selectors now decode literal and hexadecimal escapes, including utility classes
    such as .sm\:text-lg. Escape terminators stay part of the identifier instead of becoming descendant
    combinators. Non-executable character escapes and ambiguous decoded-backslash escapes are refused throughout
    the compiler, including quoted attribute values.
  • Grouped subfields support immediate-child XPath (./h2/text()), own attributes (@id), own text
    (text()), subtree text/attributes (.//text(), .//@id) and context HTML (.). Child paths are
    anchored to the actual container; nested descendants cannot satisfy the first child step.
  • Grouped extraction and schema audits share input normalization: malformed subfield pairs are
    rejected instead of unpacking a string or mapping key into unrelated selectors. Extraction also
    accepts subfield mappings directly. Page.many/one rejects malformed cardinality tuples and
    non-string separators at declaration time instead of ignoring extra arguments.
  • Page always applies a supplied callable transform, even when that callable has a false boolean
    value. First, all and joined fields use the same rule.
  • Saved-response verification uses independent item shaping, so a cardinality bug cannot change both
    the engine result and its reference. It preserves escaped CSS whitespace, handles padded XPath
    consistently and checks types inside tuple-valued transforms.
  • Rule-mutation checks propagate failures while restoring both normal builds, including after failed
    setup. Release wheels now exercise the expanded public API suite and run a core smoke check before
    optional dependencies are installed.

Developer experience

  • Documentation starts with reusable Page schemas for ordinary Scrapy callbacks and an API chooser.
    URL and grouped-item recipes are self-contained and tested with missing values; migration examples
    are executed from the docs. The compatibility guide distinguishes selector support from value parity.
    Release checks render Markdown and check repository links against heading IDs; Twine skips that step.
  • Source audits use the grouped compiler for literal Page.many/one and web-poet Many/One fields,
    with context-specific diagnostics. Dynamic-only scans report unknown coverage; --require-complete
    can fail CI on unresolved sites or empty scans. Full schema audits still check shared budgets.
    Group detection follows field modifier chains; callbacks and unknown factories cannot make an
    unresolved group look complete.
  • Page.extract_response() uses response bytes and encoding without a Scrapy dependency.
    Page.frost_schema() exposes the same named selector format as the web-poet integration.
  • Item.validate() checks required processed values, retained match counts and required group
    subfields. Reports distinguish absent matches, empty matches, empty processed values and transform
    failures, and can record Scrapy stats. FieldProcessingError adds field/selector context while
    preserving the original exception. An empty row still counts as a matched group container.
  • The migration workflow now includes complete-item verification on hashed saved responses, an
    inspectable fixture manifest, parity-gated timing reports and a manual Linux benchmark workflow.
    Historical benchmark ratios are no longer described as a guaranteed performance floor.
  • Default pytest discovery is scoped to tests/, so Cargo package snapshots under target/ cannot
    be collected as duplicate test modules.

Performance

  • Immediate first-value text and attribute fields stop retaining later matches even when an all-value
    field or group requires a complete scan. Deferred predicates, normalization and raw-HTML captures keep
    their existing ordering and retention rules. The A/B harness now includes a mixed-cardinality workload;
    measurement details and limitations are in docs/BENCHMARKS.md.
  • Page and Item share stable field definitions instead of copying parallel metadata lists on each
    response. Named item access uses direct lookups, and required-field validation uses set membership.
    Extending a page preserves earlier items' schemas. Page.one() shapes only the first matched row;
    the engine still scans and collects grouped matches as before.

Frostwork 0.1.3

Choose a tag to compare

@github-actions github-actions released this 01 Sep 23:45

Packaging and documentation

  • Includes all 0.1.2 and 0.1.1 changes below. Neither version was uploaded to PyPI: the 0.1.2
    pre-upload smoke test exposed locale-dependent file decoding in the release checker on Windows. The
    checker now reads repository text explicitly as UTF-8 and can report Unicode diagnostics safely on a
    cp1252 console.