epub-codec 1.1.0 → 1.2.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/README.md +3 -1
- package/dist/codec.d.cts +2 -2
- package/dist/codec.d.ts +2 -2
- package/dist/{diagnostics-tvGwPH46.d.cts → diagnostics-C0CRdoda.d.cts} +8 -0
- package/dist/{diagnostics-tvGwPH46.d.ts → diagnostics-C0CRdoda.d.ts} +8 -0
- package/dist/diagnostics.cjs +8 -0
- package/dist/diagnostics.d.cts +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +8 -0
- package/dist/index.cjs +8 -3
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -5
- package/dist/opf/metadata.cjs +4 -5
- package/dist/opf/metadata.d.cts +1 -1
- package/dist/opf/metadata.d.ts +1 -1
- package/dist/opf/metadata.js +5 -6
- package/dist/opf/parse.d.cts +1 -1
- package/dist/opf/parse.d.ts +1 -1
- package/dist/read.d.cts +1 -1
- package/dist/read.d.ts +1 -1
- package/dist/write.cjs +1 -1
- package/dist/write.d.cts +1 -1
- package/dist/write.d.ts +1 -1
- package/dist/write.js +1 -1
- package/dist/xhtml/context.cjs +18 -0
- package/dist/xhtml/context.d.cts +4 -2
- package/dist/xhtml/context.d.ts +4 -2
- package/dist/xhtml/context.js +16 -0
- package/dist/xhtml/inline.cjs +9 -3
- package/dist/xhtml/inline.js +9 -3
- package/dist/xhtml/read.cjs +330 -47
- package/dist/xhtml/read.js +331 -48
- package/dist/xhtml/write.cjs +72 -23
- package/dist/xhtml/write.d.cts +1 -1
- package/dist/xhtml/write.d.ts +1 -1
- package/dist/xhtml/write.js +72 -23
- package/dist/xml/entities.cjs +4 -0
- package/dist/xml/entities.d.cts +3 -1
- package/dist/xml/entities.d.ts +3 -1
- package/dist/xml/entities.js +4 -1
- package/dist/xml/node.cjs +4 -0
- package/dist/xml/node.d.cts +2 -1
- package/dist/xml/node.d.ts +2 -1
- package/dist/xml/node.js +4 -1
- package/dist/xml/query.cjs +9 -0
- package/dist/xml/query.d.cts +2 -1
- package/dist/xml/query.d.ts +2 -1
- package/dist/xml/query.js +9 -1
- package/package.json +2 -2
package/dist/xhtml/inline.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { EpubDiagnosticCodes } from "../diagnostics.js";
|
|
2
|
+
import { decodeEntities, decodeTextLikeNode } from "../xml/entities.js";
|
|
3
|
+
import { isTextLikeNode } from "../xml/node.js";
|
|
2
4
|
import { attrValue } from "../xml/query.js";
|
|
3
|
-
import {
|
|
5
|
+
import { isInertElement, reportInertElementSkip } from "./context.js";
|
|
4
6
|
import { isFootnoteReferenceAnchor, sameDocumentFragment } from "./footnote.js";
|
|
5
7
|
import { MONOSPACE_FONT_FAMILY } from "./style-constants.js";
|
|
6
8
|
//#region src/xhtml/inline.ts
|
|
@@ -27,8 +29,8 @@ function buildInlineRuns(nodes, style, context) {
|
|
|
27
29
|
const runs = [];
|
|
28
30
|
const constructs = [];
|
|
29
31
|
for (const node of nodes) {
|
|
30
|
-
if (node
|
|
31
|
-
const text = normalizeWhitespace(
|
|
32
|
+
if (isTextLikeNode(node)) {
|
|
33
|
+
const text = normalizeWhitespace(decodeTextLikeNode(node));
|
|
32
34
|
if (text.length > 0) runs.push(styledRun(text, style));
|
|
33
35
|
continue;
|
|
34
36
|
}
|
|
@@ -41,6 +43,10 @@ function buildInlineRuns(nodes, style, context) {
|
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
function appendElement(element, style, context, runs, constructs) {
|
|
46
|
+
if (isInertElement(element.tag)) {
|
|
47
|
+
reportInertElementSkip(element.tag, context);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
44
50
|
switch (element.tag) {
|
|
45
51
|
case "strong":
|
|
46
52
|
case "b":
|
package/dist/xhtml/read.cjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_diagnostics = require("../diagnostics.cjs");
|
|
3
|
+
const require_xml_entities = require("../xml/entities.cjs");
|
|
4
|
+
const require_xml_node = require("../xml/node.cjs");
|
|
3
5
|
const require_xml_query = require("../xml/query.cjs");
|
|
4
6
|
const require_xml_parse = require("../xml/parse.cjs");
|
|
5
|
-
const require_xml_entities = require("../xml/entities.cjs");
|
|
6
7
|
const require_image_dimensions = require("../image/dimensions.cjs");
|
|
7
8
|
const require_util_base64 = require("../util/base64.cjs");
|
|
8
9
|
const require_xml_build = require("../xml/build.cjs");
|
|
10
|
+
const require_xhtml_context = require("./context.cjs");
|
|
9
11
|
const require_xhtml_footnote = require("./footnote.cjs");
|
|
10
12
|
const require_xhtml_style_constants = require("./style-constants.cjs");
|
|
11
13
|
const require_xhtml_inline = require("./inline.cjs");
|
|
@@ -62,18 +64,32 @@ function decorateParagraph(paragraph, state) {
|
|
|
62
64
|
}
|
|
63
65
|
return decorated;
|
|
64
66
|
}
|
|
67
|
+
function constructsField(inline) {
|
|
68
|
+
return inline.constructs.length > 0 ? { constructs: inline.constructs } : {};
|
|
69
|
+
}
|
|
65
70
|
function buildIdElementMap(nodes) {
|
|
66
71
|
const map = /* @__PURE__ */ new Map();
|
|
67
72
|
const stack = [...nodes];
|
|
68
73
|
while (stack.length > 0) {
|
|
69
74
|
const node = stack.pop();
|
|
70
|
-
if (node?.type !== "element") continue;
|
|
75
|
+
if (node?.type !== "element" || require_xhtml_context.isInertElement(node.tag)) continue;
|
|
71
76
|
const id = require_xml_query.attrValue(node, "id");
|
|
72
77
|
if (id !== void 0 && !map.has(id)) map.set(id, node);
|
|
73
78
|
stack.push(...node.children);
|
|
74
79
|
}
|
|
75
80
|
return map;
|
|
76
81
|
}
|
|
82
|
+
function elementsWithTagSkippingInert(nodes, tag) {
|
|
83
|
+
const out = [];
|
|
84
|
+
const stack = [...nodes];
|
|
85
|
+
while (stack.length > 0) {
|
|
86
|
+
const node = stack.pop();
|
|
87
|
+
if (node?.type !== "element" || require_xhtml_context.isInertElement(node.tag)) continue;
|
|
88
|
+
if (node.tag === tag) out.push(node);
|
|
89
|
+
stack.push(...node.children);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
77
93
|
function readStyleResidue(html, context) {
|
|
78
94
|
const head = require_xml_query.findChildElement(html.children, "head");
|
|
79
95
|
if (head === void 0) return;
|
|
@@ -100,7 +116,7 @@ function readXhtmlBody(xml, options) {
|
|
|
100
116
|
};
|
|
101
117
|
const idElements = buildIdElementMap(body.children);
|
|
102
118
|
const footnoteTargetIds = /* @__PURE__ */ new Set();
|
|
103
|
-
for (const anchor of
|
|
119
|
+
for (const anchor of elementsWithTagSkippingInert(body.children, "a")) {
|
|
104
120
|
const name = require_xhtml_footnote.isFootnoteReferenceAnchor(anchor, idElements);
|
|
105
121
|
if (name !== void 0) footnoteTargetIds.add(name);
|
|
106
122
|
}
|
|
@@ -156,11 +172,11 @@ function readContainerChildren(nodes, state) {
|
|
|
156
172
|
if (segment.length === 0) return;
|
|
157
173
|
const inline = require_xhtml_inline.buildInlineRuns(segment, {}, state.context);
|
|
158
174
|
segment = [];
|
|
159
|
-
if (inline.runs.every((run) => run.text.trim().length === 0)) return;
|
|
175
|
+
if (inline.runs.every((run) => run.text.trim().length === 0) && inline.constructs.length === 0) return;
|
|
160
176
|
const paragraph = {
|
|
161
177
|
kind: "paragraph",
|
|
162
178
|
runs: inline.runs,
|
|
163
|
-
...inline
|
|
179
|
+
...constructsField(inline)
|
|
164
180
|
};
|
|
165
181
|
blocks.push(decorateParagraph(paragraph, state));
|
|
166
182
|
};
|
|
@@ -170,7 +186,7 @@ function readContainerChildren(nodes, state) {
|
|
|
170
186
|
blocks.push(...readBlockElement(node, state));
|
|
171
187
|
continue;
|
|
172
188
|
}
|
|
173
|
-
if (node.type === "element" || node
|
|
189
|
+
if (node.type === "element" || require_xml_node.isTextLikeNode(node)) segment.push(node);
|
|
174
190
|
}
|
|
175
191
|
flush();
|
|
176
192
|
return blocks;
|
|
@@ -205,7 +221,7 @@ function readBlockElementInner(element, state) {
|
|
|
205
221
|
kind: "paragraph",
|
|
206
222
|
headingLevel,
|
|
207
223
|
runs: inline.runs,
|
|
208
|
-
...inline
|
|
224
|
+
...constructsField(inline)
|
|
209
225
|
}, state)];
|
|
210
226
|
}
|
|
211
227
|
switch (element.tag) {
|
|
@@ -220,12 +236,16 @@ function readBlockElementInner(element, state) {
|
|
|
220
236
|
case "ul":
|
|
221
237
|
case "ol": return readList(element, state);
|
|
222
238
|
case "dl": return readDefinitionList(element, state);
|
|
223
|
-
case "table": return
|
|
239
|
+
case "table": return readTable(element, state);
|
|
224
240
|
case "figure": return readContainerChildren(element.children, state);
|
|
225
|
-
case "figcaption":
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
case "figcaption": {
|
|
242
|
+
const inline = require_xhtml_inline.buildInlineRuns(element.children, {}, state.context);
|
|
243
|
+
return [decorateParagraph({
|
|
244
|
+
kind: "paragraph",
|
|
245
|
+
runs: inline.runs,
|
|
246
|
+
...constructsField(inline)
|
|
247
|
+
}, state)];
|
|
248
|
+
}
|
|
229
249
|
case "img": {
|
|
230
250
|
const block = readImage(element, state);
|
|
231
251
|
return block === void 0 ? [] : [block];
|
|
@@ -237,23 +257,123 @@ function readBlockElementInner(element, state) {
|
|
|
237
257
|
function readPre(element, state) {
|
|
238
258
|
const languageSource = require_xml_query.findChildElement(element.children, "code") ?? element;
|
|
239
259
|
const codeLanguage = languageFromClass(require_xml_query.attrValue(languageSource, "class"));
|
|
240
|
-
const
|
|
260
|
+
const inline = containsFootnoteReference(element.children, state.context) ? readPreRuns(element.children, state.context) : {
|
|
261
|
+
runs: readPreFlatRuns(element.children, state.context),
|
|
262
|
+
constructs: []
|
|
263
|
+
};
|
|
241
264
|
return decorateParagraph({
|
|
242
265
|
kind: "paragraph",
|
|
243
|
-
runs:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
...codeLanguage !== void 0 ? { codeLanguage } : {}
|
|
266
|
+
runs: inline.runs,
|
|
267
|
+
preformatted: true,
|
|
268
|
+
...codeLanguage !== void 0 ? { codeLanguage } : {},
|
|
269
|
+
...constructsField(inline)
|
|
248
270
|
}, state);
|
|
249
271
|
}
|
|
272
|
+
function readPreFlatRuns(nodes, context) {
|
|
273
|
+
const text = readPreText(nodes, context);
|
|
274
|
+
return text.length > 0 ? [{
|
|
275
|
+
text,
|
|
276
|
+
fontFamily: require_xhtml_style_constants.MONOSPACE_FONT_FAMILY
|
|
277
|
+
}] : [];
|
|
278
|
+
}
|
|
279
|
+
function containsFootnoteReference(nodes, context) {
|
|
280
|
+
for (const node of nodes) {
|
|
281
|
+
if (node.type !== "element" || require_xhtml_context.isInertElement(node.tag)) continue;
|
|
282
|
+
if (node.tag === "a" && require_xhtml_footnote.isFootnoteReferenceAnchor(node, context.idElements) !== void 0) return true;
|
|
283
|
+
if (containsFootnoteReference(node.children, context)) return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
function readPreRuns(nodes, context) {
|
|
288
|
+
const runs = [];
|
|
289
|
+
const constructs = [];
|
|
290
|
+
let buffer = "";
|
|
291
|
+
const flush = () => {
|
|
292
|
+
if (buffer.length === 0) return;
|
|
293
|
+
runs.push({
|
|
294
|
+
text: buffer,
|
|
295
|
+
fontFamily: require_xhtml_style_constants.MONOSPACE_FONT_FAMILY
|
|
296
|
+
});
|
|
297
|
+
buffer = "";
|
|
298
|
+
};
|
|
299
|
+
for (const node of nodes) {
|
|
300
|
+
if (require_xml_node.isTextLikeNode(node)) {
|
|
301
|
+
buffer += require_xml_entities.decodeTextLikeNode(node);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (node.type !== "element") continue;
|
|
305
|
+
if (require_xhtml_context.isInertElement(node.tag)) {
|
|
306
|
+
require_xhtml_context.reportInertElementSkip(node.tag, context);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (node.tag === "img") {
|
|
310
|
+
buffer += require_xml_entities.decodeEntities(readPreImageFallbackText(node, context));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (node.tag === "br") {
|
|
314
|
+
buffer += "\n";
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const footnoteName = node.tag === "a" ? require_xhtml_footnote.isFootnoteReferenceAnchor(node, context.idElements) : void 0;
|
|
318
|
+
if (footnoteName === void 0 && !containsFootnoteReference([node], context)) {
|
|
319
|
+
buffer += readPreText(node.children, context);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
flush();
|
|
323
|
+
const startRun = runs.length;
|
|
324
|
+
const nested = readPreRuns(node.children, context);
|
|
325
|
+
runs.push(...nested.runs);
|
|
326
|
+
for (const construct of nested.constructs) constructs.push({
|
|
327
|
+
...construct,
|
|
328
|
+
startRun: construct.startRun + startRun,
|
|
329
|
+
endRun: construct.endRun + startRun
|
|
330
|
+
});
|
|
331
|
+
if (footnoteName !== void 0) constructs.push({
|
|
332
|
+
descriptor: {
|
|
333
|
+
kind: "anchor",
|
|
334
|
+
anchorType: "footnote",
|
|
335
|
+
name: footnoteName
|
|
336
|
+
},
|
|
337
|
+
startRun,
|
|
338
|
+
endRun: runs.length
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
flush();
|
|
342
|
+
return {
|
|
343
|
+
runs,
|
|
344
|
+
constructs
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function readPreText(nodes, context) {
|
|
348
|
+
let out = "";
|
|
349
|
+
for (const node of nodes) if (require_xml_node.isTextLikeNode(node)) out += require_xml_entities.decodeTextLikeNode(node);
|
|
350
|
+
else if (node.type === "element" && node.tag === "br") out += "\n";
|
|
351
|
+
else if (node.type === "element" && node.tag === "img") out += require_xml_entities.decodeEntities(readPreImageFallbackText(node, context));
|
|
352
|
+
else if (node.type === "element" && require_xhtml_context.isInertElement(node.tag)) {
|
|
353
|
+
require_xhtml_context.reportInertElementSkip(node.tag, context);
|
|
354
|
+
continue;
|
|
355
|
+
} else if (node.type === "element") out += readPreText(node.children, context);
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
function readPreImageFallbackText(element, context) {
|
|
359
|
+
const src = require_xml_query.attrValue(element, "src");
|
|
360
|
+
const alt = require_xml_query.attrValue(element, "alt");
|
|
361
|
+
const label = src === void 0 ? "<img>" : `<img src="${src}">`;
|
|
362
|
+
context.sink({
|
|
363
|
+
code: require_diagnostics.EpubDiagnosticCodes.IMAGE_PRE_UNSUPPORTED,
|
|
364
|
+
severity: "warning",
|
|
365
|
+
message: `${label} inside a <pre>/<code> block cannot become a real image block (this package reads a <pre> as a single text-content paragraph, so there is no block list to insert an image block into); degraded to its alt text`,
|
|
366
|
+
href: context.sourceHref
|
|
367
|
+
});
|
|
368
|
+
return alt ?? "";
|
|
369
|
+
}
|
|
250
370
|
function languageFromClass(className) {
|
|
251
371
|
if (className === void 0) return;
|
|
252
372
|
return /(?:^|\s)language-(\S+)/u.exec(className)?.[1];
|
|
253
373
|
}
|
|
254
374
|
function containsHeading(nodes) {
|
|
255
375
|
for (const node of nodes) {
|
|
256
|
-
if (node.type !== "element") continue;
|
|
376
|
+
if (node.type !== "element" || require_xhtml_context.isInertElement(node.tag)) continue;
|
|
257
377
|
if (headingLevelOf(node.tag) !== void 0) return true;
|
|
258
378
|
if (containsHeading(node.children)) return true;
|
|
259
379
|
}
|
|
@@ -279,74 +399,237 @@ function readList(element, state) {
|
|
|
279
399
|
});
|
|
280
400
|
const level = state.list === void 0 ? 0 : state.list.level + 1;
|
|
281
401
|
const blocks = [];
|
|
402
|
+
let previousItem;
|
|
403
|
+
let strayNodes = [];
|
|
282
404
|
for (const child of element.children) {
|
|
283
|
-
if (child.type
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
405
|
+
if (child.type === "element" && child.tag === "li") {
|
|
406
|
+
blocks.push(...flushListStrayContent(strayNodes, previousItem, element.tag, state));
|
|
407
|
+
strayNodes = [];
|
|
408
|
+
previousItem = {
|
|
409
|
+
numId,
|
|
410
|
+
level,
|
|
411
|
+
itemId: state.minter.mintItemId()
|
|
412
|
+
};
|
|
413
|
+
blocks.push(...readContainerChildren(child.children, withListItem(state, previousItem)));
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (child.type === "element" && require_xhtml_context.isInertElement(child.tag)) {
|
|
417
|
+
require_xhtml_context.reportInertElementSkip(child.tag, state.context);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (child.type === "element" || require_xml_node.isTextLikeNode(child)) strayNodes.push(child);
|
|
290
421
|
}
|
|
422
|
+
blocks.push(...flushListStrayContent(strayNodes, previousItem, element.tag, state));
|
|
423
|
+
return blocks;
|
|
424
|
+
}
|
|
425
|
+
function flushListStrayContent(nodes, previousItem, tag, state) {
|
|
426
|
+
if (nodes.length === 0) return [];
|
|
427
|
+
const blocks = readContainerChildren(nodes, previousItem === void 0 ? state : withListItem(state, previousItem));
|
|
428
|
+
if (blocks.length === 0) return [];
|
|
429
|
+
state.context.sink({
|
|
430
|
+
code: require_diagnostics.EpubDiagnosticCodes.LIST_CONTENT_OUTSIDE_ITEM,
|
|
431
|
+
severity: "info",
|
|
432
|
+
message: previousItem === void 0 ? `content sits directly inside a <${tag}> before its first <li> (not valid HTML5); recovered as ordinary content immediately before the list, inheriting whatever list membership its own enclosing context already carries (none, unless this <${tag}> is itself nested inside another list's <li>)` : `content sits directly inside a <${tag}> rather than inside an <li> (not valid HTML5); recovered as a continuation of the preceding <li>'s own content`,
|
|
433
|
+
href: state.context.sourceHref
|
|
434
|
+
});
|
|
291
435
|
return blocks;
|
|
292
436
|
}
|
|
293
437
|
function startAttr(element) {
|
|
294
438
|
return positiveIntAttr(element, "start");
|
|
295
439
|
}
|
|
296
440
|
function readDefinitionList(element, state) {
|
|
441
|
+
return readDefinitionListEntries(element.children, state);
|
|
442
|
+
}
|
|
443
|
+
function readDefinitionListEntries(nodes, state) {
|
|
297
444
|
const blocks = [];
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
445
|
+
let strayNodes = [];
|
|
446
|
+
const flushStray = () => {
|
|
447
|
+
blocks.push(...flushDefinitionListStrayContent(strayNodes, state));
|
|
448
|
+
strayNodes = [];
|
|
449
|
+
};
|
|
450
|
+
for (const child of nodes) {
|
|
451
|
+
if (child.type === "element" && child.tag === "dt") {
|
|
452
|
+
flushStray();
|
|
301
453
|
const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
|
|
302
454
|
blocks.push(decorateParagraph({
|
|
303
455
|
kind: "paragraph",
|
|
304
|
-
runs: inline.runs
|
|
456
|
+
runs: inline.runs,
|
|
457
|
+
...constructsField(inline)
|
|
305
458
|
}, state));
|
|
306
|
-
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (child.type === "element" && child.tag === "dd") {
|
|
462
|
+
flushStray();
|
|
307
463
|
const inline = require_xhtml_inline.buildInlineRuns(child.children, {}, state.context);
|
|
308
464
|
blocks.push(decorateParagraph({
|
|
309
465
|
kind: "paragraph",
|
|
310
466
|
runs: inline.runs,
|
|
467
|
+
...constructsField(inline),
|
|
311
468
|
indentLeftPt: 36 + state.quoteDepth * 36
|
|
312
469
|
}, state));
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (child.type === "element" && child.tag === "div") {
|
|
473
|
+
flushStray();
|
|
474
|
+
blocks.push(...readDefinitionListEntries(child.children, state));
|
|
475
|
+
continue;
|
|
313
476
|
}
|
|
477
|
+
if (child.type === "element" && require_xhtml_context.isInertElement(child.tag)) {
|
|
478
|
+
require_xhtml_context.reportInertElementSkip(child.tag, state.context);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (child.type === "element" || require_xml_node.isTextLikeNode(child)) strayNodes.push(child);
|
|
314
482
|
}
|
|
483
|
+
flushStray();
|
|
484
|
+
return blocks;
|
|
485
|
+
}
|
|
486
|
+
function flushDefinitionListStrayContent(nodes, state) {
|
|
487
|
+
if (nodes.length === 0) return [];
|
|
488
|
+
const blocks = readContainerChildren(nodes, state);
|
|
489
|
+
if (blocks.length === 0) return [];
|
|
490
|
+
state.context.sink({
|
|
491
|
+
code: require_diagnostics.EpubDiagnosticCodes.DEFINITION_LIST_CONTENT_OUTSIDE_ENTRY,
|
|
492
|
+
severity: "info",
|
|
493
|
+
message: "content sits directly inside a <dl> (or one of its <div> wrappers) outside any dt/dd (not valid HTML5); recovered as ordinary content -- a non-conformant wrapper's own dt/dd children, if any, lose their distinct term/definition treatment and degrade to plain concatenated text",
|
|
494
|
+
href: state.context.sourceHref
|
|
495
|
+
});
|
|
315
496
|
return blocks;
|
|
316
497
|
}
|
|
317
498
|
function readTable(element, state) {
|
|
499
|
+
const captionElements = element.children.filter((c) => c.type === "element" && c.tag === "caption");
|
|
318
500
|
const rows = [];
|
|
319
501
|
let columnCount = 0;
|
|
502
|
+
const strayNodes = [];
|
|
320
503
|
for (const section of element.children) {
|
|
321
|
-
if (section.type
|
|
322
|
-
|
|
504
|
+
if (section.type === "element" && section.tag === "caption") continue;
|
|
505
|
+
if (section.type !== "element") {
|
|
506
|
+
if (require_xml_node.isTextLikeNode(section)) strayNodes.push(section);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (section.tag === "colgroup") {
|
|
510
|
+
collectColgroupStrayContent(section, strayNodes, state.context);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (require_xhtml_context.isInertElement(section.tag)) {
|
|
514
|
+
require_xhtml_context.reportInertElementSkip(section.tag, state.context);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const rowContainers = section.tag === "tr" ? [section] : section.tag === "thead" || section.tag === "tbody" || section.tag === "tfoot" ? collectRowGroupRows(section, strayNodes, state.context) : void 0;
|
|
518
|
+
if (rowContainers === void 0) {
|
|
519
|
+
strayNodes.push(section);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
323
522
|
for (const tr of rowContainers) {
|
|
324
523
|
const cells = [];
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
...colSpan !== void 0 ? { colSpan } : {},
|
|
337
|
-
...rowSpan !== void 0 ? { rowSpan } : {}
|
|
524
|
+
let strayCellNodes = [];
|
|
525
|
+
const flushStrayCell = () => {
|
|
526
|
+
if (strayCellNodes.length === 0) return;
|
|
527
|
+
const recovered = readContainerChildren(strayCellNodes, state);
|
|
528
|
+
strayCellNodes = [];
|
|
529
|
+
if (recovered.length === 0) return;
|
|
530
|
+
state.context.sink({
|
|
531
|
+
code: require_diagnostics.EpubDiagnosticCodes.TABLE_ROW_CONTENT_OUTSIDE_CELL,
|
|
532
|
+
severity: "info",
|
|
533
|
+
message: "content sits directly inside a <tr> rather than inside a <td>/<th> (not valid HTML5); recovered as its own cell in the row's own column sequence",
|
|
534
|
+
href: state.context.sourceHref
|
|
338
535
|
});
|
|
536
|
+
cells.push({ blocks: recovered });
|
|
537
|
+
};
|
|
538
|
+
for (const cellNode of tr.children) {
|
|
539
|
+
if (cellNode.type === "element" && (cellNode.tag === "td" || cellNode.tag === "th")) {
|
|
540
|
+
flushStrayCell();
|
|
541
|
+
const cellStyle = cellNode.tag === "th" ? { bold: true } : {};
|
|
542
|
+
const inline = require_xhtml_inline.buildInlineRuns(cellNode.children, cellStyle, state.context);
|
|
543
|
+
const paragraph = {
|
|
544
|
+
kind: "paragraph",
|
|
545
|
+
runs: inline.runs,
|
|
546
|
+
...constructsField(inline)
|
|
547
|
+
};
|
|
548
|
+
const colSpan = positiveIntAttr(cellNode, "colspan");
|
|
549
|
+
const rowSpan = positiveIntAttr(cellNode, "rowspan");
|
|
550
|
+
cells.push({
|
|
551
|
+
blocks: [paragraph],
|
|
552
|
+
...colSpan !== void 0 ? { colSpan } : {},
|
|
553
|
+
...rowSpan !== void 0 ? { rowSpan } : {}
|
|
554
|
+
});
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (cellNode.type === "element" && require_xhtml_context.isInertElement(cellNode.tag)) {
|
|
558
|
+
require_xhtml_context.reportInertElementSkip(cellNode.tag, state.context);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (cellNode.type === "element" || require_xml_node.isTextLikeNode(cellNode)) strayCellNodes.push(cellNode);
|
|
339
562
|
}
|
|
563
|
+
flushStrayCell();
|
|
340
564
|
columnCount = Math.max(columnCount, cells.length);
|
|
341
565
|
rows.push({ cells });
|
|
342
566
|
}
|
|
343
567
|
}
|
|
568
|
+
const strayBlocks = strayNodes.length === 0 ? [] : readContainerChildren(strayNodes, state);
|
|
569
|
+
if (strayBlocks.length > 0) state.context.sink({
|
|
570
|
+
code: require_diagnostics.EpubDiagnosticCodes.TABLE_CONTENT_UNRECOGNIZED,
|
|
571
|
+
severity: "info",
|
|
572
|
+
message: "content sits directly inside a <table> (or one of its <thead>/<tbody>/<tfoot> row groups) outside any row, caption, or colgroup, or inside a <colgroup> itself (not valid HTML5); recovered as ordinary content immediately before the table",
|
|
573
|
+
href: state.context.sourceHref
|
|
574
|
+
});
|
|
344
575
|
const width = columnCount > 0 ? state.contentWidthPt / columnCount : state.contentWidthPt;
|
|
345
|
-
|
|
576
|
+
const table = {
|
|
346
577
|
kind: "table",
|
|
347
578
|
rows,
|
|
348
579
|
columnWidthsPt: new Array(Math.max(columnCount, 1)).fill(width)
|
|
349
580
|
};
|
|
581
|
+
const captionBlocks = captionElements.flatMap((captionElement, index) => readTableCaption(captionElement, index > 0, state));
|
|
582
|
+
return [
|
|
583
|
+
...strayBlocks,
|
|
584
|
+
...captionBlocks,
|
|
585
|
+
table
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
function collectRowGroupRows(section, strayNodes, context) {
|
|
589
|
+
const trs = [];
|
|
590
|
+
for (const child of section.children) {
|
|
591
|
+
if (child.type === "element" && child.tag === "tr") {
|
|
592
|
+
trs.push(child);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (child.type === "element" && require_xhtml_context.isInertElement(child.tag)) {
|
|
596
|
+
require_xhtml_context.reportInertElementSkip(child.tag, context);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (child.type === "element" || require_xml_node.isTextLikeNode(child)) strayNodes.push(child);
|
|
600
|
+
}
|
|
601
|
+
return trs;
|
|
602
|
+
}
|
|
603
|
+
function collectColgroupStrayContent(section, strayNodes, context) {
|
|
604
|
+
for (const child of section.children) {
|
|
605
|
+
if (child.type === "element" && child.tag === "col") continue;
|
|
606
|
+
if (child.type === "element" && require_xhtml_context.isInertElement(child.tag)) {
|
|
607
|
+
require_xhtml_context.reportInertElementSkip(child.tag, context);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (child.type === "element" || require_xml_node.isTextLikeNode(child)) strayNodes.push(child);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function readTableCaption(captionElement, isDuplicate, state) {
|
|
614
|
+
const captionInline = require_xhtml_inline.buildInlineRuns(captionElement.children, {}, state.context);
|
|
615
|
+
if (captionInline.runs.every((run) => run.text.trim().length === 0) && captionInline.constructs.length === 0) return [];
|
|
616
|
+
if (isDuplicate) state.context.sink({
|
|
617
|
+
code: require_diagnostics.EpubDiagnosticCodes.TABLE_DUPLICATE_CAPTION,
|
|
618
|
+
severity: "info",
|
|
619
|
+
message: "<table> carries more than one <caption> (HTML5 permits at most one); every caption beyond the first is still read as its own ordinary paragraph immediately before the table, rather than being silently discarded",
|
|
620
|
+
href: state.context.sourceHref
|
|
621
|
+
});
|
|
622
|
+
state.context.sink({
|
|
623
|
+
code: require_diagnostics.EpubDiagnosticCodes.TABLE_CAPTION_UNSUPPORTED,
|
|
624
|
+
severity: "info",
|
|
625
|
+
message: "<caption> has no document-schema.js table-caption field to carry its own distinct tag; read as an ordinary paragraph immediately before the table",
|
|
626
|
+
href: state.context.sourceHref
|
|
627
|
+
});
|
|
628
|
+
return [decorateParagraph({
|
|
629
|
+
kind: "paragraph",
|
|
630
|
+
runs: captionInline.runs,
|
|
631
|
+
...constructsField(captionInline)
|
|
632
|
+
}, state)];
|
|
350
633
|
}
|
|
351
634
|
function positiveIntAttr(element, name) {
|
|
352
635
|
const raw = require_xml_query.attrValue(element, name);
|