Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@ritojs/core", "@ritojs/kit", "@ritojs/react"]],
"fixed": [["@ritojs/core", "@ritojs/kit", "@ritojs/react"]],
"linked": [],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
Expand Down
9 changes: 9 additions & 0 deletions packages/kit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @ritojs/kit

## 0.12.1

### Patch Changes

- Updated dependencies [9c1688b]
- Updated dependencies [9c1688b]
- Updated dependencies [9c1688b]
- @ritojs/core@0.12.1

## 0.12.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ritojs/kit",
"type": "module",
"version": "0.12.0",
"version": "0.12.1",
"description": "Framework-agnostic controller, overlays, and transitions for @ritojs/core",
"license": "AGPL-3.0-only",
"main": "./dist/index.mjs",
Expand Down
10 changes: 10 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @ritojs/react

## 0.12.1

### Patch Changes

- Updated dependencies [9c1688b]
- Updated dependencies [9c1688b]
- Updated dependencies [9c1688b]
- @ritojs/core@0.12.1
- @ritojs/kit@0.12.1

## 0.12.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ritojs/react",
"type": "module",
"version": "0.12.0",
"version": "0.12.1",
"description": "React hooks and components for @ritojs/core",
"license": "AGPL-3.0-only",
"main": "./dist/index.mjs",
Expand Down
25 changes: 25 additions & 0 deletions packages/rito/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# @ritojs/core

## 0.12.1

### Patch Changes

- 9c1688b: Open spec-violating EPUBs that earlier failed to load. The OPF parser now
defaults missing `dc:title` / `dc:language` / `dc:identifier` to an empty string
with a warning instead of throwing (the structural `<manifest>` / `<spine>`
checks stay strict), and the ZIP reader percent-decodes container paths on a
lookup miss, so a manifest href like `Text/Character%20Profile.xhtml` resolves
to the literal `Text/Character Profile.xhtml` archive entry.
- 9c1688b: Resolve in-content illustrations that previously rendered as broken images.
`loadEpub` now indexes every image file present in the archive — not only those
declared in the OPF manifest — so spec-violating books that reference undeclared
illustrations still get image data. Manifest resource reads are individually
tolerant (a single missing/mislabeled entry is skipped with a warning instead of
aborting the load), and href resolution percent-decodes on miss so references
like `Images/My%20Pic.jpg` match a literal `Images/My Pic.jpg` entry.
- 9c1688b: Parse EPUB chapters whose XHTML is invalid in strict XML. The source normalizer
now escapes stray ampersands (e.g. `Schmidt & Bender`), remaps HTML named
entities undefined without a DTD (`&copy;`, `&mdash;`, `&nbsp;`, …) to numeric
references, and strips characters illegal in XML (C0 controls, `U+FFFE/FFFF`,
lone surrogates, and numeric refs pointing to them), while leaving comments and
CDATA sections untouched. Chapters that previously failed with errors such as
`EntityRef: expecting ';'` or `PCDATA invalid Char value 31` now parse.

## 0.12.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/rito/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ritojs/core",
"type": "module",
"version": "0.12.0",
"version": "0.12.1",
"description": "EPUB rendering engine with a Web Canvas backend",
"license": "AGPL-3.0-only",
"main": "./dist/index.mjs",
Expand Down
30 changes: 19 additions & 11 deletions packages/rito/src/parser/epub/package-parser.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
import type { ManifestItem, PackageDocument, PackageMetadata, SpineItem } from './types';
import { EpubParseError } from './errors';
import type { Logger } from '../../utils/logger';

/**
* Parse an OPF package document XML string into a PackageDocument.
*/
export function parsePackageDocument(opfXml: string): PackageDocument {
export function parsePackageDocument(opfXml: string, logger?: Logger): PackageDocument {
const doc = new DOMParser().parseFromString(opfXml, 'application/xml');

const parserError = doc.querySelector('parsererror');
if (parserError) {
throw new EpubParseError(`Invalid OPF package document: ${parserError.textContent}`);
}

const metadata = parseMetadata(doc);
const metadata = parseMetadata(doc, logger);
const manifest = parseManifest(doc);
const spine = parseSpine(doc);

return { metadata, manifest, spine };
}

function parseMetadata(doc: Document): PackageMetadata {
/**
* Parse Dublin Core metadata. The EPUB spec requires `<dc:title>`,
* `<dc:language>` and `<dc:identifier>`, but spec-violating files (e.g. some
* Sigil exports) omit them while remaining perfectly readable. Rather than
* refusing to open such books, missing fields fall back to an empty string and
* a warning — the structural `<manifest>`/`<spine>` checks below stay strict.
*/
function parseMetadata(doc: Document, logger?: Logger): PackageMetadata {
const title = getMetadataText(doc, 'title');
const language = getMetadataText(doc, 'language');
const identifier = getMetadataText(doc, 'identifier');

if (!title) {
throw new EpubParseError('Missing required <dc:title> in package metadata');
}
if (!language) {
throw new EpubParseError('Missing required <dc:language> in package metadata');
}
if (!title) logger?.warn('Missing <dc:title> in package metadata; using empty title');
if (!language) logger?.warn('Missing <dc:language> in package metadata; using empty language');
if (!identifier) {
throw new EpubParseError('Missing required <dc:identifier> in package metadata');
logger?.warn('Missing <dc:identifier> in package metadata; using empty identifier');
}

const creator = getMetadataText(doc, 'creator');
const result: PackageMetadata = { title, language, identifier };
const result: PackageMetadata = {
title: title ?? '',
language: language ?? '',
identifier: identifier ?? '',
};

if (creator) {
return { ...result, creator };
Expand Down
15 changes: 14 additions & 1 deletion packages/rito/src/parser/epub/zip-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export function createZipReader(data: ArrayBuffer): ZipReader {
return {
readFile(path: string): Uint8Array {
if (!entries) throw new EpubParseError('ZipReader has been closed');
const entry = entries[path];
// OPF/NCX hrefs are URLs, so they may be percent-encoded (e.g.
// "Character%20Profile.xhtml") while the actual zip entry name is literal.
// Try the raw path first, then fall back to its percent-decoded form.
const entry = entries[path] ?? entries[percentDecodePath(path)];
if (!entry) {
throw new EpubParseError(`File not found in EPUB archive: ${path}`);
}
Expand All @@ -36,3 +39,13 @@ export function createZipReader(data: ArrayBuffer): ZipReader {
},
};
}

/** Percent-decode a container path, falling back to the raw path if malformed. */
function percentDecodePath(path: string): string {
if (!path.includes('%')) return path;
try {
return decodeURIComponent(path);
} catch {
return path;
}
}
Loading
Loading