epub-codec 1.1.0 → 1.2.1
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/{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 +6 -10
- package/dist/xml/entities.d.cts +3 -1
- package/dist/xml/entities.d.ts +3 -1
- package/dist/xml/entities.js +6 -11
- 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 +3 -2
package/dist/xhtml/read.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { EpubDiagnosticCodes } from "../diagnostics.js";
|
|
2
|
-
import {
|
|
2
|
+
import { decodeEntities, decodeTextLikeNode } from "../xml/entities.js";
|
|
3
|
+
import { isTextLikeNode } from "../xml/node.js";
|
|
4
|
+
import { attrValue, findChildElement, rootElement } from "../xml/query.js";
|
|
3
5
|
import { parseXml } from "../xml/parse.js";
|
|
4
|
-
import { decodeEntities } from "../xml/entities.js";
|
|
5
6
|
import { POINTS_PER_PIXEL, detectImageFormat, readImageDimensions } from "../image/dimensions.js";
|
|
6
7
|
import { bytesToBase64 } from "../util/base64.js";
|
|
7
8
|
import { buildXml } from "../xml/build.js";
|
|
9
|
+
import { isInertElement, reportInertElementSkip } from "./context.js";
|
|
8
10
|
import { isFootnoteAside, isFootnoteReferenceAnchor } from "./footnote.js";
|
|
9
11
|
import { HORIZONTAL_RULE_STYLE_ID, MONOSPACE_FONT_FAMILY } from "./style-constants.js";
|
|
10
12
|
import { buildInlineRuns } from "./inline.js";
|
|
@@ -61,18 +63,32 @@ function decorateParagraph(paragraph, state) {
|
|
|
61
63
|
}
|
|
62
64
|
return decorated;
|
|
63
65
|
}
|
|
66
|
+
function constructsField(inline) {
|
|
67
|
+
return inline.constructs.length > 0 ? { constructs: inline.constructs } : {};
|
|
68
|
+
}
|
|
64
69
|
function buildIdElementMap(nodes) {
|
|
65
70
|
const map = /* @__PURE__ */ new Map();
|
|
66
71
|
const stack = [...nodes];
|
|
67
72
|
while (stack.length > 0) {
|
|
68
73
|
const node = stack.pop();
|
|
69
|
-
if (node?.type !== "element") continue;
|
|
74
|
+
if (node?.type !== "element" || isInertElement(node.tag)) continue;
|
|
70
75
|
const id = attrValue(node, "id");
|
|
71
76
|
if (id !== void 0 && !map.has(id)) map.set(id, node);
|
|
72
77
|
stack.push(...node.children);
|
|
73
78
|
}
|
|
74
79
|
return map;
|
|
75
80
|
}
|
|
81
|
+
function elementsWithTagSkippingInert(nodes, tag) {
|
|
82
|
+
const out = [];
|
|
83
|
+
const stack = [...nodes];
|
|
84
|
+
while (stack.length > 0) {
|
|
85
|
+
const node = stack.pop();
|
|
86
|
+
if (node?.type !== "element" || isInertElement(node.tag)) continue;
|
|
87
|
+
if (node.tag === tag) out.push(node);
|
|
88
|
+
stack.push(...node.children);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
76
92
|
function readStyleResidue(html, context) {
|
|
77
93
|
const head = findChildElement(html.children, "head");
|
|
78
94
|
if (head === void 0) return;
|
|
@@ -99,7 +115,7 @@ function readXhtmlBody(xml, options) {
|
|
|
99
115
|
};
|
|
100
116
|
const idElements = buildIdElementMap(body.children);
|
|
101
117
|
const footnoteTargetIds = /* @__PURE__ */ new Set();
|
|
102
|
-
for (const anchor of
|
|
118
|
+
for (const anchor of elementsWithTagSkippingInert(body.children, "a")) {
|
|
103
119
|
const name = isFootnoteReferenceAnchor(anchor, idElements);
|
|
104
120
|
if (name !== void 0) footnoteTargetIds.add(name);
|
|
105
121
|
}
|
|
@@ -155,11 +171,11 @@ function readContainerChildren(nodes, state) {
|
|
|
155
171
|
if (segment.length === 0) return;
|
|
156
172
|
const inline = buildInlineRuns(segment, {}, state.context);
|
|
157
173
|
segment = [];
|
|
158
|
-
if (inline.runs.every((run) => run.text.trim().length === 0)) return;
|
|
174
|
+
if (inline.runs.every((run) => run.text.trim().length === 0) && inline.constructs.length === 0) return;
|
|
159
175
|
const paragraph = {
|
|
160
176
|
kind: "paragraph",
|
|
161
177
|
runs: inline.runs,
|
|
162
|
-
...inline
|
|
178
|
+
...constructsField(inline)
|
|
163
179
|
};
|
|
164
180
|
blocks.push(decorateParagraph(paragraph, state));
|
|
165
181
|
};
|
|
@@ -169,7 +185,7 @@ function readContainerChildren(nodes, state) {
|
|
|
169
185
|
blocks.push(...readBlockElement(node, state));
|
|
170
186
|
continue;
|
|
171
187
|
}
|
|
172
|
-
if (node.type === "element" || node
|
|
188
|
+
if (node.type === "element" || isTextLikeNode(node)) segment.push(node);
|
|
173
189
|
}
|
|
174
190
|
flush();
|
|
175
191
|
return blocks;
|
|
@@ -204,7 +220,7 @@ function readBlockElementInner(element, state) {
|
|
|
204
220
|
kind: "paragraph",
|
|
205
221
|
headingLevel,
|
|
206
222
|
runs: inline.runs,
|
|
207
|
-
...inline
|
|
223
|
+
...constructsField(inline)
|
|
208
224
|
}, state)];
|
|
209
225
|
}
|
|
210
226
|
switch (element.tag) {
|
|
@@ -219,12 +235,16 @@ function readBlockElementInner(element, state) {
|
|
|
219
235
|
case "ul":
|
|
220
236
|
case "ol": return readList(element, state);
|
|
221
237
|
case "dl": return readDefinitionList(element, state);
|
|
222
|
-
case "table": return
|
|
238
|
+
case "table": return readTable(element, state);
|
|
223
239
|
case "figure": return readContainerChildren(element.children, state);
|
|
224
|
-
case "figcaption":
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
240
|
+
case "figcaption": {
|
|
241
|
+
const inline = buildInlineRuns(element.children, {}, state.context);
|
|
242
|
+
return [decorateParagraph({
|
|
243
|
+
kind: "paragraph",
|
|
244
|
+
runs: inline.runs,
|
|
245
|
+
...constructsField(inline)
|
|
246
|
+
}, state)];
|
|
247
|
+
}
|
|
228
248
|
case "img": {
|
|
229
249
|
const block = readImage(element, state);
|
|
230
250
|
return block === void 0 ? [] : [block];
|
|
@@ -236,23 +256,123 @@ function readBlockElementInner(element, state) {
|
|
|
236
256
|
function readPre(element, state) {
|
|
237
257
|
const languageSource = findChildElement(element.children, "code") ?? element;
|
|
238
258
|
const codeLanguage = languageFromClass(attrValue(languageSource, "class"));
|
|
239
|
-
const
|
|
259
|
+
const inline = containsFootnoteReference(element.children, state.context) ? readPreRuns(element.children, state.context) : {
|
|
260
|
+
runs: readPreFlatRuns(element.children, state.context),
|
|
261
|
+
constructs: []
|
|
262
|
+
};
|
|
240
263
|
return decorateParagraph({
|
|
241
264
|
kind: "paragraph",
|
|
242
|
-
runs:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
...codeLanguage !== void 0 ? { codeLanguage } : {}
|
|
265
|
+
runs: inline.runs,
|
|
266
|
+
preformatted: true,
|
|
267
|
+
...codeLanguage !== void 0 ? { codeLanguage } : {},
|
|
268
|
+
...constructsField(inline)
|
|
247
269
|
}, state);
|
|
248
270
|
}
|
|
271
|
+
function readPreFlatRuns(nodes, context) {
|
|
272
|
+
const text = readPreText(nodes, context);
|
|
273
|
+
return text.length > 0 ? [{
|
|
274
|
+
text,
|
|
275
|
+
fontFamily: MONOSPACE_FONT_FAMILY
|
|
276
|
+
}] : [];
|
|
277
|
+
}
|
|
278
|
+
function containsFootnoteReference(nodes, context) {
|
|
279
|
+
for (const node of nodes) {
|
|
280
|
+
if (node.type !== "element" || isInertElement(node.tag)) continue;
|
|
281
|
+
if (node.tag === "a" && isFootnoteReferenceAnchor(node, context.idElements) !== void 0) return true;
|
|
282
|
+
if (containsFootnoteReference(node.children, context)) return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
function readPreRuns(nodes, context) {
|
|
287
|
+
const runs = [];
|
|
288
|
+
const constructs = [];
|
|
289
|
+
let buffer = "";
|
|
290
|
+
const flush = () => {
|
|
291
|
+
if (buffer.length === 0) return;
|
|
292
|
+
runs.push({
|
|
293
|
+
text: buffer,
|
|
294
|
+
fontFamily: MONOSPACE_FONT_FAMILY
|
|
295
|
+
});
|
|
296
|
+
buffer = "";
|
|
297
|
+
};
|
|
298
|
+
for (const node of nodes) {
|
|
299
|
+
if (isTextLikeNode(node)) {
|
|
300
|
+
buffer += decodeTextLikeNode(node);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (node.type !== "element") continue;
|
|
304
|
+
if (isInertElement(node.tag)) {
|
|
305
|
+
reportInertElementSkip(node.tag, context);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (node.tag === "img") {
|
|
309
|
+
buffer += decodeEntities(readPreImageFallbackText(node, context));
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (node.tag === "br") {
|
|
313
|
+
buffer += "\n";
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const footnoteName = node.tag === "a" ? isFootnoteReferenceAnchor(node, context.idElements) : void 0;
|
|
317
|
+
if (footnoteName === void 0 && !containsFootnoteReference([node], context)) {
|
|
318
|
+
buffer += readPreText(node.children, context);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
flush();
|
|
322
|
+
const startRun = runs.length;
|
|
323
|
+
const nested = readPreRuns(node.children, context);
|
|
324
|
+
runs.push(...nested.runs);
|
|
325
|
+
for (const construct of nested.constructs) constructs.push({
|
|
326
|
+
...construct,
|
|
327
|
+
startRun: construct.startRun + startRun,
|
|
328
|
+
endRun: construct.endRun + startRun
|
|
329
|
+
});
|
|
330
|
+
if (footnoteName !== void 0) constructs.push({
|
|
331
|
+
descriptor: {
|
|
332
|
+
kind: "anchor",
|
|
333
|
+
anchorType: "footnote",
|
|
334
|
+
name: footnoteName
|
|
335
|
+
},
|
|
336
|
+
startRun,
|
|
337
|
+
endRun: runs.length
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
flush();
|
|
341
|
+
return {
|
|
342
|
+
runs,
|
|
343
|
+
constructs
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function readPreText(nodes, context) {
|
|
347
|
+
let out = "";
|
|
348
|
+
for (const node of nodes) if (isTextLikeNode(node)) out += decodeTextLikeNode(node);
|
|
349
|
+
else if (node.type === "element" && node.tag === "br") out += "\n";
|
|
350
|
+
else if (node.type === "element" && node.tag === "img") out += decodeEntities(readPreImageFallbackText(node, context));
|
|
351
|
+
else if (node.type === "element" && isInertElement(node.tag)) {
|
|
352
|
+
reportInertElementSkip(node.tag, context);
|
|
353
|
+
continue;
|
|
354
|
+
} else if (node.type === "element") out += readPreText(node.children, context);
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function readPreImageFallbackText(element, context) {
|
|
358
|
+
const src = attrValue(element, "src");
|
|
359
|
+
const alt = attrValue(element, "alt");
|
|
360
|
+
const label = src === void 0 ? "<img>" : `<img src="${src}">`;
|
|
361
|
+
context.sink({
|
|
362
|
+
code: EpubDiagnosticCodes.IMAGE_PRE_UNSUPPORTED,
|
|
363
|
+
severity: "warning",
|
|
364
|
+
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`,
|
|
365
|
+
href: context.sourceHref
|
|
366
|
+
});
|
|
367
|
+
return alt ?? "";
|
|
368
|
+
}
|
|
249
369
|
function languageFromClass(className) {
|
|
250
370
|
if (className === void 0) return;
|
|
251
371
|
return /(?:^|\s)language-(\S+)/u.exec(className)?.[1];
|
|
252
372
|
}
|
|
253
373
|
function containsHeading(nodes) {
|
|
254
374
|
for (const node of nodes) {
|
|
255
|
-
if (node.type !== "element") continue;
|
|
375
|
+
if (node.type !== "element" || isInertElement(node.tag)) continue;
|
|
256
376
|
if (headingLevelOf(node.tag) !== void 0) return true;
|
|
257
377
|
if (containsHeading(node.children)) return true;
|
|
258
378
|
}
|
|
@@ -278,74 +398,237 @@ function readList(element, state) {
|
|
|
278
398
|
});
|
|
279
399
|
const level = state.list === void 0 ? 0 : state.list.level + 1;
|
|
280
400
|
const blocks = [];
|
|
401
|
+
let previousItem;
|
|
402
|
+
let strayNodes = [];
|
|
281
403
|
for (const child of element.children) {
|
|
282
|
-
if (child.type
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
404
|
+
if (child.type === "element" && child.tag === "li") {
|
|
405
|
+
blocks.push(...flushListStrayContent(strayNodes, previousItem, element.tag, state));
|
|
406
|
+
strayNodes = [];
|
|
407
|
+
previousItem = {
|
|
408
|
+
numId,
|
|
409
|
+
level,
|
|
410
|
+
itemId: state.minter.mintItemId()
|
|
411
|
+
};
|
|
412
|
+
blocks.push(...readContainerChildren(child.children, withListItem(state, previousItem)));
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (child.type === "element" && isInertElement(child.tag)) {
|
|
416
|
+
reportInertElementSkip(child.tag, state.context);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (child.type === "element" || isTextLikeNode(child)) strayNodes.push(child);
|
|
289
420
|
}
|
|
421
|
+
blocks.push(...flushListStrayContent(strayNodes, previousItem, element.tag, state));
|
|
422
|
+
return blocks;
|
|
423
|
+
}
|
|
424
|
+
function flushListStrayContent(nodes, previousItem, tag, state) {
|
|
425
|
+
if (nodes.length === 0) return [];
|
|
426
|
+
const blocks = readContainerChildren(nodes, previousItem === void 0 ? state : withListItem(state, previousItem));
|
|
427
|
+
if (blocks.length === 0) return [];
|
|
428
|
+
state.context.sink({
|
|
429
|
+
code: EpubDiagnosticCodes.LIST_CONTENT_OUTSIDE_ITEM,
|
|
430
|
+
severity: "info",
|
|
431
|
+
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`,
|
|
432
|
+
href: state.context.sourceHref
|
|
433
|
+
});
|
|
290
434
|
return blocks;
|
|
291
435
|
}
|
|
292
436
|
function startAttr(element) {
|
|
293
437
|
return positiveIntAttr(element, "start");
|
|
294
438
|
}
|
|
295
439
|
function readDefinitionList(element, state) {
|
|
440
|
+
return readDefinitionListEntries(element.children, state);
|
|
441
|
+
}
|
|
442
|
+
function readDefinitionListEntries(nodes, state) {
|
|
296
443
|
const blocks = [];
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
444
|
+
let strayNodes = [];
|
|
445
|
+
const flushStray = () => {
|
|
446
|
+
blocks.push(...flushDefinitionListStrayContent(strayNodes, state));
|
|
447
|
+
strayNodes = [];
|
|
448
|
+
};
|
|
449
|
+
for (const child of nodes) {
|
|
450
|
+
if (child.type === "element" && child.tag === "dt") {
|
|
451
|
+
flushStray();
|
|
300
452
|
const inline = buildInlineRuns(child.children, {}, state.context);
|
|
301
453
|
blocks.push(decorateParagraph({
|
|
302
454
|
kind: "paragraph",
|
|
303
|
-
runs: inline.runs
|
|
455
|
+
runs: inline.runs,
|
|
456
|
+
...constructsField(inline)
|
|
304
457
|
}, state));
|
|
305
|
-
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (child.type === "element" && child.tag === "dd") {
|
|
461
|
+
flushStray();
|
|
306
462
|
const inline = buildInlineRuns(child.children, {}, state.context);
|
|
307
463
|
blocks.push(decorateParagraph({
|
|
308
464
|
kind: "paragraph",
|
|
309
465
|
runs: inline.runs,
|
|
466
|
+
...constructsField(inline),
|
|
310
467
|
indentLeftPt: 36 + state.quoteDepth * 36
|
|
311
468
|
}, state));
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (child.type === "element" && child.tag === "div") {
|
|
472
|
+
flushStray();
|
|
473
|
+
blocks.push(...readDefinitionListEntries(child.children, state));
|
|
474
|
+
continue;
|
|
312
475
|
}
|
|
476
|
+
if (child.type === "element" && isInertElement(child.tag)) {
|
|
477
|
+
reportInertElementSkip(child.tag, state.context);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (child.type === "element" || isTextLikeNode(child)) strayNodes.push(child);
|
|
313
481
|
}
|
|
482
|
+
flushStray();
|
|
483
|
+
return blocks;
|
|
484
|
+
}
|
|
485
|
+
function flushDefinitionListStrayContent(nodes, state) {
|
|
486
|
+
if (nodes.length === 0) return [];
|
|
487
|
+
const blocks = readContainerChildren(nodes, state);
|
|
488
|
+
if (blocks.length === 0) return [];
|
|
489
|
+
state.context.sink({
|
|
490
|
+
code: EpubDiagnosticCodes.DEFINITION_LIST_CONTENT_OUTSIDE_ENTRY,
|
|
491
|
+
severity: "info",
|
|
492
|
+
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",
|
|
493
|
+
href: state.context.sourceHref
|
|
494
|
+
});
|
|
314
495
|
return blocks;
|
|
315
496
|
}
|
|
316
497
|
function readTable(element, state) {
|
|
498
|
+
const captionElements = element.children.filter((c) => c.type === "element" && c.tag === "caption");
|
|
317
499
|
const rows = [];
|
|
318
500
|
let columnCount = 0;
|
|
501
|
+
const strayNodes = [];
|
|
319
502
|
for (const section of element.children) {
|
|
320
|
-
if (section.type
|
|
321
|
-
|
|
503
|
+
if (section.type === "element" && section.tag === "caption") continue;
|
|
504
|
+
if (section.type !== "element") {
|
|
505
|
+
if (isTextLikeNode(section)) strayNodes.push(section);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (section.tag === "colgroup") {
|
|
509
|
+
collectColgroupStrayContent(section, strayNodes, state.context);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (isInertElement(section.tag)) {
|
|
513
|
+
reportInertElementSkip(section.tag, state.context);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const rowContainers = section.tag === "tr" ? [section] : section.tag === "thead" || section.tag === "tbody" || section.tag === "tfoot" ? collectRowGroupRows(section, strayNodes, state.context) : void 0;
|
|
517
|
+
if (rowContainers === void 0) {
|
|
518
|
+
strayNodes.push(section);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
322
521
|
for (const tr of rowContainers) {
|
|
323
522
|
const cells = [];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
...colSpan !== void 0 ? { colSpan } : {},
|
|
336
|
-
...rowSpan !== void 0 ? { rowSpan } : {}
|
|
523
|
+
let strayCellNodes = [];
|
|
524
|
+
const flushStrayCell = () => {
|
|
525
|
+
if (strayCellNodes.length === 0) return;
|
|
526
|
+
const recovered = readContainerChildren(strayCellNodes, state);
|
|
527
|
+
strayCellNodes = [];
|
|
528
|
+
if (recovered.length === 0) return;
|
|
529
|
+
state.context.sink({
|
|
530
|
+
code: EpubDiagnosticCodes.TABLE_ROW_CONTENT_OUTSIDE_CELL,
|
|
531
|
+
severity: "info",
|
|
532
|
+
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",
|
|
533
|
+
href: state.context.sourceHref
|
|
337
534
|
});
|
|
535
|
+
cells.push({ blocks: recovered });
|
|
536
|
+
};
|
|
537
|
+
for (const cellNode of tr.children) {
|
|
538
|
+
if (cellNode.type === "element" && (cellNode.tag === "td" || cellNode.tag === "th")) {
|
|
539
|
+
flushStrayCell();
|
|
540
|
+
const cellStyle = cellNode.tag === "th" ? { bold: true } : {};
|
|
541
|
+
const inline = buildInlineRuns(cellNode.children, cellStyle, state.context);
|
|
542
|
+
const paragraph = {
|
|
543
|
+
kind: "paragraph",
|
|
544
|
+
runs: inline.runs,
|
|
545
|
+
...constructsField(inline)
|
|
546
|
+
};
|
|
547
|
+
const colSpan = positiveIntAttr(cellNode, "colspan");
|
|
548
|
+
const rowSpan = positiveIntAttr(cellNode, "rowspan");
|
|
549
|
+
cells.push({
|
|
550
|
+
blocks: [paragraph],
|
|
551
|
+
...colSpan !== void 0 ? { colSpan } : {},
|
|
552
|
+
...rowSpan !== void 0 ? { rowSpan } : {}
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (cellNode.type === "element" && isInertElement(cellNode.tag)) {
|
|
557
|
+
reportInertElementSkip(cellNode.tag, state.context);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (cellNode.type === "element" || isTextLikeNode(cellNode)) strayCellNodes.push(cellNode);
|
|
338
561
|
}
|
|
562
|
+
flushStrayCell();
|
|
339
563
|
columnCount = Math.max(columnCount, cells.length);
|
|
340
564
|
rows.push({ cells });
|
|
341
565
|
}
|
|
342
566
|
}
|
|
567
|
+
const strayBlocks = strayNodes.length === 0 ? [] : readContainerChildren(strayNodes, state);
|
|
568
|
+
if (strayBlocks.length > 0) state.context.sink({
|
|
569
|
+
code: EpubDiagnosticCodes.TABLE_CONTENT_UNRECOGNIZED,
|
|
570
|
+
severity: "info",
|
|
571
|
+
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",
|
|
572
|
+
href: state.context.sourceHref
|
|
573
|
+
});
|
|
343
574
|
const width = columnCount > 0 ? state.contentWidthPt / columnCount : state.contentWidthPt;
|
|
344
|
-
|
|
575
|
+
const table = {
|
|
345
576
|
kind: "table",
|
|
346
577
|
rows,
|
|
347
578
|
columnWidthsPt: new Array(Math.max(columnCount, 1)).fill(width)
|
|
348
579
|
};
|
|
580
|
+
const captionBlocks = captionElements.flatMap((captionElement, index) => readTableCaption(captionElement, index > 0, state));
|
|
581
|
+
return [
|
|
582
|
+
...strayBlocks,
|
|
583
|
+
...captionBlocks,
|
|
584
|
+
table
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
function collectRowGroupRows(section, strayNodes, context) {
|
|
588
|
+
const trs = [];
|
|
589
|
+
for (const child of section.children) {
|
|
590
|
+
if (child.type === "element" && child.tag === "tr") {
|
|
591
|
+
trs.push(child);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (child.type === "element" && isInertElement(child.tag)) {
|
|
595
|
+
reportInertElementSkip(child.tag, context);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (child.type === "element" || isTextLikeNode(child)) strayNodes.push(child);
|
|
599
|
+
}
|
|
600
|
+
return trs;
|
|
601
|
+
}
|
|
602
|
+
function collectColgroupStrayContent(section, strayNodes, context) {
|
|
603
|
+
for (const child of section.children) {
|
|
604
|
+
if (child.type === "element" && child.tag === "col") continue;
|
|
605
|
+
if (child.type === "element" && isInertElement(child.tag)) {
|
|
606
|
+
reportInertElementSkip(child.tag, context);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (child.type === "element" || isTextLikeNode(child)) strayNodes.push(child);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function readTableCaption(captionElement, isDuplicate, state) {
|
|
613
|
+
const captionInline = buildInlineRuns(captionElement.children, {}, state.context);
|
|
614
|
+
if (captionInline.runs.every((run) => run.text.trim().length === 0) && captionInline.constructs.length === 0) return [];
|
|
615
|
+
if (isDuplicate) state.context.sink({
|
|
616
|
+
code: EpubDiagnosticCodes.TABLE_DUPLICATE_CAPTION,
|
|
617
|
+
severity: "info",
|
|
618
|
+
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",
|
|
619
|
+
href: state.context.sourceHref
|
|
620
|
+
});
|
|
621
|
+
state.context.sink({
|
|
622
|
+
code: EpubDiagnosticCodes.TABLE_CAPTION_UNSUPPORTED,
|
|
623
|
+
severity: "info",
|
|
624
|
+
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",
|
|
625
|
+
href: state.context.sourceHref
|
|
626
|
+
});
|
|
627
|
+
return [decorateParagraph({
|
|
628
|
+
kind: "paragraph",
|
|
629
|
+
runs: captionInline.runs,
|
|
630
|
+
...constructsField(captionInline)
|
|
631
|
+
}, state)];
|
|
349
632
|
}
|
|
350
633
|
function positiveIntAttr(element, name) {
|
|
351
634
|
const raw = attrValue(element, name);
|
package/dist/xhtml/write.cjs
CHANGED
|
@@ -60,24 +60,31 @@ function writeSectionChildren(children, context) {
|
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
62
|
function writeSectionChild(child, context) {
|
|
63
|
-
if ((0, document_schema_js.isHeadingGroupNode)(child)) return [writeHeading(child), ...writeSectionChildren(child.children, context)];
|
|
63
|
+
if ((0, document_schema_js.isHeadingGroupNode)(child)) return [writeHeading(child, context), ...writeSectionChildren(child.children, context)];
|
|
64
64
|
if ((0, document_schema_js.isListGroupNode)(child)) return [writeList([child], context)];
|
|
65
65
|
if ((0, document_schema_js.isSectionConstructGroupNode)(child)) return writeSectionConstructGroup(child, context);
|
|
66
66
|
return writeLeafBlock(child, context);
|
|
67
67
|
}
|
|
68
|
-
function writeHeading(group) {
|
|
68
|
+
function writeHeading(group, context) {
|
|
69
69
|
const level = Math.min(6, Math.max(1, group.node.headingLevel));
|
|
70
|
-
return element(`h${String(level)}`, {}, writeRunsToNodes(group.node.runs, group.node.constructs));
|
|
70
|
+
return element(`h${String(level)}`, {}, writeRunsToNodes(group.node.runs, group.node.constructs, context));
|
|
71
71
|
}
|
|
72
72
|
function writeList(items, context) {
|
|
73
73
|
const numId = items[0]?.node.list.numId;
|
|
74
74
|
const info = numId === void 0 ? void 0 : require_xhtml_list_id.parseListNumId(numId);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
const tag = info?.type === "ordered" ? "ol" : "ul";
|
|
76
|
+
const attrs = info?.type === "ordered" && info.start !== void 0 ? { start: String(info.start) } : {};
|
|
77
|
+
const liNodes = [];
|
|
78
|
+
let index = 0;
|
|
79
|
+
while (index < items.length) {
|
|
80
|
+
const itemId = items[index]?.node.list.itemId;
|
|
81
|
+
let end = index + 1;
|
|
82
|
+
if (itemId !== void 0) while (end < items.length && items[end]?.node.list.itemId === itemId) end += 1;
|
|
83
|
+
const liChildren = items.slice(index, end).flatMap((item, position) => [...writeParagraphAsEmbeddedNodes(item.node, context, position > 0), ...writeSectionChildren(item.children, context)]);
|
|
84
|
+
liNodes.push(element("li", {}, liChildren));
|
|
85
|
+
index = end;
|
|
86
|
+
}
|
|
87
|
+
return element(tag, attrs, liNodes);
|
|
81
88
|
}
|
|
82
89
|
function writeSectionConstructGroup(group, context) {
|
|
83
90
|
const descriptor = group.node;
|
|
@@ -96,7 +103,7 @@ function writeSectionConstructGroup(group, context) {
|
|
|
96
103
|
}
|
|
97
104
|
function writeLeafBlock(block, context) {
|
|
98
105
|
switch (block.kind) {
|
|
99
|
-
case "paragraph": return [writeParagraph(block)];
|
|
106
|
+
case "paragraph": return [writeParagraph(block, context)];
|
|
100
107
|
case "table": return [writeTable(block, context)];
|
|
101
108
|
case "image": return [writeImage(block, context)];
|
|
102
109
|
case "pageBreak": return [];
|
|
@@ -111,38 +118,80 @@ function writeLeafBlock(block, context) {
|
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
function isPreBlockParagraph(paragraph) {
|
|
121
|
+
if (paragraph.preformatted === true) return true;
|
|
114
122
|
if (paragraph.codeLanguage !== void 0) return true;
|
|
115
123
|
return paragraph.runs.length === 1 && paragraph.runs[0]?.fontFamily === "Courier New" && paragraph.runs[0].text.includes("\n");
|
|
116
124
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
function isHorizontalRuleParagraph(paragraph) {
|
|
126
|
+
return paragraph.styleId === "HorizontalRule" && paragraph.runs.length === 0;
|
|
127
|
+
}
|
|
128
|
+
function writePreElement(paragraph, context) {
|
|
129
|
+
return element("pre", {}, [element("code", paragraph.codeLanguage === void 0 ? {} : { class: `language-${paragraph.codeLanguage}` }, writePreRunsToNodes(paragraph.runs, paragraph.constructs, context))]);
|
|
130
|
+
}
|
|
131
|
+
function writeParagraph(paragraph, context) {
|
|
132
|
+
if (isHorizontalRuleParagraph(paragraph)) return element("hr");
|
|
133
|
+
if (isPreBlockParagraph(paragraph)) return writePreElement(paragraph, context);
|
|
134
|
+
return element("p", {}, writeRunsToNodes(paragraph.runs, paragraph.constructs, context));
|
|
135
|
+
}
|
|
136
|
+
function writeParagraphAsEmbeddedNodes(paragraph, context, wrapOrdinaryInParagraph) {
|
|
137
|
+
if (isHorizontalRuleParagraph(paragraph)) return [element("hr")];
|
|
138
|
+
if (isPreBlockParagraph(paragraph)) return [writePreElement(paragraph, context)];
|
|
139
|
+
const runNodes = writeRunsToNodes(paragraph.runs, paragraph.constructs, context);
|
|
140
|
+
return wrapOrdinaryInParagraph ? [element("p", {}, runNodes)] : runNodes;
|
|
121
141
|
}
|
|
122
142
|
function isFootnoteExtent(construct) {
|
|
123
143
|
return construct.descriptor.kind === "anchor" && construct.descriptor.anchorType === "footnote";
|
|
124
144
|
}
|
|
125
|
-
function
|
|
145
|
+
function describeUnrepresentedFootnoteExtent(extent) {
|
|
146
|
+
if (extent.endRun === extent.startRun) return `a footnote reference ('${extent.descriptor.name}') marking the boundary before run ${extent.startRun} is never reached by the write walk -- either because it sits inside another footnote reference's own winning run range, or because its own run range falls outside this paragraph's actual runs; a point anchor wraps no run text of its own, so nothing besides this reference's own <a> link is missing from the output`;
|
|
147
|
+
return `a footnote reference ('${extent.descriptor.name}') is never reached by the write walk and cannot be represented as its own <a> element -- either because it overlaps another footnote reference's own winning run range, or because its own run range falls outside this paragraph's actual runs; wherever an overlap is the cause, the run text underneath it is not lost, since it is still written, wrapped by whichever extent's range actually claims it, or written unwrapped past it, but this reference's own anchor link is not emitted regardless of cause`;
|
|
148
|
+
}
|
|
149
|
+
function writeRunRangeNodes(runs, constructs, renderRun, renderExtentRange, context) {
|
|
126
150
|
const footnoteExtents = (constructs ?? []).filter(isFootnoteExtent);
|
|
151
|
+
const emittedExtents = /* @__PURE__ */ new Set();
|
|
127
152
|
const out = [];
|
|
128
153
|
let index = 0;
|
|
129
|
-
while (index
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
while (index <= runs.length) {
|
|
155
|
+
const extentsHere = footnoteExtents.filter((e) => e.startRun === index);
|
|
156
|
+
for (const point of extentsHere.filter((e) => e.endRun === e.startRun)) {
|
|
157
|
+
emittedExtents.add(point);
|
|
133
158
|
out.push(element("a", {
|
|
134
159
|
"epub:type": "noteref",
|
|
135
|
-
href: `#${
|
|
136
|
-
},
|
|
137
|
-
|
|
160
|
+
href: `#${point.descriptor.name}`
|
|
161
|
+
}, renderExtentRange([])));
|
|
162
|
+
}
|
|
163
|
+
const [primaryRange] = index < runs.length ? extentsHere.filter((e) => e.endRun > e.startRun) : [];
|
|
164
|
+
if (primaryRange !== void 0) {
|
|
165
|
+
emittedExtents.add(primaryRange);
|
|
166
|
+
const rangeRuns = runs.slice(primaryRange.startRun, primaryRange.endRun);
|
|
167
|
+
out.push(element("a", {
|
|
168
|
+
"epub:type": "noteref",
|
|
169
|
+
href: `#${primaryRange.descriptor.name}`
|
|
170
|
+
}, renderExtentRange(rangeRuns)));
|
|
171
|
+
index = primaryRange.endRun;
|
|
138
172
|
continue;
|
|
139
173
|
}
|
|
140
174
|
const run = runs[index];
|
|
141
|
-
if (run !== void 0) out.push(...
|
|
175
|
+
if (run !== void 0) out.push(...renderRun(run));
|
|
142
176
|
index += 1;
|
|
143
177
|
}
|
|
178
|
+
for (const extent of footnoteExtents) {
|
|
179
|
+
if (emittedExtents.has(extent)) continue;
|
|
180
|
+
context.sink({
|
|
181
|
+
code: require_diagnostics.EpubDiagnosticCodes.CONSTRUCT_UNREPRESENTED,
|
|
182
|
+
severity: "info",
|
|
183
|
+
message: describeUnrepresentedFootnoteExtent(extent),
|
|
184
|
+
href: context.sourceHref
|
|
185
|
+
});
|
|
186
|
+
}
|
|
144
187
|
return out;
|
|
145
188
|
}
|
|
189
|
+
function writeRunsToNodes(runs, constructs, context) {
|
|
190
|
+
return writeRunRangeNodes(runs, constructs, writeRunNodes, (rangeRuns) => rangeRuns.flatMap((run) => writeRunNodes(run)), context);
|
|
191
|
+
}
|
|
192
|
+
function writePreRunsToNodes(runs, constructs, context) {
|
|
193
|
+
return writeRunRangeNodes(runs, constructs, (run) => run.text.length > 0 ? [text(run.text)] : [], (rangeRuns) => [text(rangeRuns.map((run) => run.text).join(""))], context);
|
|
194
|
+
}
|
|
146
195
|
function writeRunNodes(run) {
|
|
147
196
|
const segments = run.text.split("\n");
|
|
148
197
|
let nodes = [];
|
package/dist/xhtml/write.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EpubDiagnosticSink } from "../diagnostics-
|
|
1
|
+
import { i as EpubDiagnosticSink } from "../diagnostics-C0CRdoda.cjs";
|
|
2
2
|
import { XmlElement } from "../xml/node.cjs";
|
|
3
3
|
import { ContentBlock, ContentImageBlock } from "document-schema.js";
|
|
4
4
|
//#region src/xhtml/write.d.ts
|
package/dist/xhtml/write.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EpubDiagnosticSink } from "../diagnostics-
|
|
1
|
+
import { i as EpubDiagnosticSink } from "../diagnostics-C0CRdoda.js";
|
|
2
2
|
import { XmlElement } from "../xml/node.js";
|
|
3
3
|
import { ContentBlock, ContentImageBlock } from "document-schema.js";
|
|
4
4
|
//#region src/xhtml/write.d.ts
|