Skip to content

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 09 Aug 13:58
· 5 commits to main since this release

Twenty-four fixers to twenty-eight, plus the widest single extension this
project has made. On the shared shelf — 125 books, epubveri 0.9.14 — errors fall
13,141 → 4,027 over the 28 books epubsana touches, six books reach fully
valid, and both whole-shelf instruments report nothing introduced anywhere.

Two things in here were found by the instruments rather than by reading code, and
both are the reason those instruments exist. The <blockquote> extension came
from a shape census that showed stray text is reported in exactly two containers
on 125 books. The identity fixer's first cut cleared OPF-030 and produced three
new NCX-001s — an edit that was correct while the book got worse, which no
unit test can see.

One was found the other way, by reading epubveri's source: the playOrder
renumbering was target-blind and could have created the mismatch it now
repairs, on a book shape the corpus does not contain.

Added

  • A fixer for an anchor target wrapped around a link
    (htm.epub2_dom.nested_anchor). On the shelf every case is one shape — a
    footnote reference whose outer <a> carries no href:

    <a id="bookmark1"><sup><a href="#footnote1">1</a></sup></a>
    

    That outer element is not a link but an anchor target, the legacy way of
    naming a position from before every element could hold an id. It is unwrapped
    and the id moves to its single child, so #bookmark1 still resolves, to an
    element at the same place in the same rendered line.

    Declines when the outer anchor has an href (a real link, and which of two
    nested links to keep is not ours to decide), when it carries any attribute
    besides id, when the child already has one, or when it wraps more than that
    one child. 6 findings in one book, all repaired, nothing introduced.

  • A fixer for a package whose declared identifier points at nothing usable
    (opf.package.unique_identifier_unresolved + opf.package.opf_identifier_not_empty).
    Two rules, one defect at two stages, hitting disjoint sets of five books each:
    either no <dc:identifier> carries the id unique-identifier names, or the one
    that does is empty. The declared id is attached to the book's single real
    identifier, and any leftover empty element is dropped.

    It invents nothing. The value was already in the book, written by its
    producer; the id was already in the package, written in the attribute. The
    repair only attaches one to the other — the same principle as empty_title
    moving a TOC label the author wrote.

    The whole-shelf audit earned its keep here. The first cut cleared OPF-030
    and produced three new NCX-001s: making the package identifier resolvable
    is what first lets epubveri compare the NCX's dtb:uid against it, so the
    repair unmasked a pre-existing mismatch. No unit test could have caught that —
    the edit was correct and the book ended up worse. The dtb:uid now syncs in the
    same proposal, on the pattern fix.manifest_dangling_item already set.

    Measured: 10 findings across 10 books, 3 repaired and 7 declined — and the
    declines are the point. Four books carry both a UUID and an ISBN with no id on
    either, where which identity is canonical is an editorial decision; two carry no
    <dc:identifier> at all, where the repair would have to generate one. With the
    NCX syncs the run clears 8 findings and introduces nothing.

Changed

  • fix.ncx_play_order now repairs all three playOrder faults, and no longer
    risks creating one.
    epubveri reports duplicate (different targets sharing a
    number), target_mismatch (one target reached by different numbers) and gap
    separately, and they interlock — satisfying one naively breaks another. The
    fixer now reassigns the whole NCX as the format defines: 1-based, dense, in
    document order, with elements naming the same target sharing the first number
    that target was given.

    The previous version numbered by position in the file — unique and dense, but
    target-blind, so on a book whose navigation reaches one position by two
    routes it would have created target_mismatch. No shelf book had that shape,
    so no audit could have shown it; the defect surfaced from reading epubveri's own
    rule, which skips a repeated number when all its holders name the same target.
    A corpus cannot find that class of defect; the detector's source can.

    The renumbering now parses the NCX rather than scanning it for
    playOrder=, since a target cannot be read off a string — so an NCX that will
    not parse is declined rather than rewritten, consistent with the other
    structural fixers.

    Measured: 8 target_mismatch and 1 gap cleared on top of the 14
    duplicate, nothing introduced, and one more book reaches fully valid.

  • The body-level wrapper now also works inside <blockquote> — the single
    largest lever this project has found.
    XHTML 1.1 requires block content in
    <blockquote> exactly as it does in <body>, and the 10 EPUB 2 books added on
    2026-08-08 carry 2,508 stray-text and 3,009 incomplete-content findings there.

    The two messages are one defect seen from opposite ends, which is why both
    now trigger the fixer: a <blockquote> holding only text is stray text is not
    allowed directly in "blockquote"
    and element "blockquote" has incomplete
    content
    , because its model needs at least one block child. One wrap clears
    whichever fired — verified on real books before a line was written: one file
    went {incomplete 53, stray 54, span 1}{}, another
    {incomplete 96, span 98, img 7}{img 7}, the inline elements clearing as
    part of the run.

    The container set is now a principle rather than a name: the containers
    whose XHTML 1.1 model requires block content and admits <div>body and
    blockquote. <ol>/<ul> want an <li> and <head> a <title>, wrappers
    that assert what the content is, so they stay declined. On the 125-book shelf
    that rule is not a compromise but the whole population: stray text is reported
    in exactly those two containers and nowhere else.

    Measured: findings cleared through schema_violation go 1,273 →
    8,624
    , total cleared 1,745 → 9,096, books touched 25 → 26, and both
    whole-shelf instruments still report nothing introduced anywhere.

    Honest ceiling: this does not clear all 3,009 incomplete-content findings,
    any more than it clears all 403 on <body> (it clears 2 of those). The rest
    are containers whose only children are <figure>/<section> — elements XHTML
    1.1 does not have — where the repair would be renaming, which epubsana
    deliberately does not do.

  • Tracks epubveri 0.9.14. Zero source change; styloria 0.8 → 0.9 comes
    along transitively. Upstream's own figure verified from here: the only
    (id, rule) pair that moved is CSS-008 / css.stylesheet.invalid_selector,
    0 → 21 in one book, and the shelf run confirms it passes straight through —
    errors before 13087 → 13108 and after 3991 → 4012, the same +21 on both
    sides, because no fixer of ours consumes it. Everything else identical, zero
    regressions.

    Two upstream items worth recording even though they move nothing here:

    • RSC-010 gained its fallback clause, the defect diagnosed from this side
      on 2026-08-08 (a nav/NCX link to a non-Content-Document is legal when the
      manifest declares a fallback chain reaching one). No shelf book has the
      shape; the IDPF haruko-jpeg sample that prompted it loses its three errors.
    • epubcheck's JSON and XML reports cap identical messages at 25
      (CheckMessage.java, MAX_LOCATIONS), with the "N additional locations"
      line commented out — so a consumer sees a truncated list with no indication
      anything was dropped. epubsana runs no second oracle, so nothing here is
      affected, but any cross-tool count taken from epubcheck's JSON is
      incomparable above 25 per message. Recorded so it is never quoted by
      accident.
  • Tracks epubveri 0.9.12 (from 0.9.9). Zero source change again; the shelf
    grew to 125 books the same day. Re-measured: errors 6376 → 4631 over
    the 25 books epubsana touches, 5 books reach fully valid, nothing
    introduced
    by either whole-shelf instrument. Every fixer's proposal count is
    unchanged, which is the answer to upstream's heads-up: 0.9.12 adds 171
    element "img" is missing a required attribute findings across 10 books, and
    none of them reaches a fixer of ours.

  • handled_rules() now lists ncx.uid.package_identifier_mismatch and
    ocf.mimetype.not_first_entry.
    Both sites were rule-less when their fixers
    were written, so those fixers dispatch on the bare NCX-001 / PKG-006 id;
    epubveri 0.9.11 named them. The dispatch is unchanged — our floor is 0.9.7,
    where the slugs do not exist — but a census reading this list would otherwise
    have filed two rules we do fix under "no fixer at all", which is exactly the
    mislabelling the list exists to prevent.

Not built, deliberately

  • <img> with no alt (171 findings / 10 books, the widest-spread shape on
    the shelf) is a decline, and upstream predicted it would be.
    XHTML 1.1 makes
    alt required where HTML5 does not, so this is one of the few places EPUB 2 is
    the stricter version. There is no determinate repair: alt="" is correct for a
    decorative image and wrong for a meaningful one, and nothing in the finding —
    or in the book — says which. Supplying either would be inventing content, which
    is the line empty_title already draws. Recorded in docs/COVERAGE.md rather
    than left to be re-derived.

Full notes: CHANGELOG.md. Also on crates.io.