epub-codec 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/codec.cjs +28 -0
- package/dist/codec.d.cts +2308 -0
- package/dist/codec.d.ts +2308 -0
- package/dist/codec.js +25 -0
- package/dist/diagnostics-Ee4kG7mw.d.cts +60 -0
- package/dist/diagnostics-Ee4kG7mw.d.ts +60 -0
- package/dist/diagnostics.cjs +96 -0
- package/dist/diagnostics.d.cts +2 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +85 -0
- package/dist/format.cjs +9 -0
- package/dist/format.d.cts +6 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.js +6 -0
- package/dist/image/dimensions.cjs +82 -0
- package/dist/image/dimensions.d.cts +11 -0
- package/dist/image/dimensions.d.ts +11 -0
- package/dist/image/dimensions.js +79 -0
- package/dist/index.cjs +105 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/nav/nav3.cjs +19 -0
- package/dist/nav/nav3.d.cts +4 -0
- package/dist/nav/nav3.d.ts +4 -0
- package/dist/nav/nav3.js +18 -0
- package/dist/nav/ncx.cjs +17 -0
- package/dist/nav/ncx.d.cts +4 -0
- package/dist/nav/ncx.d.ts +4 -0
- package/dist/nav/ncx.js +16 -0
- package/dist/nav/reconcile.cjs +8 -0
- package/dist/nav/reconcile.d.cts +4 -0
- package/dist/nav/reconcile.d.ts +4 -0
- package/dist/nav/reconcile.js +7 -0
- package/dist/nav/write.cjs +39 -0
- package/dist/nav/write.d.cts +10 -0
- package/dist/nav/write.d.ts +10 -0
- package/dist/nav/write.js +37 -0
- package/dist/ocf/container.cjs +20 -0
- package/dist/ocf/container.d.cts +4 -0
- package/dist/ocf/container.d.ts +4 -0
- package/dist/ocf/container.js +19 -0
- package/dist/ocf/write.cjs +13 -0
- package/dist/ocf/write.d.cts +4 -0
- package/dist/ocf/write.d.ts +4 -0
- package/dist/ocf/write.js +12 -0
- package/dist/opf/metadata.cjs +51 -0
- package/dist/opf/metadata.d.cts +7 -0
- package/dist/opf/metadata.d.ts +7 -0
- package/dist/opf/metadata.js +50 -0
- package/dist/opf/parse.cjs +47 -0
- package/dist/opf/parse.d.cts +6 -0
- package/dist/opf/parse.d.ts +6 -0
- package/dist/opf/parse.js +46 -0
- package/dist/opf/types.cjs +0 -0
- package/dist/opf/types.d.cts +20 -0
- package/dist/opf/types.d.ts +20 -0
- package/dist/opf/types.js +0 -0
- package/dist/opf/write.cjs +55 -0
- package/dist/opf/write.d.cts +12 -0
- package/dist/opf/write.d.ts +12 -0
- package/dist/opf/write.js +54 -0
- package/dist/path.cjs +22 -0
- package/dist/path.d.cts +5 -0
- package/dist/path.d.ts +5 -0
- package/dist/path.js +20 -0
- package/dist/read.cjs +166 -0
- package/dist/read.d.cts +10 -0
- package/dist/read.d.ts +10 -0
- package/dist/read.js +164 -0
- package/dist/util/base64.cjs +51 -0
- package/dist/util/base64.d.cts +5 -0
- package/dist/util/base64.d.ts +5 -0
- package/dist/util/base64.js +49 -0
- package/dist/write.cjs +136 -0
- package/dist/write.d.cts +10 -0
- package/dist/write.d.ts +10 -0
- package/dist/write.js +134 -0
- package/dist/xhtml/context.cjs +0 -0
- package/dist/xhtml/context.d.cts +20 -0
- package/dist/xhtml/context.d.ts +20 -0
- package/dist/xhtml/context.js +0 -0
- package/dist/xhtml/footnote.cjs +32 -0
- package/dist/xhtml/footnote.d.cts +7 -0
- package/dist/xhtml/footnote.d.ts +7 -0
- package/dist/xhtml/footnote.js +29 -0
- package/dist/xhtml/inline.cjs +128 -0
- package/dist/xhtml/inline.d.cts +11 -0
- package/dist/xhtml/inline.d.ts +11 -0
- package/dist/xhtml/inline.js +127 -0
- package/dist/xhtml/list-id.cjs +22 -0
- package/dist/xhtml/list-id.d.cts +13 -0
- package/dist/xhtml/list-id.d.ts +13 -0
- package/dist/xhtml/list-id.js +20 -0
- package/dist/xhtml/read.cjs +423 -0
- package/dist/xhtml/read.d.cts +20 -0
- package/dist/xhtml/read.d.ts +20 -0
- package/dist/xhtml/read.js +422 -0
- package/dist/xhtml/style-constants.cjs +13 -0
- package/dist/xhtml/style-constants.d.cts +8 -0
- package/dist/xhtml/style-constants.d.ts +8 -0
- package/dist/xhtml/style-constants.js +8 -0
- package/dist/xhtml/write.cjs +183 -0
- package/dist/xhtml/write.d.cts +12 -0
- package/dist/xhtml/write.d.ts +12 -0
- package/dist/xhtml/write.js +182 -0
- package/dist/xml/build.cjs +46 -0
- package/dist/xml/build.d.cts +5 -0
- package/dist/xml/build.d.ts +5 -0
- package/dist/xml/build.js +46 -0
- package/dist/xml/entities.cjs +20 -0
- package/dist/xml/entities.d.cts +5 -0
- package/dist/xml/entities.d.ts +5 -0
- package/dist/xml/entities.js +18 -0
- package/dist/xml/node.cjs +67 -0
- package/dist/xml/node.d.cts +83 -0
- package/dist/xml/node.d.ts +83 -0
- package/dist/xml/node.js +58 -0
- package/dist/xml/parse.cjs +88 -0
- package/dist/xml/parse.d.cts +5 -0
- package/dist/xml/parse.d.ts +5 -0
- package/dist/xml/parse.js +88 -0
- package/dist/xml/query.cjs +44 -0
- package/dist/xml/query.d.cts +11 -0
- package/dist/xml/query.d.ts +11 -0
- package/dist/xml/query.js +37 -0
- package/dist/zip.cjs +19 -0
- package/dist/zip.d.cts +9 -0
- package/dist/zip.d.ts +9 -0
- package/dist/zip.js +17 -0
- package/package.json +95 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { XmlNode } from "../xml/node.js";
|
|
2
|
+
import { InlineStyle, XhtmlReadContext } from "./context.js";
|
|
3
|
+
import { ContentRun, RunConstructExtent } from "document-schema.js";
|
|
4
|
+
//#region src/xhtml/inline.d.ts
|
|
5
|
+
interface InlineResult {
|
|
6
|
+
readonly runs: ContentRun[];
|
|
7
|
+
readonly constructs: RunConstructExtent[];
|
|
8
|
+
}
|
|
9
|
+
declare function buildInlineRuns(nodes: readonly XmlNode[], style: InlineStyle, context: XhtmlReadContext): InlineResult;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { InlineResult, buildInlineRuns };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { EpubDiagnosticCodes } from "../diagnostics.js";
|
|
2
|
+
import { attrValue } from "../xml/query.js";
|
|
3
|
+
import { decodeEntities } from "../xml/entities.js";
|
|
4
|
+
import { isFootnoteReferenceAnchor, sameDocumentFragment } from "./footnote.js";
|
|
5
|
+
import { MONOSPACE_FONT_FAMILY } from "./style-constants.js";
|
|
6
|
+
//#region src/xhtml/inline.ts
|
|
7
|
+
function normalizeWhitespace(text) {
|
|
8
|
+
return text.replace(/[ \t\r\n]+/gu, " ");
|
|
9
|
+
}
|
|
10
|
+
function mergeStyle(outer, inner) {
|
|
11
|
+
return {
|
|
12
|
+
...outer,
|
|
13
|
+
...inner
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function styledRun(text, style, hyperlink) {
|
|
17
|
+
const run = { text };
|
|
18
|
+
if (style.bold === true) run.bold = true;
|
|
19
|
+
if (style.italic === true) run.italic = true;
|
|
20
|
+
if (style.underline === true) run.underline = true;
|
|
21
|
+
if (style.strike === true) run.strike = true;
|
|
22
|
+
if (style.fontFamily !== void 0) run.fontFamily = style.fontFamily;
|
|
23
|
+
if (hyperlink !== void 0) run.hyperlink = hyperlink;
|
|
24
|
+
return run;
|
|
25
|
+
}
|
|
26
|
+
function buildInlineRuns(nodes, style, context) {
|
|
27
|
+
const runs = [];
|
|
28
|
+
const constructs = [];
|
|
29
|
+
for (const node of nodes) {
|
|
30
|
+
if (node.type === "text") {
|
|
31
|
+
const text = normalizeWhitespace(decodeEntities(node.value));
|
|
32
|
+
if (text.length > 0) runs.push(styledRun(text, style));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (node.type !== "element") continue;
|
|
36
|
+
appendElement(node, style, context, runs, constructs);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
runs,
|
|
40
|
+
constructs
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function appendElement(element, style, context, runs, constructs) {
|
|
44
|
+
switch (element.tag) {
|
|
45
|
+
case "strong":
|
|
46
|
+
case "b":
|
|
47
|
+
appendNested(element, mergeStyle(style, { bold: true }), context, runs, constructs);
|
|
48
|
+
return;
|
|
49
|
+
case "em":
|
|
50
|
+
case "i":
|
|
51
|
+
appendNested(element, mergeStyle(style, { italic: true }), context, runs, constructs);
|
|
52
|
+
return;
|
|
53
|
+
case "u":
|
|
54
|
+
appendNested(element, mergeStyle(style, { underline: true }), context, runs, constructs);
|
|
55
|
+
return;
|
|
56
|
+
case "s":
|
|
57
|
+
case "strike":
|
|
58
|
+
case "del":
|
|
59
|
+
appendNested(element, mergeStyle(style, { strike: true }), context, runs, constructs);
|
|
60
|
+
return;
|
|
61
|
+
case "code":
|
|
62
|
+
case "kbd":
|
|
63
|
+
case "samp":
|
|
64
|
+
appendNested(element, mergeStyle(style, { fontFamily: MONOSPACE_FONT_FAMILY }), context, runs, constructs);
|
|
65
|
+
return;
|
|
66
|
+
case "sub":
|
|
67
|
+
case "sup":
|
|
68
|
+
context.sink({
|
|
69
|
+
code: EpubDiagnosticCodes.ELEMENT_UNMAPPED,
|
|
70
|
+
severity: "info",
|
|
71
|
+
message: `<${element.tag}> has no document-schema.js run-level field to carry its vertical position; the text is kept, the styling is not`,
|
|
72
|
+
href: context.sourceHref
|
|
73
|
+
});
|
|
74
|
+
appendNested(element, style, context, runs, constructs);
|
|
75
|
+
return;
|
|
76
|
+
case "br":
|
|
77
|
+
runs.push(styledRun("\n", style));
|
|
78
|
+
return;
|
|
79
|
+
case "a":
|
|
80
|
+
appendAnchor(element, style, context, runs, constructs);
|
|
81
|
+
return;
|
|
82
|
+
default: appendNested(element, style, context, runs, constructs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function appendNested(element, style, context, runs, constructs) {
|
|
86
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
87
|
+
runs.push(...nested.runs);
|
|
88
|
+
constructs.push(...nested.constructs);
|
|
89
|
+
}
|
|
90
|
+
function appendAnchor(element, style, context, runs, constructs) {
|
|
91
|
+
const footnoteName = isFootnoteReferenceAnchor(element, context.idElements);
|
|
92
|
+
if (footnoteName !== void 0) {
|
|
93
|
+
const startRun = runs.length;
|
|
94
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
95
|
+
runs.push(...nested.runs);
|
|
96
|
+
constructs.push(...nested.constructs);
|
|
97
|
+
constructs.push({
|
|
98
|
+
descriptor: {
|
|
99
|
+
kind: "anchor",
|
|
100
|
+
anchorType: "footnote",
|
|
101
|
+
name: footnoteName
|
|
102
|
+
},
|
|
103
|
+
startRun,
|
|
104
|
+
endRun: runs.length
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const href = attrValue(element, "href");
|
|
109
|
+
if (href === void 0 || href.length === 0) {
|
|
110
|
+
appendNested(element, style, context, runs, constructs);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const nested = buildInlineRuns(element.children, style, context);
|
|
114
|
+
for (const run of nested.runs) runs.push({
|
|
115
|
+
...run,
|
|
116
|
+
hyperlink: href
|
|
117
|
+
});
|
|
118
|
+
constructs.push(...nested.constructs);
|
|
119
|
+
if (sameDocumentFragment(href) === void 0 && !/^[a-z][a-z0-9+.-]*:/iu.test(href)) context.sink({
|
|
120
|
+
code: EpubDiagnosticCodes.LINK_TARGET_EXTERNAL_ONLY,
|
|
121
|
+
severity: "info",
|
|
122
|
+
message: `href "${href}" carries no URI scheme and is not a same-document fragment; it is stored verbatim on ContentRun.hyperlink without resolution against the package's own manifest`,
|
|
123
|
+
href: context.sourceHref
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { buildInlineRuns };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/xhtml/list-id.ts
|
|
3
|
+
const NUMID_PATTERN = /^epub(\d+):(bullet|ordered)(?:@(\d+))?$/u;
|
|
4
|
+
function mintListNumId(nextId, options) {
|
|
5
|
+
const suffix = options.type === "ordered" && options.start !== void 0 && options.start !== 1 ? `ordered@${String(options.start)}` : options.type;
|
|
6
|
+
return `epub${String(nextId)}:${suffix}`;
|
|
7
|
+
}
|
|
8
|
+
function parseListNumId(numId) {
|
|
9
|
+
const match = NUMID_PATTERN.exec(numId);
|
|
10
|
+
if (match === null) return;
|
|
11
|
+
const type = match[2];
|
|
12
|
+
if (type !== "bullet" && type !== "ordered") return;
|
|
13
|
+
const startText = match[3];
|
|
14
|
+
if (type === "ordered" && startText !== void 0) return {
|
|
15
|
+
type,
|
|
16
|
+
start: Number.parseInt(startText, 10)
|
|
17
|
+
};
|
|
18
|
+
return { type };
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
exports.mintListNumId = mintListNumId;
|
|
22
|
+
exports.parseListNumId = parseListNumId;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/xhtml/list-id.d.ts
|
|
2
|
+
interface ListNumIdInfo {
|
|
3
|
+
readonly type: "bullet" | "ordered";
|
|
4
|
+
readonly start?: number;
|
|
5
|
+
}
|
|
6
|
+
interface MintListNumIdOptions {
|
|
7
|
+
readonly type: "bullet" | "ordered";
|
|
8
|
+
readonly start?: number;
|
|
9
|
+
}
|
|
10
|
+
declare function mintListNumId(nextId: number, options: MintListNumIdOptions): string;
|
|
11
|
+
declare function parseListNumId(numId: string): ListNumIdInfo | undefined;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ListNumIdInfo, MintListNumIdOptions, mintListNumId, parseListNumId };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/xhtml/list-id.d.ts
|
|
2
|
+
interface ListNumIdInfo {
|
|
3
|
+
readonly type: "bullet" | "ordered";
|
|
4
|
+
readonly start?: number;
|
|
5
|
+
}
|
|
6
|
+
interface MintListNumIdOptions {
|
|
7
|
+
readonly type: "bullet" | "ordered";
|
|
8
|
+
readonly start?: number;
|
|
9
|
+
}
|
|
10
|
+
declare function mintListNumId(nextId: number, options: MintListNumIdOptions): string;
|
|
11
|
+
declare function parseListNumId(numId: string): ListNumIdInfo | undefined;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ListNumIdInfo, MintListNumIdOptions, mintListNumId, parseListNumId };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/xhtml/list-id.ts
|
|
2
|
+
const NUMID_PATTERN = /^epub(\d+):(bullet|ordered)(?:@(\d+))?$/u;
|
|
3
|
+
function mintListNumId(nextId, options) {
|
|
4
|
+
const suffix = options.type === "ordered" && options.start !== void 0 && options.start !== 1 ? `ordered@${String(options.start)}` : options.type;
|
|
5
|
+
return `epub${String(nextId)}:${suffix}`;
|
|
6
|
+
}
|
|
7
|
+
function parseListNumId(numId) {
|
|
8
|
+
const match = NUMID_PATTERN.exec(numId);
|
|
9
|
+
if (match === null) return;
|
|
10
|
+
const type = match[2];
|
|
11
|
+
if (type !== "bullet" && type !== "ordered") return;
|
|
12
|
+
const startText = match[3];
|
|
13
|
+
if (type === "ordered" && startText !== void 0) return {
|
|
14
|
+
type,
|
|
15
|
+
start: Number.parseInt(startText, 10)
|
|
16
|
+
};
|
|
17
|
+
return { type };
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { mintListNumId, parseListNumId };
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_diagnostics = require("../diagnostics.cjs");
|
|
3
|
+
const require_xml_query = require("../xml/query.cjs");
|
|
4
|
+
const require_xml_parse = require("../xml/parse.cjs");
|
|
5
|
+
const require_xml_entities = require("../xml/entities.cjs");
|
|
6
|
+
const require_image_dimensions = require("../image/dimensions.cjs");
|
|
7
|
+
const require_util_base64 = require("../util/base64.cjs");
|
|
8
|
+
const require_xml_build = require("../xml/build.cjs");
|
|
9
|
+
const require_xhtml_footnote = require("./footnote.cjs");
|
|
10
|
+
const require_xhtml_style_constants = require("./style-constants.cjs");
|
|
11
|
+
const require_xhtml_inline = require("./inline.cjs");
|
|
12
|
+
const require_xhtml_list_id = require("./list-id.cjs");
|
|
13
|
+
let document_schema_js = require("document-schema.js");
|
|
14
|
+
//#region src/xhtml/read.ts
|
|
15
|
+
function createIdMinter() {
|
|
16
|
+
let nextList = 0;
|
|
17
|
+
let nextItem = 0;
|
|
18
|
+
return {
|
|
19
|
+
mintNumId: (options) => {
|
|
20
|
+
nextList += 1;
|
|
21
|
+
return require_xhtml_list_id.mintListNumId(nextList, options);
|
|
22
|
+
},
|
|
23
|
+
mintItemId: () => {
|
|
24
|
+
nextItem += 1;
|
|
25
|
+
return `item${String(nextItem)}`;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function withQuote(state) {
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
quoteDepth: state.quoteDepth + 1
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function withListItem(state, listItem) {
|
|
36
|
+
return {
|
|
37
|
+
...state,
|
|
38
|
+
listItem,
|
|
39
|
+
list: {
|
|
40
|
+
numId: listItem.numId,
|
|
41
|
+
level: listItem.level
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function decorateParagraph(paragraph, state) {
|
|
46
|
+
let decorated = paragraph;
|
|
47
|
+
if (state.quoteDepth > 0) decorated = {
|
|
48
|
+
...decorated,
|
|
49
|
+
indentLeftPt: state.quoteDepth * 36,
|
|
50
|
+
styleId: decorated.styleId ?? "Quote"
|
|
51
|
+
};
|
|
52
|
+
if (state.listItem !== void 0) {
|
|
53
|
+
const membership = {
|
|
54
|
+
numId: state.listItem.numId,
|
|
55
|
+
level: state.listItem.level,
|
|
56
|
+
itemId: state.listItem.itemId
|
|
57
|
+
};
|
|
58
|
+
decorated = {
|
|
59
|
+
...decorated,
|
|
60
|
+
list: membership
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return decorated;
|
|
64
|
+
}
|
|
65
|
+
function buildIdElementMap(nodes) {
|
|
66
|
+
const map = /* @__PURE__ */ new Map();
|
|
67
|
+
const stack = [...nodes];
|
|
68
|
+
while (stack.length > 0) {
|
|
69
|
+
const node = stack.pop();
|
|
70
|
+
if (node?.type !== "element") continue;
|
|
71
|
+
const id = require_xml_query.attrValue(node, "id");
|
|
72
|
+
if (id !== void 0 && !map.has(id)) map.set(id, node);
|
|
73
|
+
stack.push(...node.children);
|
|
74
|
+
}
|
|
75
|
+
return map;
|
|
76
|
+
}
|
|
77
|
+
function readStyleResidue(html, context) {
|
|
78
|
+
const head = require_xml_query.findChildElement(html.children, "head");
|
|
79
|
+
if (head === void 0) return;
|
|
80
|
+
const styleElements = head.children.filter((node) => node.type === "element" && (node.tag === "style" || node.tag === "link" && require_xml_query.attrValue(node, "rel") === "stylesheet"));
|
|
81
|
+
if (styleElements.length === 0) return;
|
|
82
|
+
context.sink({
|
|
83
|
+
code: require_diagnostics.EpubDiagnosticCodes.STYLE_RESIDUE,
|
|
84
|
+
severity: "info",
|
|
85
|
+
message: "the document's own <head> style declarations (CSS) are quarantined as residue rather than interpreted; the schema is content, not styling",
|
|
86
|
+
href: context.sourceHref
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
format: "epub",
|
|
90
|
+
xml: require_xml_build.buildXml(styleElements)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function readXhtmlBody(xml, options) {
|
|
94
|
+
const nodes = require_xml_parse.parseXml(xml);
|
|
95
|
+
const html = require_xml_query.rootElement(nodes);
|
|
96
|
+
const body = html === void 0 ? void 0 : require_xml_query.findChildElement(html.children, "body");
|
|
97
|
+
if (body === void 0 || html === void 0) return {
|
|
98
|
+
blocks: [],
|
|
99
|
+
source: void 0
|
|
100
|
+
};
|
|
101
|
+
const idElements = buildIdElementMap(body.children);
|
|
102
|
+
const footnoteTargetIds = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const anchor of require_xml_query.elementsWithTag(body.children, "a")) {
|
|
104
|
+
const name = require_xhtml_footnote.isFootnoteReferenceAnchor(anchor, idElements);
|
|
105
|
+
if (name !== void 0) footnoteTargetIds.add(name);
|
|
106
|
+
}
|
|
107
|
+
const context = {
|
|
108
|
+
resolveImage: options.resolveImage,
|
|
109
|
+
sink: options.sink,
|
|
110
|
+
sourceHref: options.sourceHref,
|
|
111
|
+
idElements,
|
|
112
|
+
footnoteTargetIds,
|
|
113
|
+
quoteDepth: 0
|
|
114
|
+
};
|
|
115
|
+
const state = {
|
|
116
|
+
context,
|
|
117
|
+
minter: createIdMinter(),
|
|
118
|
+
quoteDepth: 0,
|
|
119
|
+
listItem: void 0,
|
|
120
|
+
list: void 0,
|
|
121
|
+
contentWidthPt: options.contentWidthPt
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
blocks: readContainerChildren(body.children, state),
|
|
125
|
+
source: readStyleResidue(html, context)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
129
|
+
"p",
|
|
130
|
+
"h1",
|
|
131
|
+
"h2",
|
|
132
|
+
"h3",
|
|
133
|
+
"h4",
|
|
134
|
+
"h5",
|
|
135
|
+
"h6",
|
|
136
|
+
"ul",
|
|
137
|
+
"ol",
|
|
138
|
+
"dl",
|
|
139
|
+
"table",
|
|
140
|
+
"blockquote",
|
|
141
|
+
"pre",
|
|
142
|
+
"hr",
|
|
143
|
+
"figure",
|
|
144
|
+
"figcaption",
|
|
145
|
+
"div",
|
|
146
|
+
"section",
|
|
147
|
+
"article",
|
|
148
|
+
"aside",
|
|
149
|
+
"nav",
|
|
150
|
+
"img"
|
|
151
|
+
]);
|
|
152
|
+
function readContainerChildren(nodes, state) {
|
|
153
|
+
const blocks = [];
|
|
154
|
+
let segment = [];
|
|
155
|
+
const flush = () => {
|
|
156
|
+
if (segment.length === 0) return;
|
|
157
|
+
const inline = require_xhtml_inline.buildInlineRuns(segment, {}, state.context);
|
|
158
|
+
segment = [];
|
|
159
|
+
if (inline.runs.every((run) => run.text.trim().length === 0)) return;
|
|
160
|
+
const paragraph = {
|
|
161
|
+
kind: "paragraph",
|
|
162
|
+
runs: inline.runs,
|
|
163
|
+
...inline.constructs.length > 0 ? { constructs: inline.constructs } : {}
|
|
164
|
+
};
|
|
165
|
+
blocks.push(decorateParagraph(paragraph, state));
|
|
166
|
+
};
|
|
167
|
+
for (const node of nodes) {
|
|
168
|
+
if (node.type === "element" && BLOCK_LEVEL_TAGS.has(node.tag)) {
|
|
169
|
+
flush();
|
|
170
|
+
blocks.push(...readBlockElement(node, state));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (node.type === "element" || node.type === "text") segment.push(node);
|
|
174
|
+
}
|
|
175
|
+
flush();
|
|
176
|
+
return blocks;
|
|
177
|
+
}
|
|
178
|
+
function headingLevelOf(tag) {
|
|
179
|
+
const match = /^h([1-6])$/u.exec(tag);
|
|
180
|
+
return match?.[1] === void 0 ? void 0 : (0, document_schema_js.clampHeadingLevel)(Number(match[1]));
|
|
181
|
+
}
|
|
182
|
+
function readBlockElement(element, state) {
|
|
183
|
+
const id = require_xml_query.attrValue(element, "id");
|
|
184
|
+
const isFootnoteTarget = id !== void 0 && state.context.footnoteTargetIds.has(id) && !require_xhtml_footnote.isFootnoteAside(element);
|
|
185
|
+
const blocks = readBlockElementInner(element, state);
|
|
186
|
+
if (!isFootnoteTarget) return blocks;
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
kind: "constructStart",
|
|
190
|
+
descriptor: {
|
|
191
|
+
kind: "anchor",
|
|
192
|
+
anchorType: "footnote",
|
|
193
|
+
name: id
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
...blocks,
|
|
197
|
+
{ kind: "constructEnd" }
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
function readBlockElementInner(element, state) {
|
|
201
|
+
const headingLevel = headingLevelOf(element.tag);
|
|
202
|
+
if (headingLevel !== void 0) {
|
|
203
|
+
const inline = require_xhtml_inline.buildInlineRuns(element.children, {}, state.context);
|
|
204
|
+
return [decorateParagraph({
|
|
205
|
+
kind: "paragraph",
|
|
206
|
+
headingLevel,
|
|
207
|
+
runs: inline.runs,
|
|
208
|
+
...inline.constructs.length > 0 ? { constructs: inline.constructs } : {}
|
|
209
|
+
}, state)];
|
|
210
|
+
}
|
|
211
|
+
switch (element.tag) {
|
|
212
|
+
case "p": return readContainerChildren(element.children, state);
|
|
213
|
+
case "hr": return [decorateParagraph({
|
|
214
|
+
kind: "paragraph",
|
|
215
|
+
runs: [],
|
|
216
|
+
styleId: require_xhtml_style_constants.HORIZONTAL_RULE_STYLE_ID
|
|
217
|
+
}, state)];
|
|
218
|
+
case "pre": return [readPre(element, state)];
|
|
219
|
+
case "blockquote": return readBlockquote(element, state);
|
|
220
|
+
case "ul":
|
|
221
|
+
case "ol": return readList(element, state);
|
|
222
|
+
case "dl": return readDefinitionList(element, state);
|
|
223
|
+
case "table": return [readTable(element, state)];
|
|
224
|
+
case "figure": return readContainerChildren(element.children, state);
|
|
225
|
+
case "figcaption": return [decorateParagraph({
|
|
226
|
+
kind: "paragraph",
|
|
227
|
+
runs: require_xhtml_inline.buildInlineRuns(element.children, {}, state.context).runs
|
|
228
|
+
}, state)];
|
|
229
|
+
case "img": {
|
|
230
|
+
const block = readImage(element, state);
|
|
231
|
+
return block === void 0 ? [] : [block];
|
|
232
|
+
}
|
|
233
|
+
case "aside": return readAside(element, state);
|
|
234
|
+
default: return readContainerChildren(element.children, state);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function readPre(element, state) {
|
|
238
|
+
const languageSource = require_xml_query.findChildElement(element.children, "code") ?? element;
|
|
239
|
+
const codeLanguage = languageFromClass(require_xml_query.attrValue(languageSource, "class"));
|
|
240
|
+
const text = require_xml_entities.decodeEntities(require_xml_query.textContent(element.children));
|
|
241
|
+
return decorateParagraph({
|
|
242
|
+
kind: "paragraph",
|
|
243
|
+
runs: text.length > 0 ? [{
|
|
244
|
+
text,
|
|
245
|
+
fontFamily: require_xhtml_style_constants.MONOSPACE_FONT_FAMILY
|
|
246
|
+
}] : [],
|
|
247
|
+
...codeLanguage !== void 0 ? { codeLanguage } : {}
|
|
248
|
+
}, state);
|
|
249
|
+
}
|
|
250
|
+
function languageFromClass(className) {
|
|
251
|
+
if (className === void 0) return;
|
|
252
|
+
return /(?:^|\s)language-(\S+)/u.exec(className)?.[1];
|
|
253
|
+
}
|
|
254
|
+
function containsHeading(nodes) {
|
|
255
|
+
for (const node of nodes) {
|
|
256
|
+
if (node.type !== "element") continue;
|
|
257
|
+
if (headingLevelOf(node.tag) !== void 0) return true;
|
|
258
|
+
if (containsHeading(node.children)) return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
function readBlockquote(element, state) {
|
|
263
|
+
const nestedState = withQuote(state);
|
|
264
|
+
const blocks = readContainerChildren(element.children, nestedState);
|
|
265
|
+
if (containsHeading(element.children)) return blocks;
|
|
266
|
+
return [
|
|
267
|
+
{
|
|
268
|
+
kind: "constructStart",
|
|
269
|
+
descriptor: { kind: "division" }
|
|
270
|
+
},
|
|
271
|
+
...blocks,
|
|
272
|
+
{ kind: "constructEnd" }
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
function readList(element, state) {
|
|
276
|
+
const numId = state.list?.numId ?? state.minter.mintNumId({
|
|
277
|
+
type: element.tag === "ol" ? "ordered" : "bullet",
|
|
278
|
+
start: startAttr(element)
|
|
279
|
+
});
|
|
280
|
+
const level = state.list === void 0 ? 0 : state.list.level + 1;
|
|
281
|
+
const blocks = [];
|
|
282
|
+
for (const child of element.children) {
|
|
283
|
+
if (child.type !== "element" || child.tag !== "li") continue;
|
|
284
|
+
const itemState = withListItem(state, {
|
|
285
|
+
numId,
|
|
286
|
+
level,
|
|
287
|
+
itemId: state.minter.mintItemId()
|
|
288
|
+
});
|
|
289
|
+
blocks.push(...readContainerChildren(child.children, itemState));
|
|
290
|
+
}
|
|
291
|
+
return blocks;
|
|
292
|
+
}
|
|
293
|
+
function startAttr(element) {
|
|
294
|
+
return positiveIntAttr(element, "start");
|
|
295
|
+
}
|
|
296
|
+
function readDefinitionList(element, state) {
|
|
297
|
+
const blocks = [];
|
|
298
|
+
for (const child of element.children) {
|
|
299
|
+
if (child.type !== "element") continue;
|
|
300
|
+
if (child.tag === "dt") {
|
|
301
|
+
const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
|
|
302
|
+
blocks.push(decorateParagraph({
|
|
303
|
+
kind: "paragraph",
|
|
304
|
+
runs: inline.runs
|
|
305
|
+
}, state));
|
|
306
|
+
} else if (child.tag === "dd") {
|
|
307
|
+
const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
|
|
308
|
+
blocks.push(decorateParagraph({
|
|
309
|
+
kind: "paragraph",
|
|
310
|
+
runs: inline.runs,
|
|
311
|
+
indentLeftPt: 36 + state.quoteDepth * 36
|
|
312
|
+
}, state));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return blocks;
|
|
316
|
+
}
|
|
317
|
+
function readTable(element, state) {
|
|
318
|
+
const rows = [];
|
|
319
|
+
let columnCount = 0;
|
|
320
|
+
for (const section of element.children) {
|
|
321
|
+
if (section.type !== "element") continue;
|
|
322
|
+
const rowContainers = section.tag === "tr" ? [section] : section.tag === "thead" || section.tag === "tbody" || section.tag === "tfoot" ? section.children.filter((c) => c.type === "element" && c.tag === "tr") : [];
|
|
323
|
+
for (const tr of rowContainers) {
|
|
324
|
+
const cells = [];
|
|
325
|
+
for (const cellNode of tr.children) {
|
|
326
|
+
if (cellNode.type !== "element" || cellNode.tag !== "td" && cellNode.tag !== "th") continue;
|
|
327
|
+
const cellStyle = cellNode.tag === "th" ? { bold: true } : {};
|
|
328
|
+
const paragraph = {
|
|
329
|
+
kind: "paragraph",
|
|
330
|
+
runs: require_xhtml_inline.buildInlineRuns(cellNode.children, cellStyle, state.context).runs
|
|
331
|
+
};
|
|
332
|
+
const colSpan = positiveIntAttr(cellNode, "colspan");
|
|
333
|
+
const rowSpan = positiveIntAttr(cellNode, "rowspan");
|
|
334
|
+
cells.push({
|
|
335
|
+
blocks: [paragraph],
|
|
336
|
+
...colSpan !== void 0 ? { colSpan } : {},
|
|
337
|
+
...rowSpan !== void 0 ? { rowSpan } : {}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
columnCount = Math.max(columnCount, cells.length);
|
|
341
|
+
rows.push({ cells });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const width = columnCount > 0 ? state.contentWidthPt / columnCount : state.contentWidthPt;
|
|
345
|
+
return {
|
|
346
|
+
kind: "table",
|
|
347
|
+
rows,
|
|
348
|
+
columnWidthsPt: new Array(Math.max(columnCount, 1)).fill(width)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function positiveIntAttr(element, name) {
|
|
352
|
+
const raw = require_xml_query.attrValue(element, name);
|
|
353
|
+
if (raw === void 0) return;
|
|
354
|
+
const value = Number.parseInt(raw, 10);
|
|
355
|
+
return Number.isInteger(value) && value > 0 ? value : void 0;
|
|
356
|
+
}
|
|
357
|
+
function readImage(element, state) {
|
|
358
|
+
const src = require_xml_query.attrValue(element, "src");
|
|
359
|
+
const alt = require_xml_query.attrValue(element, "alt");
|
|
360
|
+
if (src === void 0) return;
|
|
361
|
+
const bytes = state.context.resolveImage(src);
|
|
362
|
+
if (bytes === void 0) {
|
|
363
|
+
state.context.sink({
|
|
364
|
+
code: require_diagnostics.EpubDiagnosticCodes.IMAGE_UNRESOLVED,
|
|
365
|
+
severity: "warning",
|
|
366
|
+
message: `<img src="${src}"> names no resolvable manifest part; degraded to its alt text`,
|
|
367
|
+
href: state.context.sourceHref
|
|
368
|
+
});
|
|
369
|
+
return alt === void 0 || alt.length === 0 ? void 0 : decorateParagraph({
|
|
370
|
+
kind: "paragraph",
|
|
371
|
+
runs: [{ text: require_xml_entities.decodeEntities(alt) }]
|
|
372
|
+
}, state);
|
|
373
|
+
}
|
|
374
|
+
const format = require_image_dimensions.detectImageFormat(bytes);
|
|
375
|
+
const dimensions = require_image_dimensions.readImageDimensions(bytes);
|
|
376
|
+
if (format === void 0 || dimensions === void 0) {
|
|
377
|
+
state.context.sink({
|
|
378
|
+
code: require_diagnostics.EpubDiagnosticCodes.IMAGE_FORMAT_UNSUPPORTED,
|
|
379
|
+
severity: "warning",
|
|
380
|
+
message: `<img src="${src}"> is neither a PNG nor a JPEG (document-schema.js's ContentImageBlock supports only those two); degraded to its alt text`,
|
|
381
|
+
href: state.context.sourceHref
|
|
382
|
+
});
|
|
383
|
+
return alt === void 0 || alt.length === 0 ? void 0 : decorateParagraph({
|
|
384
|
+
kind: "paragraph",
|
|
385
|
+
runs: [{ text: require_xml_entities.decodeEntities(alt) }]
|
|
386
|
+
}, state);
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
kind: "image",
|
|
390
|
+
format,
|
|
391
|
+
base64: require_util_base64.bytesToBase64(bytes),
|
|
392
|
+
widthPt: dimensions.widthPx * require_image_dimensions.POINTS_PER_PIXEL,
|
|
393
|
+
heightPt: dimensions.heightPx * require_image_dimensions.POINTS_PER_PIXEL,
|
|
394
|
+
...alt !== void 0 && alt.length > 0 ? { altText: require_xml_entities.decodeEntities(alt) } : {}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function readAside(element, state) {
|
|
398
|
+
if (!require_xhtml_footnote.isFootnoteAside(element)) return readContainerChildren(element.children, state);
|
|
399
|
+
const name = require_xml_query.attrValue(element, "id");
|
|
400
|
+
if (name === void 0) {
|
|
401
|
+
state.context.sink({
|
|
402
|
+
code: require_diagnostics.EpubDiagnosticCodes.FOOTNOTE_TARGET_UNRESOLVED,
|
|
403
|
+
severity: "warning",
|
|
404
|
+
message: "a footnote <aside> carries no id and cannot be referenced; read as ordinary content",
|
|
405
|
+
href: state.context.sourceHref
|
|
406
|
+
});
|
|
407
|
+
return readContainerChildren(element.children, state);
|
|
408
|
+
}
|
|
409
|
+
return [
|
|
410
|
+
{
|
|
411
|
+
kind: "constructStart",
|
|
412
|
+
descriptor: {
|
|
413
|
+
kind: "anchor",
|
|
414
|
+
anchorType: "footnote",
|
|
415
|
+
name
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
...readContainerChildren(element.children, state),
|
|
419
|
+
{ kind: "constructEnd" }
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
exports.readXhtmlBody = readXhtmlBody;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ContentBlock, SourceResidue } from "document-schema.js";
|
|
2
|
+
//#region src/xhtml/read.d.ts
|
|
3
|
+
interface ReadXhtmlBodyOptions {
|
|
4
|
+
readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
|
5
|
+
readonly sink: (diagnostic: {
|
|
6
|
+
code: string;
|
|
7
|
+
severity: "info" | "warning";
|
|
8
|
+
message: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
}) => void;
|
|
11
|
+
readonly sourceHref: string;
|
|
12
|
+
readonly contentWidthPt: number;
|
|
13
|
+
}
|
|
14
|
+
interface ReadXhtmlBodyResult {
|
|
15
|
+
readonly blocks: ContentBlock[];
|
|
16
|
+
readonly source: SourceResidue | undefined;
|
|
17
|
+
}
|
|
18
|
+
declare function readXhtmlBody(xml: string, options: ReadXhtmlBodyOptions): ReadXhtmlBodyResult;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ReadXhtmlBodyOptions, ReadXhtmlBodyResult, readXhtmlBody };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ContentBlock, SourceResidue } from "document-schema.js";
|
|
2
|
+
//#region src/xhtml/read.d.ts
|
|
3
|
+
interface ReadXhtmlBodyOptions {
|
|
4
|
+
readonly resolveImage: (href: string) => Uint8Array<ArrayBuffer> | undefined;
|
|
5
|
+
readonly sink: (diagnostic: {
|
|
6
|
+
code: string;
|
|
7
|
+
severity: "info" | "warning";
|
|
8
|
+
message: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
}) => void;
|
|
11
|
+
readonly sourceHref: string;
|
|
12
|
+
readonly contentWidthPt: number;
|
|
13
|
+
}
|
|
14
|
+
interface ReadXhtmlBodyResult {
|
|
15
|
+
readonly blocks: ContentBlock[];
|
|
16
|
+
readonly source: SourceResidue | undefined;
|
|
17
|
+
}
|
|
18
|
+
declare function readXhtmlBody(xml: string, options: ReadXhtmlBodyOptions): ReadXhtmlBodyResult;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ReadXhtmlBodyOptions, ReadXhtmlBodyResult, readXhtmlBody };
|