v0.5.0
Ten new fixers (nine → nineteen), tracking epubveri through 0.5.15, and the
structural fixers now read the EPUB 2 documents they previously couldn't.
Four whole error families are now handled end to end — entities, doctypes, NCX
internal consistency, and the EPUB 2 <guide>.
Changed
- Structural fixers now read EPUB 2 content documents that use DTD-only entities
( under an XHTML 1.1 DOCTYPE). roxmltree doesn't fetch the external DTD, so
these documents didn't parse and every structural fixer silently declined them —
the same reach gap epubveri closed in its issue #23. epubsana now declares the
named entities the document uses in the DOCTYPE's internal subset in a working
copy, parses that, and maps node byte ranges back to the original text it edits;
the declarations never appear in the output, and the injection is bounded by the
DOCTYPE (not a body[1]). Effect on the corpus:fix.empty_titleproposals rise
from 2153 to 2286 (+133) — the previously-unreadable documents that carry a
title source now get one; the rest parse but are declined for having none. Also
benefitscontent_type_metaandbare_text_in_body. Zero new regressions.
Added
-
Two fixers completing the EPUB 2
<guide>family:opf.guide.reference_missing_resource(RSC-007,fix.guide_dangling_reference,
ConfirmNeeded) — a<guide>reference whosehrefresolves to no resource in
the container (on the corpus, a wrong extension likerica.htmlbeside
rica.xhtml) is dropped: it names a landmark no reader can reach and nothing
records what file it meant. If dropping leaves the<guide>empty — invalid, and
the element is optional — the<guide>is dropped too. Matches on the reported
href; paths are not re-resolved. Dropping the reference also clears the
co-emittedOPF-031("not declared in the manifest").opf.guide.duplicate_reference(RSC-017,fix.guide_duplicate_reference,
ConfirmNeeded) — two or more references sharing the sametypeandhref;
the first is kept and the redundant repeats dropped. References with the same
typebut differenthrefare not duplicates and are left alone.
On the corpus this clears every occurrence in the affected books (missing-resource
5 books, duplicate 1 book), and theOPF-031side effect too, with zero
regressions. This closes theopf.guidefamily. -
Two fixers completing the NCX internal-consistency family (
RSC-005):ncx.ids.duplicate_id(fix.ncx_duplicate_id, ConfirmNeeded) — two or more NCX
elements share anid. The first keeps it; each later duplicate is renamed to a
unique value. NCX ids are not IDREF targets anywhere in an EPUB, so no reference
is rewritten. Disjoint from the NCName fixer by construction (that one touches
only ids occurring exactly once; a duplicate occurs more than once).ncx.play_order.duplicate(fix.ncx_play_order, ConfirmNeeded) — navigation
elements repeat aplayOrder. EveryplayOrderis renumbered to its 1-based
document-order position (the canonical assignment), making the values unique.
playOrderis only a hint; the reading order a system follows is the spine,
untouched.
With the existing NCName and
dtb:uidfixers, this handles the NCX
internal-consistency family end to end: the one remaining member,
ncx.page_target.invalid_type, is deliberately declined — a bad@type
(front/normal/special) has no determinate replacement, and guessing the page
category would be inventing. On the corpus both new fixers clear every occurrence
in the two affected books (duplicate_id3→0,play_order3→0 each), zero
regressions. -
Two fixers for an obsolete or unrecognized DOCTYPE (
HTM-004), closing out
thehtm.doctypefamily:htm.doctype.epub3_obsolete_public_id(fix.doctype_html5, AutoSafe) — an
EPUB 3 document's DOCTYPE carrying a PUBLIC identifier is reduced to HTML5's
only legal form,<!DOCTYPE html>. Declines a DOCTYPE with an internal subset,
whose[ … ]declarations HTML5 can't carry.htm.doctype.epub2_unrecognized_public_id(fix.doctype_xhtml11,
ConfirmNeeded) — an EPUB 2 DOCTYPE whose identifier is a malformed XHTML 1.1
id (names 1.1 / thexhtml11.dtdbut mistypes the exact string) is canonicalized
to the recognized form. Declines a document declaring a genuinely different DTD
(XHTML 1.0, a bare<!DOCTYPE html>, OEB): relabeling it to 1.1 would assert a
content model epubsana can't verify and risks trading the finding for
content-model errors. On the corpus the one affected book (XHTML 1.0 Strict, 77×)
is declined, correctly.
Both are surgical on the DOCTYPE only and bound it by its own closing
>(never a
body[1]), the lesson of the upstream bracket bug. Thehtm.doctypefamily is
now handled end to end — every finding gets a repair or a principled decline —
though not "every occurrence rewritten"; the decline is the "never guess" rule at
work. Seedocs/COVERAGE.md. -
A fixer for an entity reference missing its closing
;
(RSC-016/htm.entity.missing_semicolon, fatal) — for .
A recognized name is replaced by the character it denotes (well-formed with or
without a DTD); one of the XML-predefined five (&…), whose character is
the bare delimiter itself, is closed with the missing;instead. The match is
boundary-checked, so a correct and a longer entity that merely starts
with the name are never touched; an unrecognized name is declined.This completes the
htm.entityfamily — the first error family epubsana
covers end to end: every entity defect epubveri reports (undeclared, and now
unterminated) has a repair. Seedocs/COVERAGE.mdfor the family map this
closure is measured against. -
Two fixers for dangling references in the package document, contributed as
requirements byepublift, which carried its own repair for them
(#4,
#3):RSC-001/opf.manifest_item.missing_resource— a manifest<item>
declaring a resource the container doesn't hold. The declaration is dropped
together with every reference that named it: the spine<itemref>s it
would otherwise orphan, and a legacy<meta name="cover">pointing at it.
Those travel in a single proposal rather than separate ones, because
approving the item drop and declining the spine drop would leave you with an
OPF-049epubsana created itself.OPF-049/opf.spine.itemref_idref_not_in_manifest— a spine<itemref>
naming a manifest id that does not exist. Dropped; every other entry keeps
its place in the reading order.
Both are
ConfirmNeeded— they are deletions that can shorten the reading
order or remove a cover declaration, and epubsana does not delete visible
structure unattended. Both decline rather than repair when the deletions would
leave<spine>with no children: a spine-less EPUB is not a repaired book.On the 171-book corpus this clears every
RSC-001(3 findings in 2 books) and
takes both books from invalid to fully valid — 26 → 28 books that epubsana
brings all the way to valid. No book gains a finding. -
A fixer for a duplicated spine entry — the same manifest item listed twice,
so a chapter appears twice in the reading order
(#2). The first occurrence
is kept and the repeats dropped: the repeat carries no information the first
doesn't, and the first is where the document belongs in the sequence.epubveri reports this condition under two ids —
OPF-034in EPUB 2,
RSC-005in EPUB 3 — with one sharedrule, so the fixer keys on therule
and inherits the id from the finding. A fixer written againstOPF-034alone
would have done nothing on every EPUB 3 book.Declines rather than guessing when the duplicate's
lineardisagrees with the
first's (the book means "in the reading order and reachable out-of-line",
which is deliberate), or when a repeat carries anidthat a<meta refines>
targets. Not present in the reference corpus, which contains no Kindle→EPUB
conversions — it lands onepublift's reproduction of it in the wild.
Changed
-
Track
epubveri0.5.15 (from 0.5.9). No source change across the whole span
— therule/paramscontract held every bump; the effects are behavioural.- epubveri#23 (0.5.12): EPUB 2
documents with DTD-declared entities ( under an XHTML 1.1 DOCTYPE) now
parse, so a class of falseRSC-012"fragment not defined" findings is gone.
RSC-012drops from 1247 to 172 — the 172 are the genuinely dangling fragments,
the ~1075 removed were the detector failing to read a valid document and calling
its ids absent.empty_titlefindings rise +157 as the same documents become
readable. - epubveri#25 (0.5.12): a
regression in 0.5.10/0.5.11 that turned any EPUB 2 document with a[in its
body (a footnote marker) into a false fatal — 78 across 11 corpus books on
0.5.11, zero on 0.5.12. - Content-model validation (0.5.13 EPUB 2, 0.5.15 EPUB 3): EPUB 2 books are
now checked against XHTML 1.1 + OPS 2.0.1 instead of HTML5, and EPUB 3 nesting /
IDREF rules are enforced. This is verdict-changing:opf.content_document.schema_violation
rises from 16 to 32 books, and newRSC-005sub-codes appear
(htm.epub2_dom.nested_anchor,htm.epub2_dom.html5_only_element). epubsana
does not yet repair these classes.
Net on the 171-book corpus: 25 books brought all the way to valid (was 30 on
0.5.12), still zero regressions. The drop is not lost coverage — every fixer
fires identically; it is the content model finding real defects epubsana does not
yet fix. Measured: 14 books are now blocked from fully-valid only by a new
content-model finding, which is the ROI case for a content-model fixer.The manifest floor is now
epubveri = "0.5.15"— earlier versions either misreport
(RSC-012on 0.5.9, the #25 fatal on 0.5.10/0.5.11) or predate content-model parity. - epubveri#23 (0.5.12): EPUB 2
Full notes: CHANGELOG.md. Also on crates.io.