@dogsbay/format-dita 0.2.0-beta.114 → 0.2.0-beta.116
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/dist/anchors.d.ts +20 -0
- package/dist/anchors.d.ts.map +1 -0
- package/dist/anchors.js +35 -0
- package/dist/anchors.js.map +1 -0
- package/dist/blocks.d.ts +10 -1
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +438 -41
- package/dist/blocks.js.map +1 -1
- package/dist/inline.d.ts +40 -2
- package/dist/inline.d.ts.map +1 -1
- package/dist/inline.js +258 -22
- package/dist/inline.js.map +1 -1
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +118 -18
- package/dist/parse.js.map +1 -1
- package/dist/publish-support.d.ts +11 -0
- package/dist/publish-support.d.ts.map +1 -0
- package/dist/publish-support.js +11 -0
- package/dist/publish-support.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -3
package/dist/blocks.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inlineToDogsbayMd } from "@dogsbay/format-dogsbay-md";
|
|
2
|
+
import { convertInline, elementName, idAnchor, publishImage, trimInline, textOf, inlineSafeSnippet, conrefLedgerDetail, } from "./inline.js";
|
|
3
|
+
import { publishHref, publishLabel, publishLanguage, publishString } from "./types.js";
|
|
4
|
+
import { anchorId } from "./anchors.js";
|
|
2
5
|
const isElement = (node) => node.type === "tag";
|
|
3
6
|
const isNonEmptyText = (node) => node.type === "text" && node.data.trim().length > 0;
|
|
4
7
|
/** DITA note @type → callout type in the dogsbay-md vocabulary. */
|
|
@@ -14,6 +17,10 @@ const NOTE_TYPE_MAP = {
|
|
|
14
17
|
restriction: "important",
|
|
15
18
|
trouble: "warning",
|
|
16
19
|
};
|
|
20
|
+
/** Publishing: more DITA note types, as the closest callout dogsbay-md renders. */
|
|
21
|
+
const PUBLISH_NOTE_TYPE_MAP = { notice: "important", fastpath: "tip", other: "note" };
|
|
22
|
+
/** Callout types dogsbay-md renders, kept as authored when publishing. */
|
|
23
|
+
const PUBLISH_CALLOUT_TYPES = new Set(["note", "tip", "important", "warning", "caution", "danger"]);
|
|
17
24
|
/** Containers whose children are spliced through without a wrapper node. */
|
|
18
25
|
const TRANSPARENT_CONTAINERS = new Set([
|
|
19
26
|
"body", "conbody", "refbody", "taskbody", "glossdef", "abstract", "bodydiv", "sectiondiv", "div",
|
|
@@ -51,6 +58,26 @@ const noteFilterAttrs = (el, ctx) => {
|
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* A caption is inline Markdown: the title's formatting (code, emphasis,
|
|
63
|
+
* and in publishing DITA-OT's generated markup such as `<plugin>`) is kept.
|
|
64
|
+
*/
|
|
65
|
+
const captionText = (titleEl, ctx, owner) => {
|
|
66
|
+
// Publishing with numbering: DITA-OT's "Table 1. " / "Figure 2. " label first
|
|
67
|
+
const label = ctx.numberLabels?.get(owner);
|
|
68
|
+
const inline = trimInline(convertInline(titleEl.children, ctx));
|
|
69
|
+
return inlineToDogsbayMd(label ? [{ type: "text", text: label }, ...inline] : inline);
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Whether a child element converts as a block. Publishing: an image is inline
|
|
73
|
+
* unless its placement is "break" (DITA's default placement is inline).
|
|
74
|
+
*/
|
|
75
|
+
const isBlockChild = (el, ctx) => {
|
|
76
|
+
const name = blockName(el, ctx);
|
|
77
|
+
if (ctx.publish && name === "image")
|
|
78
|
+
return el.attribs?.placement === "break";
|
|
79
|
+
return BLOCK_ELEMENTS.has(name);
|
|
80
|
+
};
|
|
54
81
|
const paragraphFrom = (nodes, ctx) => {
|
|
55
82
|
const inline = trimInline(convertInline(nodes, ctx));
|
|
56
83
|
if (inline.length === 0)
|
|
@@ -82,13 +109,15 @@ const mixedContent = (el, ctx) => {
|
|
|
82
109
|
}
|
|
83
110
|
};
|
|
84
111
|
for (const child of el.children) {
|
|
85
|
-
if (isElement(child) &&
|
|
112
|
+
if (isElement(child) && (isBlockChild(child, ctx) || TRANSPARENT_CONTAINERS.has(blockName(child, ctx)))) {
|
|
86
113
|
flushRun();
|
|
87
114
|
sawBlock = true;
|
|
88
115
|
blockNodes.push(...convertBlock(child, ctx));
|
|
89
116
|
continue;
|
|
90
117
|
}
|
|
91
|
-
|
|
118
|
+
// Whitespace between two inline elements is a word space; at the edges
|
|
119
|
+
// of a run it is trimmed
|
|
120
|
+
if (isElement(child) || isNonEmptyText(child) || (child.type === "text" && run.length > 0)) {
|
|
92
121
|
run.push(child);
|
|
93
122
|
}
|
|
94
123
|
}
|
|
@@ -186,7 +215,8 @@ const convertListItems = (el, ctx, itemTag) => el.children
|
|
|
186
215
|
const viaConref = itemConref(li, ctx, "list-item") ?? definingItemNode(li, ctx, "list-item");
|
|
187
216
|
if (viaConref)
|
|
188
217
|
return withProvenance(viaConref, li);
|
|
189
|
-
const { inline, children } = mixedContent(li, { ...ctx, constrained: true });
|
|
218
|
+
const { inline: content, children: blocks } = mixedContent(li, { ...ctx, constrained: true });
|
|
219
|
+
const { inline, children } = withItemAnchor(content, blocks, [li.attribs?.id], ctx);
|
|
190
220
|
const item = { type: "list-item" };
|
|
191
221
|
if (inline)
|
|
192
222
|
item.inline = inline;
|
|
@@ -205,6 +235,7 @@ const convertDefList = (el, ctx, tags = { entry: "dlentry", term: "dt", def: "dd
|
|
|
205
235
|
const ref = entry.attribs.conref ?? entry.attribs.conkeyref;
|
|
206
236
|
ctx.note(entry.attribs.conref ? "@conref" : "@conkeyref", `<${tags.entry}> pulls content from "${ref}" — ${conrefLedgerDetail(entry.attribs, undefined, ctx.conref !== undefined)}`);
|
|
207
237
|
}
|
|
238
|
+
let entryAnchored = false;
|
|
208
239
|
for (const part of entry.children.filter(isElement)) {
|
|
209
240
|
const name = part.name.toLowerCase();
|
|
210
241
|
if (name === tags.term) {
|
|
@@ -213,8 +244,10 @@ const convertDefList = (el, ctx, tags = { entry: "dlentry", term: "dt", def: "dd
|
|
|
213
244
|
const viaConref = constrainedConrefInline(part, ctx);
|
|
214
245
|
children.push({
|
|
215
246
|
type: "dt",
|
|
216
|
-
|
|
247
|
+
// The entry's id anchors its first term only (several terms would repeat it)
|
|
248
|
+
inline: withInlineAnchor(viaConref ?? trimInline(convertInline(part.children, ctx)), [entryAnchored ? undefined : entry.attribs?.id, part.attribs?.id], ctx),
|
|
217
249
|
});
|
|
250
|
+
entryAnchored = true;
|
|
218
251
|
}
|
|
219
252
|
else if (name === tags.def) {
|
|
220
253
|
const viaConref = constrainedConrefInline(part, ctx);
|
|
@@ -223,6 +256,9 @@ const convertDefList = (el, ctx, tags = { entry: "dlentry", term: "dt", def: "dd
|
|
|
223
256
|
continue;
|
|
224
257
|
}
|
|
225
258
|
const { inline, children: blocks } = mixedContent(part, { ...ctx, constrained: true });
|
|
259
|
+
// Publishing: an empty definition (all its content filtered out) renders nothing
|
|
260
|
+
if (ctx.publish && !inline && blocks.length === 0)
|
|
261
|
+
continue;
|
|
226
262
|
const dd = { type: "dd" };
|
|
227
263
|
if (inline)
|
|
228
264
|
dd.inline = inline;
|
|
@@ -234,31 +270,85 @@ const convertDefList = (el, ctx, tags = { entry: "dlentry", term: "dt", def: "dd
|
|
|
234
270
|
}
|
|
235
271
|
return { type: "deflist", children };
|
|
236
272
|
};
|
|
273
|
+
/**
|
|
274
|
+
* A CALS entry's column and row spans: `morerows`, and the columns from
|
|
275
|
+
* `namest` to `nameend` by their `colspec` positions. DITA-OT's
|
|
276
|
+
* preprocessing also writes the column span as `dita-ot:morecols`.
|
|
277
|
+
*/
|
|
278
|
+
const cellSpans = (cellEl, rowEl) => {
|
|
279
|
+
const spans = {};
|
|
280
|
+
const morerows = Number(cellEl.attribs?.morerows);
|
|
281
|
+
if (morerows > 0)
|
|
282
|
+
spans.rowspan = morerows + 1;
|
|
283
|
+
const morecols = Number(cellEl.attribs?.["dita-ot:morecols"]);
|
|
284
|
+
if (morecols > 0) {
|
|
285
|
+
spans.colspan = morecols + 1;
|
|
286
|
+
}
|
|
287
|
+
else if (cellEl.attribs?.namest && cellEl.attribs?.nameend) {
|
|
288
|
+
const tgroup = rowEl.parent?.parent;
|
|
289
|
+
const columns = new Map();
|
|
290
|
+
(tgroup?.children ?? [])
|
|
291
|
+
.filter((c) => isElement(c) && c.name.toLowerCase() === "colspec")
|
|
292
|
+
.reduce((previous, spec) => {
|
|
293
|
+
// A colspec without colnum is the column after the previous one (CALS)
|
|
294
|
+
const colnum = Number(spec.attribs?.colnum) || previous + 1;
|
|
295
|
+
if (spec.attribs?.colname)
|
|
296
|
+
columns.set(spec.attribs.colname, colnum);
|
|
297
|
+
return colnum;
|
|
298
|
+
}, 0);
|
|
299
|
+
const start = columns.get(cellEl.attribs.namest);
|
|
300
|
+
const end = columns.get(cellEl.attribs.nameend);
|
|
301
|
+
if (start && end && end > start)
|
|
302
|
+
spans.colspan = end - start + 1;
|
|
303
|
+
}
|
|
304
|
+
return spans.colspan || spans.rowspan ? spans : undefined;
|
|
305
|
+
};
|
|
306
|
+
const TABLE_PARTS = new Set([
|
|
307
|
+
"tgroup", "thead", "tbody", "row", "entry", "sthead", "strow", "stentry", "chhead", "chrow", "colspec", "title",
|
|
308
|
+
]);
|
|
309
|
+
const hasClassToken = (el, token) => ` ${el.attribs?.class ?? ""} `.includes(` ${token} `);
|
|
310
|
+
const elementChildren = (el) => el.children.filter(isElement);
|
|
311
|
+
const PROPERTY_KINDS = ["proptype", "propvalue", "propdesc"];
|
|
312
|
+
/** A properties table cell's column: `proptype`, or with suffix "hd" a header cell (`proptypehd`). */
|
|
313
|
+
const propertyKind = (cell, suffix = "") => PROPERTY_KINDS.find((k) => hasClassToken(cell, `reference/${k}${suffix}`));
|
|
314
|
+
/** A properties table's columns in use, its properties that have cells, and its header row. */
|
|
315
|
+
const propertiesColumns = (el) => {
|
|
316
|
+
const header = elementChildren(el).find((c) => hasClassToken(c, "reference/prophead"));
|
|
317
|
+
const propertyRows = elementChildren(el).filter((c) => hasClassToken(c, "reference/property") && elementChildren(c).some((cell) => !!propertyKind(cell)));
|
|
318
|
+
const used = PROPERTY_KINDS.filter((k) => propertyRows.some((r) => elementChildren(r).some((c) => propertyKind(c) === k)) ||
|
|
319
|
+
(header ? elementChildren(header).some((c) => propertyKind(c, "hd") === k) : false));
|
|
320
|
+
return { used, propertyRows, ...(header ? { header } : {}) };
|
|
321
|
+
};
|
|
237
322
|
/** CALS <table> and <simpletable> → table/tr/td tree. */
|
|
238
323
|
const convertTable = (el, ctx) => {
|
|
239
324
|
const rows = [];
|
|
240
325
|
const headRows = [];
|
|
241
|
-
|
|
326
|
+
// Header cells: every cell of a header row; publishing also marks the key
|
|
327
|
+
// column of a simple table (@keycol, 1-based) or the first column of a
|
|
328
|
+
// CALS table with rowheader="firstcol", as DITA-OT's HTML does
|
|
329
|
+
const keycol = ctx.publish ? Number(el.attribs?.keycol ?? (el.attribs?.rowheader === "firstcol" ? 1 : 0)) || 0 : 0;
|
|
330
|
+
const convertRow = (rowEl, cellTags, header = false) => {
|
|
242
331
|
// Row-level conref: a whole row can't splice as an include — ledger it
|
|
243
332
|
if (rowEl.attribs?.conref || rowEl.attribs?.conkeyref) {
|
|
244
333
|
const ref = rowEl.attribs.conref ?? rowEl.attribs.conkeyref;
|
|
245
334
|
ctx.note(rowEl.attribs.conref ? "@conref" : "@conkeyref", `<${rowEl.name.toLowerCase()}> pulls content from "${ref}" — ${conrefLedgerDetail(rowEl.attribs, undefined, ctx.conref !== undefined)}`);
|
|
246
335
|
}
|
|
247
336
|
const cells = rowEl.children
|
|
248
|
-
.filter((c) => isElement(c) && cellTags.has(c.
|
|
249
|
-
.map((cellEl) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
337
|
+
.filter((c) => isElement(c) && cellTags.has(elementName(c, ctx, (n) => cellTags.has(n))))
|
|
338
|
+
.map((cellEl, index) => {
|
|
339
|
+
const cellType = header || index + 1 === keycol ? "th" : "td";
|
|
340
|
+
const spans = cellSpans(cellEl, rowEl);
|
|
341
|
+
if (cellEl.attribs?.namest && cellEl.attribs?.nameend && !spans?.colspan) {
|
|
342
|
+
ctx.note("@namest", `column span ${cellEl.attribs.namest}–${cellEl.attribs.nameend} has no matching colspec — cell emitted without its column span`);
|
|
254
343
|
}
|
|
255
344
|
// Cell-level conref: single-line snippets splice as an inline
|
|
256
345
|
// include; anything else is ledgered
|
|
257
346
|
const cellConref = constrainedConrefInline(cellEl, ctx);
|
|
258
347
|
if (cellConref) {
|
|
259
348
|
return {
|
|
260
|
-
type:
|
|
349
|
+
type: cellType,
|
|
261
350
|
children: [{ type: "paragraph", inline: cellConref }],
|
|
351
|
+
...(spans ? { props: spans } : {}),
|
|
262
352
|
};
|
|
263
353
|
}
|
|
264
354
|
// Cell content always lives in children (leading inline wrapped as a
|
|
@@ -269,9 +359,11 @@ const convertTable = (el, ctx) => {
|
|
|
269
359
|
if (inline)
|
|
270
360
|
cellChildren.push({ type: "paragraph", inline });
|
|
271
361
|
cellChildren.push(...children);
|
|
272
|
-
const cell = { type:
|
|
362
|
+
const cell = { type: cellType };
|
|
273
363
|
if (cellChildren.length > 0)
|
|
274
364
|
cell.children = cellChildren;
|
|
365
|
+
if (spans)
|
|
366
|
+
cell.props = spans;
|
|
275
367
|
return cell;
|
|
276
368
|
});
|
|
277
369
|
return { type: "tr", children: cells };
|
|
@@ -282,7 +374,8 @@ const convertTable = (el, ctx) => {
|
|
|
282
374
|
const CHOICE_ROW_CELLS = new Set(["choption", "chdesc"]);
|
|
283
375
|
const walk = (node) => {
|
|
284
376
|
for (const child of node.children.filter(isElement)) {
|
|
285
|
-
|
|
377
|
+
// Specialized table parts (a properties table's prophead and property rows) by @class
|
|
378
|
+
const name = elementName(child, ctx, (n) => TABLE_PARTS.has(n));
|
|
286
379
|
// Row-set reuse (conref on tgroup/thead/tbody) can't splice into a
|
|
287
380
|
// markdown table — ledger it so the pulled rows are never silently
|
|
288
381
|
// an empty table body
|
|
@@ -295,24 +388,24 @@ const convertTable = (el, ctx) => {
|
|
|
295
388
|
walk(child);
|
|
296
389
|
else if (name === "thead") {
|
|
297
390
|
for (const row of child.children.filter(isElement)) {
|
|
298
|
-
if (row
|
|
299
|
-
headRows.push(convertRow(row, CALS_CELLS));
|
|
391
|
+
if (elementName(row, ctx, (n) => n === "row") === "row")
|
|
392
|
+
headRows.push(convertRow(row, CALS_CELLS, true));
|
|
300
393
|
}
|
|
301
394
|
}
|
|
302
395
|
else if (name === "tbody") {
|
|
303
396
|
for (const row of child.children.filter(isElement)) {
|
|
304
|
-
if (row
|
|
397
|
+
if (elementName(row, ctx, (n) => n === "row") === "row")
|
|
305
398
|
rows.push(convertRow(row, CALS_CELLS));
|
|
306
399
|
}
|
|
307
400
|
}
|
|
308
401
|
else if (name === "sthead") {
|
|
309
|
-
headRows.push(convertRow(child, SIMPLE_CELLS));
|
|
402
|
+
headRows.push(convertRow(child, SIMPLE_CELLS, true));
|
|
310
403
|
}
|
|
311
404
|
else if (name === "strow") {
|
|
312
405
|
rows.push(convertRow(child, SIMPLE_CELLS));
|
|
313
406
|
}
|
|
314
407
|
else if (name === "chhead") {
|
|
315
|
-
headRows.push(convertRow(child, CHOICE_HEAD_CELLS));
|
|
408
|
+
headRows.push(convertRow(child, CHOICE_HEAD_CELLS, true));
|
|
316
409
|
}
|
|
317
410
|
else if (name === "chrow") {
|
|
318
411
|
rows.push(convertRow(child, CHOICE_ROW_CELLS));
|
|
@@ -326,16 +419,63 @@ const convertTable = (el, ctx) => {
|
|
|
326
419
|
}
|
|
327
420
|
};
|
|
328
421
|
walk(el);
|
|
422
|
+
// Publishing: a properties table's columns are the type, value and description columns
|
|
423
|
+
// its header or any property uses (DITA-OT's hasType, hasValue, hasDesc); each row
|
|
424
|
+
// aligns its cells to them, a property with no cells is dropped, and a header cell
|
|
425
|
+
// missing for a column in use is DITA-OT's generated label ("Type", "Value", "Description")
|
|
426
|
+
if (ctx.publish && hasClassToken(el, "reference/properties")) {
|
|
427
|
+
const { used, propertyRows, header } = propertiesColumns(el);
|
|
428
|
+
rows.length = 0;
|
|
429
|
+
for (const row of propertyRows) {
|
|
430
|
+
const converted = convertRow(row, SIMPLE_CELLS);
|
|
431
|
+
const cellEls = elementChildren(row).filter((c) => !!propertyKind(c));
|
|
432
|
+
rows.push({
|
|
433
|
+
type: "tr",
|
|
434
|
+
children: used.map((k) => {
|
|
435
|
+
const i = cellEls.findIndex((c) => propertyKind(c) === k);
|
|
436
|
+
return i >= 0 ? converted.children[i] : { type: "td" };
|
|
437
|
+
}),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
const lang = publishLanguage(el, ctx.publish);
|
|
441
|
+
const label = {
|
|
442
|
+
proptype: publishString(ctx.publish, lang, "Type"),
|
|
443
|
+
propvalue: publishString(ctx.publish, lang, "Value"),
|
|
444
|
+
propdesc: publishString(ctx.publish, lang, "Description"),
|
|
445
|
+
};
|
|
446
|
+
const authored = header ? convertRow(header, SIMPLE_CELLS, true) : undefined;
|
|
447
|
+
const headerCells = header ? elementChildren(header).filter((c) => !!propertyKind(c, "hd")) : [];
|
|
448
|
+
headRows.length = 0;
|
|
449
|
+
headRows.push({
|
|
450
|
+
type: "tr",
|
|
451
|
+
children: used.map((k) => {
|
|
452
|
+
const i = headerCells.findIndex((c) => propertyKind(c, "hd") === k);
|
|
453
|
+
return i >= 0 && authored ? authored.children[i] : { type: "th", children: [{ type: "paragraph", inline: [{ type: "text", text: label[k] }] }] };
|
|
454
|
+
}),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
// Publishing: DITA-OT generates the "Option | Description" header of a
|
|
458
|
+
// choice table that has none
|
|
459
|
+
if (ctx.publish && el.name.toLowerCase() === "choicetable" && headRows.length === 0) {
|
|
460
|
+
const th = (text) => ({
|
|
461
|
+
type: "th",
|
|
462
|
+
children: [{ type: "paragraph", inline: [{ type: "text", text }] }],
|
|
463
|
+
});
|
|
464
|
+
const lang = publishLanguage(el, ctx.publish);
|
|
465
|
+
headRows.push({ type: "tr", children: [th(publishString(ctx.publish, lang, "Option")), th(publishString(ctx.publish, lang, "Description"))] });
|
|
466
|
+
}
|
|
329
467
|
const table = {
|
|
330
468
|
type: "table",
|
|
331
469
|
children: [
|
|
332
470
|
...(headRows.length > 0 ? [{ type: "thead", children: headRows }] : []),
|
|
333
471
|
{ type: "tbody", children: rows },
|
|
334
472
|
],
|
|
473
|
+
// Publishing: a table without a header row has none (DITA-OT does not promote a row)
|
|
474
|
+
...(ctx.publish && headRows.length === 0 ? { meta: { headerless: true } } : {}),
|
|
335
475
|
};
|
|
336
476
|
const titleEl = el.children
|
|
337
477
|
.filter(isElement)
|
|
338
|
-
.find((c) => c
|
|
478
|
+
.find((c) => blockName(c, ctx) === "title");
|
|
339
479
|
if (titleEl) {
|
|
340
480
|
// Caption is a plain-string prop — a conref'd title can't carry an
|
|
341
481
|
// include directive, so ledger it instead of emitting empty text
|
|
@@ -343,7 +483,7 @@ const convertTable = (el, ctx) => {
|
|
|
343
483
|
const ref = titleEl.attribs.conref ?? titleEl.attribs.conkeyref;
|
|
344
484
|
ctx.note(titleEl.attribs.conref ? "@conref" : "@conkeyref", `<title> (table caption) pulls content from "${ref}" — ${conrefLedgerDetail(titleEl.attribs, undefined, ctx.conref !== undefined)}`);
|
|
345
485
|
}
|
|
346
|
-
table.props = { caption:
|
|
486
|
+
table.props = { caption: captionText(titleEl, ctx, el) };
|
|
347
487
|
}
|
|
348
488
|
return table;
|
|
349
489
|
};
|
|
@@ -395,16 +535,36 @@ const convertSteps = (el, ctx, type) => {
|
|
|
395
535
|
const viaConref = itemConref(stepEl, ctx, "step") ?? definingItemNode(stepEl, ctx, "step");
|
|
396
536
|
if (viaConref)
|
|
397
537
|
return withProvenance(viaConref, stepEl);
|
|
398
|
-
const { inline, children: blocks } = convertStepContent(stepEl, { ...ctx, constrained: true });
|
|
538
|
+
const { inline: content, children: blocks } = convertStepContent(stepEl, { ...ctx, constrained: true });
|
|
539
|
+
// Publishing: DITA-OT labels optional and required steps
|
|
540
|
+
const importance = stepEl.attribs?.importance;
|
|
541
|
+
const stepLabel = ctx.publish && (importance === "optional" || importance === "required")
|
|
542
|
+
? publishLabel(ctx.publish, publishLanguage(stepEl, ctx.publish), importance === "optional" ? "Optional" : "Required")
|
|
543
|
+
: undefined;
|
|
544
|
+
const labelled = stepLabel
|
|
545
|
+
? [{ type: "text", text: stepLabel, bold: true }, { type: "text", text: " " }, ...(content ?? [])]
|
|
546
|
+
: content;
|
|
547
|
+
const { inline, children } = withItemAnchor(labelled, blocks, [stepEl.attribs?.id], ctx);
|
|
399
548
|
const step = { type: "step" };
|
|
400
549
|
if (inline)
|
|
401
550
|
step.inline = inline;
|
|
402
|
-
if (
|
|
403
|
-
step.children =
|
|
551
|
+
if (children.length > 0)
|
|
552
|
+
step.children = children;
|
|
404
553
|
return withProvenance(step, stepEl);
|
|
405
554
|
});
|
|
406
555
|
return { type, children };
|
|
407
556
|
};
|
|
557
|
+
/**
|
|
558
|
+
* Publishing: a task with one step renders it as a paragraph and its
|
|
559
|
+
* content, not a one-item list, as DITA-OT's HTML does.
|
|
560
|
+
*/
|
|
561
|
+
const publishSteps = (el, ctx, type) => {
|
|
562
|
+
const steps = convertSteps(el, ctx, type);
|
|
563
|
+
const only = steps.children?.length === 1 ? steps.children[0] : undefined;
|
|
564
|
+
if (!ctx.publish || type !== "steps" || !only || only.type !== "step")
|
|
565
|
+
return [withProvenance(steps, el)];
|
|
566
|
+
return [...(only.inline ? [{ type: "paragraph", inline: only.inline }] : []), ...(only.children ?? [])];
|
|
567
|
+
};
|
|
408
568
|
/**
|
|
409
569
|
* Standalone content of an item-level element (<li>, <step>, …) for
|
|
410
570
|
* conref snippet extraction: leading inline as a paragraph, block
|
|
@@ -426,16 +586,186 @@ const TASK_SECTION_HEADINGS = {
|
|
|
426
586
|
postreq: "What to do next",
|
|
427
587
|
tasktroubleshooting: "Troubleshooting",
|
|
428
588
|
};
|
|
589
|
+
/** Publishing: where a topic's related links go (see publishDitaTemp). */
|
|
590
|
+
export const PUBLISH_RELATED_LINKS_MARKER = "<!-- dogsbay:related-links -->";
|
|
591
|
+
/** Publishing: where a topic's required links go, at the start of its body (see publishDitaTemp). */
|
|
592
|
+
export const PUBLISH_PREREQUISITES_MARKER = /<!-- dogsbay:prerequisites:([^ ]*) -->/g;
|
|
593
|
+
const prerequisitesMarker = (topicId) => `<!-- dogsbay:prerequisites:${topicId} -->`;
|
|
594
|
+
/** DITA-OT's string ids for the task labels. */
|
|
595
|
+
const TASK_LABEL_IDS = {
|
|
596
|
+
prereq: "task_prereq", context: "task_context", steps: "task_procedure",
|
|
597
|
+
"steps-unordered": "task_procedure_unordered", result: "task_results", postreq: "task_postreq",
|
|
598
|
+
};
|
|
599
|
+
const publishTaskLabel = (name, ctx, el) => ctx.publish?.taskLabels && TASK_LABEL_IDS[name]
|
|
600
|
+
? [{ type: "heading", props: { level: ctx.headingLevel }, inline: [{ type: "text", text: publishString(ctx.publish, publishLanguage(el ?? null, ctx.publish), TASK_LABEL_IDS[name]) }] }]
|
|
601
|
+
: [];
|
|
429
602
|
/** Block-level DITA elements (used to split mixed content). */
|
|
430
603
|
const BLOCK_ELEMENTS = new Set([
|
|
431
604
|
"p", "shortdesc", "note", "codeblock", "pre", "screen", "msgblock", "ul", "ol", "sl", "dl", "parml", "table", "simpletable",
|
|
432
605
|
"fig", "image", "section", "example", "steps", "steps-unordered", "substeps", "lq", "object",
|
|
433
|
-
"prereq", "context", "result", "postreq", "tasktroubleshooting", "choices", "choicetable",
|
|
606
|
+
"prereq", "context", "result", "postreq", "tasktroubleshooting", "choices", "choicetable", "syntaxdiagram",
|
|
434
607
|
]);
|
|
608
|
+
const SYNTAX_GROUPS = new Set(["groupseq", "groupchoice", "groupcomp"]);
|
|
609
|
+
/**
|
|
610
|
+
* Publishing: a syntax diagram's inline form, as DITA-OT's HTML writes it
|
|
611
|
+
* (syntax-braces.xsl): keywords bold code, operators code, variables
|
|
612
|
+
* italic, `[ ]` around optional parts, `{ a | b }` for choices.
|
|
613
|
+
*/
|
|
614
|
+
const syntaxInline = (el, ctx) => {
|
|
615
|
+
const out = [];
|
|
616
|
+
const text = (t) => void out.push({ type: "text", text: t });
|
|
617
|
+
for (const child of el.children) {
|
|
618
|
+
if (child.type === "text") {
|
|
619
|
+
text(child.data.replace(/\s+/g, " "));
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (!isElement(child))
|
|
623
|
+
continue;
|
|
624
|
+
const name = blockName(child, ctx) === "syntaxdiagram" ? "syntaxdiagram" : elementName(child, ctx, (n) => SYNTAX_GROUPS.has(n) || ["kwd", "var", "oper", "delim", "sep", "fragref", "repsep", "synnote", "synnoteref", "title"].includes(n));
|
|
625
|
+
const inChoice = elementName(el, ctx, (n) => SYNTAX_GROUPS.has(n)) === "groupchoice";
|
|
626
|
+
const hasPreceding = (child.prev ? el.children.slice(0, el.children.indexOf(child)).some(isElement) : false);
|
|
627
|
+
const optional = child.attribs?.importance === "optional";
|
|
628
|
+
const choiceBar = inChoice && hasPreceding ? " | " : "";
|
|
629
|
+
const value = collapseText(child);
|
|
630
|
+
if (SYNTAX_GROUPS.has(name)) {
|
|
631
|
+
text(`${choiceBar ? " |" : ""}${optional ? " [" : ""}${name === "groupchoice" ? " {" : ""} `);
|
|
632
|
+
out.push(...syntaxInline(child, ctx));
|
|
633
|
+
text(` ${name === "groupchoice" ? "} " : ""}${optional ? "] " : ""}`);
|
|
634
|
+
}
|
|
635
|
+
else if (name === "kwd") {
|
|
636
|
+
text(choiceBar);
|
|
637
|
+
out.push({ type: "code", text: `${optional ? "[" : ""}${value}${optional ? "]" : ""}`, bold: true });
|
|
638
|
+
text(" ");
|
|
639
|
+
}
|
|
640
|
+
else if (name === "var") {
|
|
641
|
+
// DITA-OT writes the choice bar and brackets inside the variable's italics
|
|
642
|
+
text(" ");
|
|
643
|
+
out.push({ type: "text", text: `${choiceBar ? "| " : ""}${optional ? "[" : ""}${value}${optional ? "]" : ""}`, italic: true });
|
|
644
|
+
text(" ");
|
|
645
|
+
}
|
|
646
|
+
else if (name === "oper") {
|
|
647
|
+
text(choiceBar);
|
|
648
|
+
out.push({ type: "code", text: value });
|
|
649
|
+
}
|
|
650
|
+
else if (name === "fragref") {
|
|
651
|
+
// DITA-OT links a fragment reference to the fragment by its title text
|
|
652
|
+
out.push({ type: "link", href: `#${fragmentSlug(value)}`, children: [{ type: "code", text: `<${value}>` }] });
|
|
653
|
+
}
|
|
654
|
+
else if (name === "synnote" && ctx.publish && ctx.footnotes) {
|
|
655
|
+
// A syntax diagram note is a footnote (DITA-OT lists them at the end of the page)
|
|
656
|
+
const label = String(ctx.footnotes.filter((f) => !f.callout).length + 1);
|
|
657
|
+
ctx.footnotes.push({ label, el: child });
|
|
658
|
+
out.push({ type: "footnote-ref", label });
|
|
659
|
+
}
|
|
660
|
+
else if (name === "delim" || name === "sep") {
|
|
661
|
+
text(value);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return out;
|
|
665
|
+
};
|
|
666
|
+
/** The anchor of a syntax diagram fragment, from its title text (what a fragref names). */
|
|
667
|
+
const fragmentSlug = (title) => `fragment-${title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
668
|
+
const collapseText = (el) => textOf(el.children).replace(/\s+/g, " ").trim();
|
|
669
|
+
const convertSyntaxDiagram = (el, ctx) => {
|
|
670
|
+
const out = [];
|
|
671
|
+
const groupName = (c) => elementName(c, ctx, (n) => SYNTAX_GROUPS.has(n) || n === "fragment" || n === "title");
|
|
672
|
+
for (const child of el.children.filter(isElement)) {
|
|
673
|
+
const name = groupName(child);
|
|
674
|
+
if (name === "title") {
|
|
675
|
+
out.push({ type: "heading", props: { level: Math.min(6, ctx.headingLevel + 1) }, inline: trimInline(convertInline(child.children, ctx)) });
|
|
676
|
+
}
|
|
677
|
+
else if (name === "fragment") {
|
|
678
|
+
for (const part of child.children.filter(isElement)) {
|
|
679
|
+
if (groupName(part) === "title") {
|
|
680
|
+
out.push({ type: "heading", props: { level: Math.min(6, ctx.headingLevel + 2), id: fragmentSlug(collapseText(part)) }, inline: trimInline(convertInline(part.children, ctx)) });
|
|
681
|
+
}
|
|
682
|
+
else if (SYNTAX_GROUPS.has(groupName(part))) {
|
|
683
|
+
const inline = trimInline(syntaxInline({ ...child, children: [part] }, ctx));
|
|
684
|
+
if (inline.length)
|
|
685
|
+
out.push({ type: "blockquote", children: [{ type: "paragraph", inline }] });
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else if (SYNTAX_GROUPS.has(name)) {
|
|
690
|
+
const inline = trimInline(syntaxInline({ ...el, children: [child] }, ctx));
|
|
691
|
+
if (inline.length)
|
|
692
|
+
out.push({ type: "paragraph", inline });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return out;
|
|
696
|
+
};
|
|
697
|
+
/** Element names convertBlock handles, for publishing's `@class` fallback. */
|
|
698
|
+
const isKnownBlock = (name) => BLOCK_ELEMENTS.has(name) || TRANSPARENT_CONTAINERS.has(name) || name === "foreign" || name === "unknown" || name === "required-cleanup" ||
|
|
699
|
+
["title", "prolog", "related-links", "draft-comment", "data", "indexterm", "substeps", "sli", "li", "dlentry", "dt", "dd", "syntaxdiagram"].includes(name);
|
|
700
|
+
/** The name to convert a block as (see elementName). */
|
|
701
|
+
const blockName = (el, ctx) => elementName(el, ctx, isKnownBlock);
|
|
435
702
|
/** Whether an element name converts as a standalone block (conref-extractable). */
|
|
436
703
|
export const isBlockTarget = (name) => BLOCK_ELEMENTS.has(name);
|
|
704
|
+
/** Elements whose @id already becomes an anchor (a heading id) or never is a link target. */
|
|
705
|
+
const ANCHORED_ELSEWHERE = new Set(["title", "related-links", "prolog", "data", "indexterm", "draft-comment", "required-cleanup", "foreign", "unknown"]);
|
|
706
|
+
/**
|
|
707
|
+
* Publishing: an element with an @id gets an anchor before its blocks, so
|
|
708
|
+
* cross-references to paragraphs, notes, tables and figures land, as they do on
|
|
709
|
+
* DITA-OT's pages. (Headings carry their own id.)
|
|
710
|
+
*/
|
|
437
711
|
export function convertBlock(el, ctx) {
|
|
438
|
-
const
|
|
712
|
+
const out = convertBlockContent(el, ctx);
|
|
713
|
+
const id = el.attribs?.id;
|
|
714
|
+
if (!ctx.publish || !id || ANCHORED_ELSEWHERE.has(blockName(el, ctx)))
|
|
715
|
+
return out;
|
|
716
|
+
const anchor = anchorId(ctx, id);
|
|
717
|
+
// An empty paragraph or division still keeps its id (DITA-OT writes the empty element)
|
|
718
|
+
if (out.length === 0)
|
|
719
|
+
return KEEPS_EMPTY.has(blockName(el, ctx)) ? [{ type: "html", html: idAnchor(anchor) }] : out;
|
|
720
|
+
// A titled section's heading carries the id
|
|
721
|
+
if (out.some((n) => n.type === "heading" && n.props?.id === anchor))
|
|
722
|
+
return out;
|
|
723
|
+
// Before a paragraph, the anchor is the start of its text (a separate block would be a paragraph of its own)
|
|
724
|
+
if (out[0].type === "paragraph" && out[0].inline) {
|
|
725
|
+
return [{ ...out[0], inline: [{ type: "html-inline", html: idAnchor(anchor) }, ...out[0].inline] }, ...out.slice(1)];
|
|
726
|
+
}
|
|
727
|
+
return [{ type: "html", html: idAnchor(anchor) }, ...out];
|
|
728
|
+
}
|
|
729
|
+
/** Elements DITA-OT's HTML writes, with their id, even when they have no content. */
|
|
730
|
+
const KEEPS_EMPTY = new Set(["p", "div", "bodydiv", "sectiondiv", "section", "example"]);
|
|
731
|
+
/** Publishing: the anchor elements for ids (none outside publishing). */
|
|
732
|
+
const anchorNodes = (ids, ctx) => ctx.publish ? ids.filter((id) => !!id).map((id) => ({ type: "html-inline", html: idAnchor(anchorId(ctx, id)) })) : [];
|
|
733
|
+
/** Inline anchors for an item's @id (terms), first in its text. */
|
|
734
|
+
const withInlineAnchor = (inline, ids, ctx) => {
|
|
735
|
+
const anchors = anchorNodes(ids, ctx);
|
|
736
|
+
return anchors.length ? [...anchors, ...(inline ?? [])] : inline;
|
|
737
|
+
};
|
|
738
|
+
/** Anchors at the start of the first paragraph in blocks (inside a note or quotation too), or undefined when none starts them. */
|
|
739
|
+
const prependToFirstParagraph = (blocks, anchors) => {
|
|
740
|
+
const [first, ...rest] = blocks;
|
|
741
|
+
if (!first)
|
|
742
|
+
return undefined;
|
|
743
|
+
if (first.type === "paragraph" && first.inline)
|
|
744
|
+
return [{ ...first, inline: [...anchors, ...first.inline] }, ...rest];
|
|
745
|
+
if ((first.type === "callout" || first.type === "blockquote") && first.children) {
|
|
746
|
+
const inner = prependToFirstParagraph(first.children, anchors);
|
|
747
|
+
return inner && [{ ...first, children: inner }, ...rest];
|
|
748
|
+
}
|
|
749
|
+
return undefined;
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* Anchors for an item's @id (list items, steps, definitions): first in its text, or, when
|
|
753
|
+
* the item has only blocks, in its first paragraph (an anchor alone would add an empty
|
|
754
|
+
* paragraph to the item).
|
|
755
|
+
*/
|
|
756
|
+
const withItemAnchor = (inline, children, ids, ctx) => {
|
|
757
|
+
const anchors = anchorNodes(ids, ctx);
|
|
758
|
+
if (anchors.length === 0)
|
|
759
|
+
return { inline, children };
|
|
760
|
+
if (!inline?.length) {
|
|
761
|
+
const anchored = prependToFirstParagraph(children, anchors);
|
|
762
|
+
if (anchored)
|
|
763
|
+
return { inline, children: anchored };
|
|
764
|
+
}
|
|
765
|
+
return { inline: [...anchors, ...(inline ?? [])], children };
|
|
766
|
+
};
|
|
767
|
+
function convertBlockContent(el, ctx) {
|
|
768
|
+
const name = blockName(el, ctx);
|
|
439
769
|
noteFilterAttrs(el, ctx);
|
|
440
770
|
// Defining site: this element was extracted into a reuse snippet — emit
|
|
441
771
|
// the include here too, so exactly ONE copy of the text survives
|
|
@@ -487,8 +817,16 @@ export function convertBlock(el, ctx) {
|
|
|
487
817
|
}
|
|
488
818
|
}
|
|
489
819
|
if (TRANSPARENT_CONTAINERS.has(name)) {
|
|
820
|
+
// Publishing: a topic body starts with the topic's "Prerequisites" links, when it has any
|
|
821
|
+
const topicId = el.parent?.attribs?.id;
|
|
822
|
+
if (ctx.publish && name.endsWith("body") && topicId && !name.includes("div")) {
|
|
823
|
+
return [{ type: "html", html: prerequisitesMarker(topicId) }, ...convertChildren(el, ctx)];
|
|
824
|
+
}
|
|
490
825
|
return convertChildren(el, ctx);
|
|
491
826
|
}
|
|
827
|
+
// Publishing: DITA-OT's HTML shows none of these (MathML needs a plugin; cleanup needs args.draft)
|
|
828
|
+
if (ctx.publish && (name === "foreign" || name === "unknown" || name === "required-cleanup"))
|
|
829
|
+
return [];
|
|
492
830
|
switch (name) {
|
|
493
831
|
case "p":
|
|
494
832
|
case "shortdesc": {
|
|
@@ -504,7 +842,11 @@ export function convertBlock(el, ctx) {
|
|
|
504
842
|
return out;
|
|
505
843
|
}
|
|
506
844
|
case "note": {
|
|
507
|
-
const
|
|
845
|
+
const authored = el.attribs?.type ?? "note";
|
|
846
|
+
// Publishing keeps every type dogsbay-md renders (caution, danger)
|
|
847
|
+
const type = ctx.publish && PUBLISH_CALLOUT_TYPES.has(authored)
|
|
848
|
+
? authored
|
|
849
|
+
: NOTE_TYPE_MAP[authored] ?? (ctx.publish ? PUBLISH_NOTE_TYPE_MAP[authored] : undefined) ?? "note";
|
|
508
850
|
if (el.attribs?.type && !NOTE_TYPE_MAP[el.attribs.type]) {
|
|
509
851
|
ctx.note("@type", `<note type="${el.attribs.type}"> mapped to plain note callout`);
|
|
510
852
|
}
|
|
@@ -521,7 +863,12 @@ export function convertBlock(el, ctx) {
|
|
|
521
863
|
case "pre":
|
|
522
864
|
case "screen":
|
|
523
865
|
case "msgblock": {
|
|
524
|
-
const
|
|
866
|
+
const outputclass = el.attribs?.outputclass ?? "";
|
|
867
|
+
// Publishing: DITA-OT's language classes (language-xml, syntax-bash)
|
|
868
|
+
// name the language; other output classes are not languages
|
|
869
|
+
const language = ctx.publish
|
|
870
|
+
? (/(?:^|\s)(?:language|syntax)-(\S+)/.exec(outputclass)?.[1] ?? "")
|
|
871
|
+
: outputclass;
|
|
525
872
|
const content = textOf(el.children).replace(/^\n/, "").replace(/\s+$/, "");
|
|
526
873
|
// Content in BOTH html and props.code: the serializer's block path
|
|
527
874
|
// reads html, its HTML-table path (code inside cells) reads props.code
|
|
@@ -558,14 +905,35 @@ export function convertBlock(el, ctx) {
|
|
|
558
905
|
return [withProvenance(convertDefList(el, ctx, { entry: "plentry", term: "pt", def: "pd" }), el)];
|
|
559
906
|
case "table":
|
|
560
907
|
case "simpletable":
|
|
908
|
+
// Publishing: DITA-OT drops a properties table none of whose properties has a cell
|
|
909
|
+
if (ctx.publish && hasClassToken(el, "reference/properties") && propertiesColumns(el).propertyRows.length === 0)
|
|
910
|
+
return [];
|
|
561
911
|
return [withProvenance(convertTable(el, ctx), el)];
|
|
912
|
+
case "syntaxdiagram":
|
|
913
|
+
if (ctx.publish)
|
|
914
|
+
return convertSyntaxDiagram(el, ctx);
|
|
915
|
+
// Migration: as any unrecognized block
|
|
916
|
+
{
|
|
917
|
+
const children = convertChildren(el, ctx);
|
|
918
|
+
ctx.note(`<${name}>`, children.length > 0
|
|
919
|
+
? "unrecognized block element — children kept, wrapper dropped"
|
|
920
|
+
: "unrecognized block element dropped");
|
|
921
|
+
return children;
|
|
922
|
+
}
|
|
562
923
|
case "fig": {
|
|
563
924
|
const titleEl = el.children
|
|
564
925
|
.filter(isElement)
|
|
565
|
-
.find((c) => c
|
|
926
|
+
.find((c) => blockName(c, ctx) === "title");
|
|
566
927
|
const imageEl = el.children
|
|
567
928
|
.filter(isElement)
|
|
568
929
|
.find((c) => c.name.toLowerCase() === "image");
|
|
930
|
+
// Publishing: every figure converts its content (an image with its size, and anything else in it)
|
|
931
|
+
if (ctx.publish) {
|
|
932
|
+
const body = convertChildren(el, ctx, new Set(["title"]));
|
|
933
|
+
return titleEl
|
|
934
|
+
? [withProvenance({ type: "figure", props: { caption: captionText(titleEl, ctx, el) }, children: body }, el)]
|
|
935
|
+
: body;
|
|
936
|
+
}
|
|
569
937
|
if (imageEl) {
|
|
570
938
|
const figure = {
|
|
571
939
|
type: "figure",
|
|
@@ -581,7 +949,7 @@ export function convertBlock(el, ctx) {
|
|
|
581
949
|
},
|
|
582
950
|
],
|
|
583
951
|
...(titleEl
|
|
584
|
-
? { props: { caption:
|
|
952
|
+
? { props: { caption: captionText(titleEl, ctx, el) } }
|
|
585
953
|
: {}),
|
|
586
954
|
};
|
|
587
955
|
return [withProvenance(figure, el)];
|
|
@@ -609,6 +977,10 @@ export function convertBlock(el, ctx) {
|
|
|
609
977
|
}
|
|
610
978
|
return [];
|
|
611
979
|
}
|
|
980
|
+
if (ctx.publish) {
|
|
981
|
+
const { alt, attrs } = publishImage(el, ctx.publish);
|
|
982
|
+
return [withProvenance({ type: "image", props: { src, alt: alt ?? "", ...(attrs ?? {}) } }, el)];
|
|
983
|
+
}
|
|
612
984
|
const node = {
|
|
613
985
|
type: "image",
|
|
614
986
|
props: {
|
|
@@ -623,7 +995,7 @@ export function convertBlock(el, ctx) {
|
|
|
623
995
|
const out = [];
|
|
624
996
|
const titleEl = el.children
|
|
625
997
|
.filter(isElement)
|
|
626
|
-
.find((c) => c
|
|
998
|
+
.find((c) => blockName(c, ctx) === "title");
|
|
627
999
|
if (titleEl) {
|
|
628
1000
|
// <title conref=…/> — heading text pulled from elsewhere
|
|
629
1001
|
const titleConref = constrainedConrefInline(titleEl, ctx);
|
|
@@ -633,16 +1005,17 @@ export function convertBlock(el, ctx) {
|
|
|
633
1005
|
// cross-topic xref fragments (renamed to #elementid) must land
|
|
634
1006
|
props: {
|
|
635
1007
|
level: ctx.headingLevel,
|
|
636
|
-
...(el.attribs?.id ? { id: el.attribs.id } : {}),
|
|
1008
|
+
...(el.attribs?.id ? { id: anchorId(ctx, el.attribs.id) } : {}),
|
|
637
1009
|
},
|
|
638
1010
|
inline: titleConref ?? trimInline(convertInline(titleEl.children, ctx)),
|
|
639
1011
|
}, el));
|
|
640
1012
|
}
|
|
641
|
-
else if (name === "example") {
|
|
1013
|
+
else if (name === "example" && (!ctx.publish || (ctx.publish.taskLabels && el.parent && isElement(el.parent) && el.parent.name.toLowerCase() === "taskbody"))) {
|
|
1014
|
+
// Publishing: DITA-OT labels only an untitled task example, and only with task labels on
|
|
642
1015
|
out.push({
|
|
643
1016
|
type: "heading",
|
|
644
1017
|
props: { level: ctx.headingLevel },
|
|
645
|
-
inline: [{ type: "text", text: "Example" }],
|
|
1018
|
+
inline: [{ type: "text", text: ctx.publish ? publishString(ctx.publish, publishLanguage(el, ctx.publish), "task_example") : "Example" }],
|
|
646
1019
|
});
|
|
647
1020
|
}
|
|
648
1021
|
const inner = { ...ctx, headingLevel: Math.min(6, ctx.headingLevel + 1) };
|
|
@@ -651,15 +1024,24 @@ export function convertBlock(el, ctx) {
|
|
|
651
1024
|
}
|
|
652
1025
|
case "steps":
|
|
653
1026
|
case "steps-unordered":
|
|
654
|
-
|
|
1027
|
+
// Publishing: unordered steps are a bulleted list, as DITA-OT renders them
|
|
1028
|
+
if (ctx.publish && name === "steps-unordered") {
|
|
1029
|
+
const steps = convertSteps(el, ctx, "steps");
|
|
1030
|
+
const items = (steps.children ?? []).map((step) => ({ ...step, type: "list-item" }));
|
|
1031
|
+
return [...publishTaskLabel(name, ctx, el), withProvenance({ type: "unordered-list", children: items }, el)];
|
|
1032
|
+
}
|
|
1033
|
+
return [...publishTaskLabel(name, ctx, el), ...publishSteps(el, ctx, "steps")];
|
|
655
1034
|
case "substeps":
|
|
656
1035
|
return [withProvenance(convertSteps(el, ctx, "substeps"), el)];
|
|
657
1036
|
case "context":
|
|
658
|
-
return convertChildren(el, ctx);
|
|
1037
|
+
return [...publishTaskLabel(name, ctx, el), ...convertChildren(el, ctx)];
|
|
659
1038
|
case "prereq":
|
|
660
1039
|
case "result":
|
|
661
1040
|
case "postreq":
|
|
662
1041
|
case "tasktroubleshooting": {
|
|
1042
|
+
// Publishing: DITA-OT's HTML titles these sections only with task labels on
|
|
1043
|
+
if (ctx.publish)
|
|
1044
|
+
return [...publishTaskLabel(name, ctx, el), ...convertChildren(el, ctx)];
|
|
663
1045
|
const heading = {
|
|
664
1046
|
type: "heading",
|
|
665
1047
|
props: { level: ctx.headingLevel },
|
|
@@ -678,13 +1060,28 @@ export function convertBlock(el, ctx) {
|
|
|
678
1060
|
if (inline)
|
|
679
1061
|
body.push({ type: "paragraph", inline });
|
|
680
1062
|
body.push(...children);
|
|
1063
|
+
// Publishing: DITA-OT cites the quote's source (@reftitle, linked to @href)
|
|
1064
|
+
const cite = el.attribs?.reftitle ?? el.attribs?.href;
|
|
1065
|
+
if (ctx.publish && cite) {
|
|
1066
|
+
const text = { type: "text", text: cite, italic: true };
|
|
1067
|
+
// A link to a DITA topic goes to its page, as an xref does
|
|
1068
|
+
const href = el.attribs?.href;
|
|
1069
|
+
const external = !!href && (el.attribs?.scope === "external" || /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(href));
|
|
1070
|
+
const target = href && !external && /\.(dita|xml)(#|$)/i.test(href) ? publishHref(ctx.publish, href) : href;
|
|
1071
|
+
body.push({ type: "paragraph", inline: [target ? { type: "link", href: target, children: [text] } : text] });
|
|
1072
|
+
}
|
|
681
1073
|
return [{ type: "blockquote", children: body }];
|
|
682
1074
|
}
|
|
683
1075
|
case "title":
|
|
684
1076
|
// Titles are handled by their parents (topic/section/fig/table)
|
|
685
1077
|
return [];
|
|
686
|
-
case "prolog":
|
|
687
1078
|
case "related-links":
|
|
1079
|
+
// Publishing: a placeholder where DITA-OT renders the topic's related
|
|
1080
|
+
// links (after its body, before nested topics); publishDitaTemp fills it
|
|
1081
|
+
if (ctx.publish)
|
|
1082
|
+
return [{ type: "html", html: PUBLISH_RELATED_LINKS_MARKER }];
|
|
1083
|
+
// falls through
|
|
1084
|
+
case "prolog":
|
|
688
1085
|
ctx.note(`<${name}>`, "metadata/related-links dropped — links belong in the topic map (reltable design pending)");
|
|
689
1086
|
return [];
|
|
690
1087
|
case "draft-comment":
|
|
@@ -712,10 +1109,10 @@ export function convertChildren(el, ctx, skip = new Set()) {
|
|
|
712
1109
|
};
|
|
713
1110
|
for (const child of el.children) {
|
|
714
1111
|
if (isElement(child)) {
|
|
715
|
-
const name = child
|
|
1112
|
+
const name = blockName(child, ctx);
|
|
716
1113
|
if (skip.has(name))
|
|
717
1114
|
continue;
|
|
718
|
-
if (
|
|
1115
|
+
if (isBlockChild(child, ctx) || TRANSPARENT_CONTAINERS.has(name) || name === "title" ||
|
|
719
1116
|
name === "prolog" || name === "related-links" || name === "draft-comment") {
|
|
720
1117
|
flushInline();
|
|
721
1118
|
out.push(...convertBlock(child, ctx));
|
|
@@ -724,7 +1121,7 @@ export function convertChildren(el, ctx, skip = new Set()) {
|
|
|
724
1121
|
// Inline element loose in a body: accumulate into a paragraph
|
|
725
1122
|
inlineRun.push(child);
|
|
726
1123
|
}
|
|
727
|
-
else if (isNonEmptyText(child)) {
|
|
1124
|
+
else if (isNonEmptyText(child) || (child.type === "text" && inlineRun.length > 0)) {
|
|
728
1125
|
inlineRun.push(child);
|
|
729
1126
|
}
|
|
730
1127
|
}
|