@dogsbay/format-dita 0.2.0-beta.123 → 0.2.0-beta.125
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 +18 -1
- package/dist/blocks.js +110 -31
- package/dist/class-defaults-apply.d.ts +15 -0
- package/dist/class-defaults-apply.js +42 -0
- package/dist/class-defaults.d.ts +8 -0
- package/dist/class-defaults.js +625 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inline.d.ts +12 -0
- package/dist/inline.js +41 -29
- package/dist/lines.d.ts +5 -0
- package/dist/lines.js +24 -0
- package/dist/parse.js +34 -7
- package/dist/types.d.ts +22 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -18,3 +18,5 @@ export { ditaTopicToDogsbayMd, type DitaMarkdownResult } from "./markdown.js";
|
|
|
18
18
|
export { planConrefs, type ConrefCorpusFile, type ConrefPlan, type ConrefSnippet, } from "./conref.js";
|
|
19
19
|
export { DB_QUALIFIERS } from "./blocks.js";
|
|
20
20
|
export { checkWellFormed, type WellFormedProblem } from "./well-formed.js";
|
|
21
|
+
export { DITA_CLASS_DEFAULTS, DITA_MAP_CLASS_DEFAULTS } from "./class-defaults.js";
|
|
22
|
+
export { applyClassDefaults, classDefault } from "./class-defaults-apply.js";
|
package/dist/index.js
CHANGED
|
@@ -19,3 +19,5 @@ export { planConrefs, } from "./conref.js";
|
|
|
19
19
|
// The closed db- qualifier vocabulary (tones, tab variants, scoped themes)
|
|
20
20
|
export { DB_QUALIFIERS } from "./blocks.js";
|
|
21
21
|
export { checkWellFormed } from "./well-formed.js";
|
|
22
|
+
export { DITA_CLASS_DEFAULTS, DITA_MAP_CLASS_DEFAULTS } from "./class-defaults.js";
|
|
23
|
+
export { applyClassDefaults, classDefault } from "./class-defaults-apply.js";
|
package/dist/inline.d.ts
CHANGED
|
@@ -9,6 +9,18 @@ export interface InlineContext {
|
|
|
9
9
|
conref?: ConrefSiteContext;
|
|
10
10
|
/** Publishing mode: DITA-OT preprocessed input (absent for migration). */
|
|
11
11
|
publish?: PublishContext;
|
|
12
|
+
/** Every element carries @class (DTD defaults filled in): resolve unknown names by it, as publishing does. */
|
|
13
|
+
classes?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The element being converted, shared by every context derived from the
|
|
16
|
+
* topic's (a note takes its line from it). Set by convertBlock and, for each
|
|
17
|
+
* element, convertInline.
|
|
18
|
+
*/
|
|
19
|
+
position?: {
|
|
20
|
+
el?: Element;
|
|
21
|
+
};
|
|
22
|
+
/** ParseOptions.sourceLines: an element's 1-based source line. */
|
|
23
|
+
lineOf?: (el: Element) => number | undefined;
|
|
12
24
|
/** Publishing: trademarks already marked in this topic (DITA-OT marks only the first). */
|
|
13
25
|
trademarks?: Set<string>;
|
|
14
26
|
/** Publishing: the number label of each titled table and figure ("Table 1. "). */
|
package/dist/inline.js
CHANGED
|
@@ -83,7 +83,7 @@ const BOLD_LIKE = new Set(["b", "strong", "uicontrol", "wintitle", "menucascade"
|
|
|
83
83
|
*/
|
|
84
84
|
export const elementName = (el, ctx, known) => {
|
|
85
85
|
const raw = el.name.toLowerCase();
|
|
86
|
-
if (!ctx.publish || known(raw))
|
|
86
|
+
if (!(ctx.publish || ctx.classes) || known(raw))
|
|
87
87
|
return raw;
|
|
88
88
|
const tokens = (el.attribs?.class ?? "").trim().split(/\s+/).slice(1).reverse();
|
|
89
89
|
for (const token of tokens) {
|
|
@@ -216,6 +216,19 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
216
216
|
}
|
|
217
217
|
if (!isElement(node))
|
|
218
218
|
continue;
|
|
219
|
+
const outer = ctx.position?.el;
|
|
220
|
+
if (ctx.position)
|
|
221
|
+
ctx.position.el = node;
|
|
222
|
+
try {
|
|
223
|
+
convertInlineElement(node);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
if (ctx.position)
|
|
227
|
+
ctx.position.el = outer;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return mergeAdjacentText(out);
|
|
231
|
+
function convertInlineElement(node) {
|
|
219
232
|
const name = elementName(node, ctx, isKnownInline);
|
|
220
233
|
const children = node.children;
|
|
221
234
|
// Publishing: an inline element with an @id gets an anchor, as DITA-OT's HTML keeps the id
|
|
@@ -238,7 +251,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
238
251
|
const defining = ctx.conref.definingSnippet(node.attribs.id);
|
|
239
252
|
if (defining && inlineSafeSnippet(defining)) {
|
|
240
253
|
pushText(`{% include "${ctx.conref.includePath(defining)}" %}`);
|
|
241
|
-
|
|
254
|
+
return;
|
|
242
255
|
}
|
|
243
256
|
}
|
|
244
257
|
// Inline conrefs (<ph conref="…"/>) are the standard DITA reused-text
|
|
@@ -254,7 +267,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
254
267
|
// Local children are the stale preview copy in DITA conref
|
|
255
268
|
// semantics — the include replaces the whole element
|
|
256
269
|
pushText(`{% include "${ctx.conref.includePath(snippet)}" %}`);
|
|
257
|
-
|
|
270
|
+
return;
|
|
258
271
|
}
|
|
259
272
|
ctx.note(node.attribs.conref ? "@conref" : "@conkeyref", `<${name}> pulls content from "${ref}" — ${conrefLedgerDetail(node.attribs, snippet, ctx.conref !== undefined)}`);
|
|
260
273
|
}
|
|
@@ -275,7 +288,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
275
288
|
noteOnce(ctx, `db-badge ${t}`, `"${t}" is not a tone (${DB_QUALIFIERS.tones.join(", ")}) — default badge`);
|
|
276
289
|
}
|
|
277
290
|
out.push({ type: "badge", text, ...(variant ? { variant } : {}) });
|
|
278
|
-
|
|
291
|
+
return;
|
|
279
292
|
}
|
|
280
293
|
// A content-less keyref falls through to the key's text ({{ key }}): the badge look is what is lost
|
|
281
294
|
if (node.attribs?.keyref)
|
|
@@ -309,7 +322,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
309
322
|
: [];
|
|
310
323
|
// An unresolved key with no content renders nothing, as in DITA-OT's output
|
|
311
324
|
if (content.length === 0)
|
|
312
|
-
|
|
325
|
+
return;
|
|
313
326
|
const href = node.attribs?.href;
|
|
314
327
|
if (href) {
|
|
315
328
|
// An external link keeps its href as written, even to a .dita file
|
|
@@ -319,14 +332,14 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
319
332
|
else {
|
|
320
333
|
out.push(...content);
|
|
321
334
|
}
|
|
322
|
-
|
|
335
|
+
return;
|
|
323
336
|
}
|
|
324
337
|
if (!ctx.publish && node.attribs?.keyref && name !== "xref" && name !== "link" && name !== "image") {
|
|
325
338
|
const hasFallback = children.some((c) => isElement(c) || (isText(c) && c.data.trim().length > 0));
|
|
326
339
|
if (!hasFallback) {
|
|
327
340
|
noteOnce(ctx, "@keyref", "content-less keyref emitted as {{ key }} minja variable — resolves only for TEXT keys (keys.yaml entries with text:); href-only keys need manual attention");
|
|
328
341
|
pushText(`{{ ${node.attribs.keyref} }}`);
|
|
329
|
-
|
|
342
|
+
return;
|
|
330
343
|
}
|
|
331
344
|
// Fallback content present: note the dropped indirection, then fall
|
|
332
345
|
// through so the element converts normally (term stays italic, …)
|
|
@@ -334,12 +347,12 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
334
347
|
}
|
|
335
348
|
if (TRANSPARENT.has(name) && !isCodeLike(name, ctx)) {
|
|
336
349
|
out.push(...convertInline(children, ctx, formatting));
|
|
337
|
-
|
|
350
|
+
return;
|
|
338
351
|
}
|
|
339
352
|
// Publishing: content DITA-OT's HTML does not show (foreign content such as MathML without
|
|
340
353
|
// a plugin, and required-cleanup without args.draft)
|
|
341
354
|
if (ctx.publish && (name === "foreign" || name === "unknown" || name === "required-cleanup"))
|
|
342
|
-
|
|
355
|
+
return;
|
|
343
356
|
if ((name === "fn" || name === "synnote") && ctx.publish && ctx.footnotes) {
|
|
344
357
|
// Publishing: a numbered footnote, listed at the end of the page as DITA-OT does. Only
|
|
345
358
|
// footnotes without @callout are numbered; a callout is the label when it is a valid,
|
|
@@ -351,14 +364,14 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
351
364
|
// Converted by parse.ts, which can convert block content (a list in a footnote)
|
|
352
365
|
ctx.footnotes.push({ label, el: node, ...(callout ? { callout: true } : {}) });
|
|
353
366
|
out.push({ type: "footnote-ref", label });
|
|
354
|
-
|
|
367
|
+
return;
|
|
355
368
|
}
|
|
356
369
|
if (name === "q" && ctx.publish) {
|
|
357
370
|
// Publishing: a quotation in quotation marks, as a browser renders DITA-OT's <q>
|
|
358
371
|
pushText("\u201C");
|
|
359
372
|
out.push(...convertInline(children, ctx, formatting));
|
|
360
373
|
pushText("\u201D");
|
|
361
|
-
|
|
374
|
+
return;
|
|
362
375
|
}
|
|
363
376
|
if (isCodeLike(name, ctx) && ctx.publish && children.some((c) => isElement(c) && ["xref", "link"].includes(c.name.toLowerCase()))) {
|
|
364
377
|
// Publishing: a code phrase containing a link, as DITA-OT renders it: one code
|
|
@@ -374,7 +387,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
374
387
|
out.push({ type: "html-inline", html: `<code>${collapseWhitespace(html).trim()}</code>` });
|
|
375
388
|
if (raw.endsWith(" "))
|
|
376
389
|
pushText(" ");
|
|
377
|
-
|
|
390
|
+
return;
|
|
378
391
|
}
|
|
379
392
|
if (isCodeLike(name, ctx)) {
|
|
380
393
|
if (name !== "codeph" && name !== "tt") {
|
|
@@ -394,7 +407,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
394
407
|
});
|
|
395
408
|
if (ctx.publish && raw.endsWith(" ") && raw.trim())
|
|
396
409
|
pushText(" ");
|
|
397
|
-
|
|
410
|
+
return;
|
|
398
411
|
}
|
|
399
412
|
if (name === "menucascade") {
|
|
400
413
|
// "File > Save Project", as DITA-OT's HTML renders it: each step keeps
|
|
@@ -412,7 +425,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
412
425
|
out.push(...converted);
|
|
413
426
|
any = true;
|
|
414
427
|
}
|
|
415
|
-
|
|
428
|
+
return;
|
|
416
429
|
}
|
|
417
430
|
if (name === "shortcut") {
|
|
418
431
|
// A keystroke (`<shortcut>Ctrl+S</shortcut>`) is a keycap. Inside a
|
|
@@ -424,21 +437,21 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
424
437
|
out.push({ type: "kbd", keys });
|
|
425
438
|
else
|
|
426
439
|
out.push(...convertInline(children, ctx, formatting));
|
|
427
|
-
|
|
440
|
+
return;
|
|
428
441
|
}
|
|
429
442
|
if (BOLD_LIKE.has(name)) {
|
|
430
443
|
if (name !== "b" && name !== "strong") {
|
|
431
444
|
noteOnce(ctx, `<${name}>`, `rendered as bold — semantic identity not preserved`);
|
|
432
445
|
}
|
|
433
446
|
out.push(...convertInline(children, ctx, { ...formatting, bold: true }));
|
|
434
|
-
|
|
447
|
+
return;
|
|
435
448
|
}
|
|
436
449
|
if (ITALIC_LIKE.has(name)) {
|
|
437
450
|
if (name !== "i" && name !== "em") {
|
|
438
451
|
noteOnce(ctx, `<${name}>`, `rendered as italic — semantic identity not preserved`);
|
|
439
452
|
}
|
|
440
453
|
out.push(...convertInline(children, ctx, { ...formatting, italic: true }));
|
|
441
|
-
|
|
454
|
+
return;
|
|
442
455
|
}
|
|
443
456
|
if (name === "tm" && ctx.publish) {
|
|
444
457
|
// Publishing: the trademark symbol DITA-OT's HTML adds, on a trademark's first use in the topic
|
|
@@ -448,18 +461,18 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
448
461
|
if (symbol && !ctx.trademarks?.has(trademark))
|
|
449
462
|
pushText(symbol);
|
|
450
463
|
ctx.trademarks?.add(trademark);
|
|
451
|
-
|
|
464
|
+
return;
|
|
452
465
|
}
|
|
453
466
|
// Highlighting Markdown has no syntax for: inline HTML around the converted content
|
|
454
467
|
if (name === "u" || name === "sub" || name === "sup" || name === "overline") {
|
|
455
468
|
const open = name === "overline" ? '<span style="text-decoration: overline">' : `<${name}>`;
|
|
456
469
|
const close = name === "overline" ? "</span>" : `</${name}>`;
|
|
457
470
|
out.push({ type: "html-inline", html: open }, ...convertInline(children, ctx, formatting), { type: "html-inline", html: close });
|
|
458
|
-
|
|
471
|
+
return;
|
|
459
472
|
}
|
|
460
473
|
if (name === "line-through") {
|
|
461
474
|
out.push(...convertInline(children, ctx, { ...formatting, strikethrough: true }));
|
|
462
|
-
|
|
475
|
+
return;
|
|
463
476
|
}
|
|
464
477
|
if (name === "xref" || name === "link") {
|
|
465
478
|
const href = node.attribs?.href;
|
|
@@ -476,7 +489,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
476
489
|
out.push(...linkChildren);
|
|
477
490
|
else if (text)
|
|
478
491
|
pushText(text);
|
|
479
|
-
|
|
492
|
+
return;
|
|
480
493
|
}
|
|
481
494
|
if (keyref && !ctx.publish) {
|
|
482
495
|
// Key-based links keep their indirection via the key: scheme
|
|
@@ -486,7 +499,7 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
486
499
|
href: `key:${keyref}`,
|
|
487
500
|
children: linkChildren.length > 0 ? linkChildren : [{ type: "text", text: keyref }],
|
|
488
501
|
});
|
|
489
|
-
|
|
502
|
+
return;
|
|
490
503
|
}
|
|
491
504
|
if (href) {
|
|
492
505
|
// An external link keeps its href as written, even to a .dita file
|
|
@@ -498,11 +511,11 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
498
511
|
? linkChildren
|
|
499
512
|
: [{ type: "text", text: fallbackText || (ctx.publish ? ditaOtHrefText(href) : href) }],
|
|
500
513
|
});
|
|
501
|
-
|
|
514
|
+
return;
|
|
502
515
|
}
|
|
503
516
|
noteOnce(ctx, `<${name}>`, "xref without href/keyref — emitted as plain text");
|
|
504
517
|
pushText(fallbackText);
|
|
505
|
-
|
|
518
|
+
return;
|
|
506
519
|
}
|
|
507
520
|
if (name === "image") {
|
|
508
521
|
const imgHref = node.attribs?.href;
|
|
@@ -513,13 +526,13 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
513
526
|
else {
|
|
514
527
|
noteOnce(ctx, "<image>", "image without href dropped");
|
|
515
528
|
}
|
|
516
|
-
|
|
529
|
+
return;
|
|
517
530
|
}
|
|
518
531
|
if (ctx.publish) {
|
|
519
532
|
// Publishing: size, alignment and the <alt> element, as DITA-OT's HTML renders them
|
|
520
533
|
const { alt, attrs } = publishImage(node, ctx.publish);
|
|
521
534
|
out.push({ type: "image", src: imgHref, ...(alt ? { alt } : {}), ...(attrs ? { attrs } : {}) });
|
|
522
|
-
|
|
535
|
+
return;
|
|
523
536
|
}
|
|
524
537
|
// The <alt> element (DITA 1.1+) wins over the deprecated @alt, as in publishing.
|
|
525
538
|
const altEl = (children).find((c) => isElement(c) && c.name.toLowerCase() === "alt");
|
|
@@ -529,17 +542,16 @@ export const convertInline = (nodes, ctx, formatting = {}) => {
|
|
|
529
542
|
src: imgHref,
|
|
530
543
|
...(alt ? { alt } : {}),
|
|
531
544
|
});
|
|
532
|
-
|
|
545
|
+
return;
|
|
533
546
|
}
|
|
534
547
|
if (name === "indexterm" || name === "draft-comment" || name === "data") {
|
|
535
548
|
noteOnce(ctx, `<${name}>`, "metadata element dropped from output");
|
|
536
|
-
|
|
549
|
+
return;
|
|
537
550
|
}
|
|
538
551
|
// Unknown inline element: keep the text, note the loss
|
|
539
552
|
noteOnce(ctx, `<${name}>`, "unrecognized inline element — text kept, markup dropped");
|
|
540
553
|
out.push(...convertInline(children, ctx, formatting));
|
|
541
554
|
}
|
|
542
|
-
return mergeAdjacentText(out);
|
|
543
555
|
};
|
|
544
556
|
/** Merge neighbouring text spans with identical formatting (recursive calls
|
|
545
557
|
* push whole runs, so pushText's local merging alone is not enough). */
|
package/dist/lines.d.ts
ADDED
package/dist/lines.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offsets → 1-based line numbers, counting lines as checkWellFormed does:
|
|
3
|
+
* `\n`, `\r\n` and a lone `\r` each end one.
|
|
4
|
+
*/
|
|
5
|
+
export function lineLocator(text) {
|
|
6
|
+
const starts = [0];
|
|
7
|
+
for (let i = 0; i < text.length; i++) {
|
|
8
|
+
const c = text.charCodeAt(i);
|
|
9
|
+
if (c === 10 || (c === 13 && text.charCodeAt(i + 1) !== 10))
|
|
10
|
+
starts.push(i + 1);
|
|
11
|
+
}
|
|
12
|
+
return (offset) => {
|
|
13
|
+
let lo = 0;
|
|
14
|
+
let hi = starts.length - 1;
|
|
15
|
+
while (lo < hi) {
|
|
16
|
+
const mid = (lo + hi + 1) >> 1;
|
|
17
|
+
if (starts[mid] <= offset)
|
|
18
|
+
lo = mid;
|
|
19
|
+
else
|
|
20
|
+
hi = mid - 1;
|
|
21
|
+
}
|
|
22
|
+
return lo + 1;
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/parse.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { checkWellFormed } from "./well-formed.js";
|
|
2
|
+
import { applyClassDefaults } from "./class-defaults-apply.js";
|
|
3
|
+
import { lineLocator } from "./lines.js";
|
|
2
4
|
import { parseDocument } from "htmlparser2";
|
|
3
5
|
import { convertChildren, convertItemContent } from "./blocks.js";
|
|
4
6
|
import { textOf, convertInline, trimInline, collapseWhitespace } from "./inline.js";
|
|
@@ -88,6 +90,8 @@ const convertSecondaryTopic = (el, ctx, level, bindConrefSite) => {
|
|
|
88
90
|
for (const child of el.children) {
|
|
89
91
|
if (!isElement(child))
|
|
90
92
|
continue;
|
|
93
|
+
if (ctx.position)
|
|
94
|
+
ctx.position.el = child;
|
|
91
95
|
const name = child.name.toLowerCase();
|
|
92
96
|
if (name === "title" || name === "glossterm") {
|
|
93
97
|
out.push({
|
|
@@ -97,14 +101,16 @@ const convertSecondaryTopic = (el, ctx, level, bindConrefSite) => {
|
|
|
97
101
|
...(el.attribs?.id ? { id: el.attribs.id } : {}),
|
|
98
102
|
},
|
|
99
103
|
inline: trimInline(convertInline(child.children, inner)),
|
|
100
|
-
...(el.attribs?.id
|
|
104
|
+
...(el.attribs?.id || ctx.lineOf?.(child) !== undefined
|
|
105
|
+
? { meta: { ...(el.attribs?.id ? { dita: { id: el.attribs.id } } : {}), ...(ctx.lineOf?.(child) !== undefined ? { line: ctx.lineOf(child) } : {}) } }
|
|
106
|
+
: {}),
|
|
101
107
|
});
|
|
102
108
|
headingEmitted = true;
|
|
103
109
|
continue;
|
|
104
110
|
}
|
|
105
111
|
if (name === "titlealts")
|
|
106
112
|
continue;
|
|
107
|
-
if (isTopicElement(child, !!ctx.publish)) {
|
|
113
|
+
if (isTopicElement(child, !!ctx.publish || !!ctx.classes)) {
|
|
108
114
|
out.push(...convertSecondaryTopic(child, ctx, bodyLevel, bindConrefSite));
|
|
109
115
|
continue;
|
|
110
116
|
}
|
|
@@ -219,13 +225,27 @@ const extractProlog = (el, meta, ctx) => {
|
|
|
219
225
|
export function ditaTopicToTree(xml, options = {}) {
|
|
220
226
|
const topicPath = options.topicPath ?? "";
|
|
221
227
|
const notes = [];
|
|
222
|
-
|
|
228
|
+
// Every note carries the line of the element converting when it was raised
|
|
229
|
+
// (a shared holder: contexts are copied, the holder is not)
|
|
230
|
+
const lineOf = lineLocator(xml);
|
|
231
|
+
const position = {};
|
|
232
|
+
const lineAt = (el) => el?.startIndex != null ? lineOf(el.startIndex) : undefined;
|
|
233
|
+
const note = (construct, detail, line = lineAt(position.el)) => notes.push({ topicPath, construct, detail, ...(line ? { line } : {}) });
|
|
223
234
|
// The parser repairs malformed XML silently: say so, with a line
|
|
224
235
|
const malformed = checkWellFormed(xml);
|
|
225
236
|
if (malformed)
|
|
226
|
-
note("<xml>", `not well-formed XML, line ${malformed.line}: ${malformed.message} — the converted content may be incomplete
|
|
227
|
-
const doc = parseDocument(xml, { xmlMode: true });
|
|
228
|
-
|
|
237
|
+
note("<xml>", `not well-formed XML, line ${malformed.line}: ${malformed.message} — the converted content may be incomplete`, malformed.line);
|
|
238
|
+
const doc = parseDocument(xml, { xmlMode: true, withStartIndices: true });
|
|
239
|
+
// Raw source: fill in the @class the DTDs default, so specializations convert as their base
|
|
240
|
+
if (options.classDefaults) {
|
|
241
|
+
const firstOf = new Map();
|
|
242
|
+
const undeclared = applyClassDefaults(doc.children, firstOf);
|
|
243
|
+
if (undeclared.length) {
|
|
244
|
+
note("@class", `no DITA 1.3 class default for ${undeclared.map((n) => `<${n}>`).join(", ")} — converted by name only`, lineAt(firstOf.get(undeclared[0])));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const byClass = !!options.publish || !!options.classDefaults;
|
|
248
|
+
const roots = findTopicRoots(doc.children, byClass);
|
|
229
249
|
const root = roots[0] ?? null;
|
|
230
250
|
if (!root) {
|
|
231
251
|
note("<topic>", "no DITA topic root element found (concept/task/reference/topic/glossentry)");
|
|
@@ -274,6 +294,9 @@ export function ditaTopicToTree(xml, options = {}) {
|
|
|
274
294
|
note,
|
|
275
295
|
seenConstructs: new Set(),
|
|
276
296
|
headingLevel: 2,
|
|
297
|
+
position,
|
|
298
|
+
...(options.classDefaults ? { classes: true } : {}),
|
|
299
|
+
...(options.sourceLines ? { lineOf: lineAt } : {}),
|
|
277
300
|
...(bindConrefSite ? { conref: bindConrefSite(root.attribs?.id) } : {}),
|
|
278
301
|
...(options.publish
|
|
279
302
|
? { publish: options.publish, trademarks: new Set(), footnotes: [], topicId: root.attribs?.id, anchorIds: renamedElementIds(doc.children) }
|
|
@@ -287,6 +310,7 @@ export function ditaTopicToTree(xml, options = {}) {
|
|
|
287
310
|
for (const child of root.children) {
|
|
288
311
|
if (!isElement(child))
|
|
289
312
|
continue;
|
|
313
|
+
position.el = child;
|
|
290
314
|
const name = child.name.toLowerCase();
|
|
291
315
|
if (name === "title" || name === "glossterm") {
|
|
292
316
|
// The page title is plain text: a badge in it keeps only its words
|
|
@@ -332,7 +356,7 @@ export function ditaTopicToTree(xml, options = {}) {
|
|
|
332
356
|
extractProlog(child, meta, ctx);
|
|
333
357
|
continue;
|
|
334
358
|
}
|
|
335
|
-
if (isTopicElement(child,
|
|
359
|
+
if (isTopicElement(child, byClass)) {
|
|
336
360
|
// Nested topic: convert inline as a section (chunked authoring)
|
|
337
361
|
tree.push(...convertSecondaryTopic(child, ctx, 2, bindConrefSite));
|
|
338
362
|
continue;
|
|
@@ -341,9 +365,12 @@ export function ditaTopicToTree(xml, options = {}) {
|
|
|
341
365
|
}
|
|
342
366
|
// Sibling topics under a composite wrapper convert the same way
|
|
343
367
|
for (const sibling of roots.slice(1)) {
|
|
368
|
+
position.el = sibling;
|
|
344
369
|
// Publishing: DITA-OT titles sibling topics in one file like the first (h1)
|
|
345
370
|
tree.push(...convertSecondaryTopic(sibling, ctx, options.publish ? 1 : 2, bindConrefSite));
|
|
346
371
|
}
|
|
372
|
+
// Notes about the whole file (duplicate ids) are about no one element
|
|
373
|
+
position.el = undefined;
|
|
347
374
|
// DITA element ids are unique per TOPIC, not per file — after inlining
|
|
348
375
|
// multiple topics, colliding ids mean fragment links can mis-target.
|
|
349
376
|
// Known loss, never silent loss: report every duplicate anchor.
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface DitaConversionNote {
|
|
|
10
10
|
/** Construct that triggered the note (element/attribute name). */
|
|
11
11
|
construct: string;
|
|
12
12
|
detail: string;
|
|
13
|
+
/**
|
|
14
|
+
* The 1-based source line the note is about: the element that raised it, or
|
|
15
|
+
* failing that the block being converted (absent when there is none, as for
|
|
16
|
+
* "no topic root").
|
|
17
|
+
*/
|
|
18
|
+
line?: number;
|
|
13
19
|
}
|
|
14
20
|
/** Topic-level metadata extracted alongside the block tree. */
|
|
15
21
|
export interface DitaTopicMeta {
|
|
@@ -152,6 +158,22 @@ export interface DitaParseOptions {
|
|
|
152
158
|
* its behavior unchanged.
|
|
153
159
|
*/
|
|
154
160
|
publish?: PublishContext;
|
|
161
|
+
/**
|
|
162
|
+
* Fill in each missing @class from the DITA 1.3 DTD defaults (612 elements,
|
|
163
|
+
* see class-defaults.ts) before converting, as parsing against the DTDs
|
|
164
|
+
* would. Raw source has no @class, so without it an element is known only by
|
|
165
|
+
* its name, and a specialization the converter does not know by name loses
|
|
166
|
+
* its markup; with it, it converts as what it specializes. An existing
|
|
167
|
+
* @class is kept; a name the DTDs do not declare stays unclassed and is
|
|
168
|
+
* noted once. Off by default (it changes the output for such elements).
|
|
169
|
+
*/
|
|
170
|
+
classDefaults?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Put each block node's 1-based source line on `meta.line` (the line of the
|
|
173
|
+
* element it came from), so a host can map the rendered page back to the
|
|
174
|
+
* source; render-html writes it as `data-line` when asked. Off by default.
|
|
175
|
+
*/
|
|
176
|
+
sourceLines?: boolean;
|
|
155
177
|
}
|
|
156
178
|
/** DITA topic root elements this importer understands. */
|
|
157
179
|
export declare const DITA_TOPIC_TYPES: readonly ["concept", "task", "reference", "topic", "glossentry"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/format-dita",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.125",
|
|
4
4
|
"description": "DITA topic XML → TreeNode importer with conversion-note ledger; composes with format-dogsbay-md for .dita → .md migration. Browser-safe: no Node built-ins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"domhandler": "^5.0.3",
|
|
30
30
|
"htmlparser2": "^10.1.0",
|
|
31
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
32
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
31
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.125",
|
|
32
|
+
"@dogsbay/types": "0.2.0-beta.125"
|
|
33
33
|
},
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"repository": {
|