|
|||||||
![]() |
|
|
Thread Tools | Search this Thread |
|
|
#1 |
|
Connoisseur
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 58
Karma: 100000
Join Date: Jul 2026
Location: Planet Earth
Device: Kobo Forma
|
epubsana — automatic repair for what epubcheck reports (not an editor)
Some of you have been putting epubveri through its paces in PeterT's thread, and nearly every false positive fixed since 0.5.15 came from that — real books finding what fixtures can't. Thank you. I'd like to try the same thing for the other half.
epubsana repairs the defects epubveri reports. epubveri says what's wrong; epubsana turns the safely-fixable findings into edits you approve one at a time, applies them, and prints a report of exactly what changed. What it is not: it is not an editor and it is not trying to become one. It has no UI for writing markup, no preview, no book view. Sigil and calibre are where you edit a book; epubsana is meant to clear the mechanical, boring defects before you open it there, so the errors you're left staring at are the ones that actually need a human. If it ever competes with an editor, I've made a mistake. Three ways to try it
Why it's safe to point at a real book
What it refuses, and why that's the interesting part A repairer that guesses is worse than no repairer. Some examples of things it will not do:
Known limitations, so you don't have to find them the hard way
What would actually help Two things, and the second is the one I can't get anywhere else. 1. Tell me when a repair is wrong. Not "it didn't fix X" — "it changed X and it shouldn't have". epubsana checks its own work by re-validating with epubveri, which means it is structurally blind to any damage epubveri doesn't know how to see. A person who knows their own book is the only check that isn't circular. If a repaired file opens differently, renders differently, or loses something, that's the report I most want. 2. Which defects do you fix by hand, over and over? I rank new fixers by what shows up in real books, and my own shelf has run out of candidates that can be repaired without guessing. Everything still on it is either genuinely ambiguous or needs information the file doesn't contain. So the ranking now has to come from outside — if you find yourself making the same tedious correction across books, that's exactly what should be automated next. Repo, with a per-defect catalogue of what each fix changes and when it declines: https://github.com/veripublica/epubsana It's AGPL, or a commercial licence for closed embedders. Written in Rust, no Java, no C dependencies. |
|
|
|
|
|
#2 |
|
Weirdo
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,185
Karma: 13516840
Join Date: Nov 2019
Location: Wuppertal, Germany
Device: Kobo Libra 2, Kobo Sage, Kobo Aura H2O, reMarkable PaperPro
|
Great idea!
|
|
|
|
|
|
#3 | |
|
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 5,874
Karma: 24222221
Join Date: Dec 2010
Device: Kindle PW2
|
Quote:
This would greatly simplify creating plugins for Sigil, Calibre and other epub editors. |
|
|
|
|
|
|
#4 |
|
Connoisseur
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 58
Karma: 100000
Join Date: Jul 2026
Location: Planet Earth
Device: Kobo Forma
|
Thanks, both of you.
Doitsu — you're right, and the current output is a worse fit for your purpose than the docs make it look. Here is a real fix item from --format json as it stands today: Code:
{
"type": "fix",
"outcome": "proposed",
"code": "RSC-005",
"rule": "opf.content_document.schema_violation",
"severity": "error",
"location": "Chapter-003.html",
"message": "Wrap 3 runs of non-block content in <div> in Chapter-003.html",
"data": {
"fix_id": "fix.bare_text_in_body",
"tier": "confirm_needed",
"changes": [
"wrap in <div>: \"<br />\"",
"wrap in <div>: \"<br />\"",
"wrap in <div>: \"<br /><br /><br /><br />\""
]
}
}
I checked where a diff would have to live before replying. Our JSON contract makes items[].data tool-owned and requires consumers to ignore fields they don't recognise, so epubsana can add this on its own: no change to the shared spec, and no existing consumer breaks. The contract also already has the mode this belongs in — --dry-run emits the same envelope with every item marked "proposed", which is precisely the run a plugin would make to show someone a change before applying it. Two issues are now open: - #8 — Emit a unified diff per fix under items[].data: https://github.com/veripublica/epubsana/issues/8 - #7 — Workspace: a speculative apply: https://github.com/veripublica/epubsana/issues/7 The dependency between them is real rather than bureaucratic. Fixers currently write their edits straight into the in-memory container, so there is no way to ask what a fix would produce without producing it. That same missing piece is what per-fix rollback needs, so I would rather build it properly than bolt a diff onto the end. Three questions where your answer is worth more than my guess: 1. One diff per fix, or one per file? Some fixes span files — renaming an invalid id rewrites every reference to it, across content documents and the NCX. 2. Diffs on applied fixes as well, or only under --dry-run? 3. Large diffs — cap them, and if so, how would you want a cap signalled? A fix over a big document can produce a lot of output, and I would rather settle that before someone's plugin runs into it. One case has no text diff at all: the PKG-006 fix re-packages the container so the mimetype entry comes first and stored. Whatever gets emitted there has to say so, rather than showing an empty diff that reads as "nothing changed". No timeline promised — I would rather land it correctly than quickly. |
|
|
|
|
|
#5 | |
|
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 5,874
Karma: 24222221
Join Date: Dec 2010
Device: Kindle PW2
|
For plugin integration one diff per file would be ideal. If that's too much work to implement, one diff per change is also fine.
It should only be output if --dry-run is selected. The idea is that a plugin could run epubsana in --dry-run mode, parse the json output and update only the files where problems were found instead of replacing all files. It doesn't have to be unified diff. Any standard diff format that will allow Python based Calibre or Sigil plugins to update a text file is fine. Quote:
I personally would prefer all diffs, unless the diff output is close to the original file size. Speaking of the diff output. It might even be easier to process the fixes that epubsana found if you embedded the complete fixed file as a base64 encoded zip file in the JSON output. (I realize that this is not very efficient. Maybe some other kind of compressed file format that is compatible with json would work better.) Then a plugin could simply retrieve all updated files from the json file and replace the original text with the changed text. |
|
|
|
|
|
|
#6 |
|
Connoisseur
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 58
Karma: 100000
Join Date: Jul 2026
Location: Planet Earth
Device: Kobo Forma
|
Doitsu — that's three decisions, thank you. All of it is now recorded on #8.
One diff per file. That happens to be the shape the code already has: a fix's changes are recorded per container entry, so a fix that spans files — renaming an invalid id rewrites the document, the other documents linking to it, and the NCX — already produces one record per file. The diff attaches to that. --dry-run only. This halves the work, and it sharpens the dependency rather than removing it: a dry run applies nothing, so the diff can only come from asking what a fix would produce. That was already the harder half; now it is the only path. It also surfaced a requirement neither of us listed. If diffs are dry-run-only, your workflow is two invocations — one to get the diffs and show them, one to apply — and that is only sound if the second run plans identically to the first. So planning determinism has to become a documented guarantee rather than something that happens to be true today. Worth saying out loud before anyone builds on it. A proportional cap. Agreed, and your fourth idea answers the question the cap leaves open — what happens past the threshold. The natural answer is: emit the complete new content of that file instead. So a diff and a full file are not two competing features; the full file is the diff's overflow mode. One field, either a diff or a replacement, explicitly tagged, so you never have to guess which you received. It won't ever silently truncate. On embedding whole fixed books as base64 — I want to make sure I understood yon reads two ways and they are quite different. Did you mean the completerepaired EPUB as one blob, or the new content of each changed file? If it's the second, we already agree — that's the overflow mode above. If it's the first, my hesitation isn't size (though an EPUB is already a deflated ZIP, so compressing it again gains little while base64 adds about a third). It's that epubsana asks about each fix separately, so the repaired book is a function of which fixes you accepted — and under --dry-run you haven't accepted any yet. An embedded "fixed book" would have tomean "the book if you accept everything", which is one arbitrary choice out of many and wrong for anyone who approves two fixes out of three. Diffs compose; a finished artifact doesn't. There's also -o, which writes the repaired file already. The one place that doesn't help is a browser, where there's no filesystem — and there the WASM binding hands the repairedbytes straight back to JavaScript, so that case is covered without going through the JSON at all. But if you're after a single atomic response for a reason I'm not seeing, say so and I'll cost it properly rather than talk you out of it. |
|
|
|
|
|
#7 |
|
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 6,087
Karma: 107275353
Join Date: Apr 2011
Device: pb360
|
I think python can work with gzip compressed JSON objects.
|
|
|
|
|
|
#8 | |
|
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 5,874
Karma: 24222221
Join Date: Dec 2010
Device: Kindle PW2
|
Quote:
There are several ways, in which epubsana could simplify this task. a) output file based diffs for each fixed file. b) embed fixed files with in the json output as lzma, gzip or zip archives. c) simply output an additional compressed file that contains only the files that were changed and maybe some kind of file list. I'm not a programmer, but I'm pretty sure that someone else has already come up with a more elegant solution. |
|
|
|
|
![]() |
| Tags |
| epub, epub2, epub3, epubsana, epubveri |
|
Similar Threads
|
||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| [Editor Plugin] EpubCheck | Doitsu | Plugins | 230 | 04-10-2026 03:19 AM |
| squashed images in Editor/Tools/Reports after search | rjwse@aol.com | Calibre | 1 | 12-18-2019 12:00 PM |
| Possible bug in editor (reports) | ratanplan | Editor | 2 | 02-18-2015 06:22 AM |
| Reports of 3.1 being pushed out for automatic upgrade | Tiersten | Amazon Kindle | 33 | 02-20-2011 10:37 AM |
| Web-based epubcheck upgraded to epubcheck 1.0.5 | kjk | ePub | 4 | 02-09-2010 09:53 PM |