@docx4j/core-ts 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +4 -0
- package/README.md +34 -3
- package/dist/html/convert.mjs +40 -15
- package/dist/html/elements.mjs +2 -1
- package/dist/model/color/ColorMath.d.mts +29 -0
- package/dist/model/color/ColorMath.mjs +146 -0
- package/dist/model/color/ColorTables.d.mts +6 -0
- package/dist/model/color/ColorTables.mjs +83 -0
- package/dist/model/color/DrawingMLColors.d.mts +52 -0
- package/dist/model/color/DrawingMLColors.mjs +374 -0
- package/dist/model/color/ResolvedColor.d.mts +36 -0
- package/dist/model/color/ResolvedColor.mjs +65 -0
- package/dist/model/color/SpreadsheetColors.d.mts +36 -0
- package/dist/model/color/SpreadsheetColors.mjs +84 -0
- package/dist/model/color/StyleFill.d.mts +28 -0
- package/dist/model/color/StyleFill.mjs +21 -0
- package/dist/model/color/ThemeColors.d.mts +86 -0
- package/dist/model/color/ThemeColors.mjs +304 -0
- package/dist/model/color/WordColors.d.mts +71 -0
- package/dist/model/color/WordColors.mjs +121 -0
- package/dist/model/color/index.d.mts +6 -0
- package/dist/model/color/index.mjs +30 -0
- package/dist/model/content/Body.d.mts +153 -19
- package/dist/model/content/Body.mjs +570 -131
- package/dist/model/content/Comment.d.mts +21 -3
- package/dist/model/content/Comment.mjs +202 -19
- package/dist/model/content/ContentControl.d.mts +197 -18
- package/dist/model/content/ContentControl.mjs +664 -95
- package/dist/model/content/Font.d.mts +67 -17
- package/dist/model/content/Font.mjs +114 -35
- package/dist/model/content/InlinePicture.d.mts +82 -7
- package/dist/model/content/InlinePicture.mjs +221 -28
- package/dist/model/content/List.mjs +13 -1
- package/dist/model/content/Paragraph.d.mts +213 -14
- package/dist/model/content/Paragraph.mjs +936 -100
- package/dist/model/content/Range.d.mts +242 -11
- package/dist/model/content/Range.mjs +975 -119
- package/dist/model/content/ResolvedView.d.mts +104 -0
- package/dist/model/content/ResolvedView.mjs +596 -0
- package/dist/model/content/Table.d.mts +29 -3
- package/dist/model/content/Table.mjs +109 -33
- package/dist/model/content/TrackedChange.d.mts +301 -16
- package/dist/model/content/TrackedChange.mjs +1284 -158
- package/dist/model/content/binder.d.mts +2 -1
- package/dist/model/content/binder.mjs +48 -1
- package/dist/model/content/comments.d.mts +20 -6
- package/dist/model/content/comments.mjs +67 -57
- package/dist/model/content/generalException.d.mts +18 -0
- package/dist/model/content/generalException.mjs +24 -0
- package/dist/model/content/index.d.mts +8 -3
- package/dist/model/content/index.mjs +7 -2
- package/dist/model/content/mathText.d.mts +21 -0
- package/dist/model/content/mathText.mjs +267 -0
- package/dist/model/content/officeText.d.mts +27 -0
- package/dist/model/content/officeText.mjs +74 -0
- package/dist/model/content/outline.d.mts +5 -0
- package/dist/model/content/outline.mjs +13 -0
- package/dist/model/content/placeholders.d.mts +110 -0
- package/dist/model/content/placeholders.mjs +253 -0
- package/dist/model/content/relationships.d.mts +30 -0
- package/dist/model/content/relationships.mjs +133 -0
- package/dist/model/content/search.d.mts +72 -0
- package/dist/model/content/search.mjs +204 -2
- package/dist/model/content/spanEdits.d.mts +159 -0
- package/dist/model/content/spanEdits.mjs +728 -0
- package/dist/model/content/story.d.mts +44 -0
- package/dist/model/content/story.mjs +92 -0
- package/dist/model/content/tableColumns.d.mts +1 -1
- package/dist/model/content/tableColumns.mjs +108 -80
- package/dist/model/content/tableRevisions.d.mts +11 -3
- package/dist/model/content/tableRevisions.mjs +72 -42
- package/dist/model/content/tableRules.d.mts +30 -0
- package/dist/model/content/tableRules.mjs +100 -0
- package/dist/model/content/textBoxes.d.mts +18 -0
- package/dist/model/content/textBoxes.mjs +105 -0
- package/dist/model/content/trackedChangeFilter.d.mts +39 -0
- package/dist/model/content/trackedChangeFilter.mjs +81 -0
- package/dist/model/content/tracking.d.mts +28 -0
- package/dist/model/content/tracking.mjs +149 -10
- package/dist/model/content/tree.d.mts +212 -13
- package/dist/model/content/tree.mjs +356 -35
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +8 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +34 -11
- package/dist/model/customxml/XmlMapping.d.mts +1 -1
- package/dist/model/customxml/XmlMapping.mjs +11 -0
- package/dist/model/customxml/bindings.d.mts +21 -2
- package/dist/model/customxml/bindings.mjs +76 -32
- package/dist/model/customxml/kinds.d.mts +8 -3
- package/dist/model/customxml/kinds.mjs +26 -5
- package/dist/model/customxml/picturePlaceholder.d.mts +17 -0
- package/dist/model/customxml/picturePlaceholder.mjs +32 -0
- package/dist/model/customxml/pictures.d.mts +9 -5
- package/dist/model/customxml/pictures.mjs +31 -13
- package/dist/model/customxml/xhtml.mjs +8 -2
- package/dist/model/fonts/families.generated.mjs +2 -2
- package/dist/model/fonts/registry.mjs +7 -1
- package/dist/model/fonts/substitutions.generated.mjs +2 -2
- package/dist/model/fonts/themes.generated.mjs +1 -1
- package/dist/model/index.d.mts +2 -0
- package/dist/model/index.mjs +4 -0
- package/dist/model/listnumbering/Emulator.d.mts +37 -5
- package/dist/model/listnumbering/Emulator.mjs +63 -9
- package/dist/model/listnumbering/definitions.mjs +18 -0
- package/dist/model/properties/PropertyResolver.d.mts +87 -7
- package/dist/model/properties/PropertyResolver.mjs +197 -17
- package/dist/model/properties/catalogue.d.mts +32 -4
- package/dist/model/properties/catalogue.mjs +115 -36
- package/dist/model/properties/log.d.mts +1 -1
- package/dist/model/properties/log.mjs +5 -5
- package/dist/model/properties/table.d.mts +16 -2
- package/dist/model/properties/table.mjs +17 -5
- package/dist/model/text/Extractor.d.mts +289 -0
- package/dist/model/text/Extractor.mjs +2162 -0
- package/dist/model/text/LinkTargets.d.mts +45 -0
- package/dist/model/text/LinkTargets.mjs +170 -0
- package/dist/model/text/MarkdownExporter.d.mts +140 -0
- package/dist/model/text/MarkdownExporter.mjs +255 -0
- package/dist/model/text/PlainText.d.mts +29 -0
- package/dist/model/text/PlainText.mjs +539 -0
- package/dist/model/text/TextContext.d.mts +161 -0
- package/dist/model/text/TextContext.mjs +425 -0
- package/dist/model/text/TextOptions.d.mts +150 -0
- package/dist/model/text/TextOptions.mjs +278 -0
- package/dist/model/text/TextResult.d.mts +504 -0
- package/dist/model/text/TextResult.mjs +526 -0
- package/dist/model/text/Texts.d.mts +99 -0
- package/dist/model/text/Texts.mjs +187 -0
- package/dist/model/text/WmlToMarkdown.d.mts +120 -0
- package/dist/model/text/WmlToMarkdown.mjs +911 -0
- package/dist/model/text/commonmark.d.mts +121 -0
- package/dist/model/text/commonmark.mjs +655 -0
- package/dist/model/text/index.d.mts +7 -0
- package/dist/model/text/index.mjs +8 -0
- package/dist/model/text/normalize.d.mts +8 -0
- package/dist/model/text/normalize.mjs +13 -0
- package/dist/model/text/outline.d.mts +4 -0
- package/dist/model/text/outline.mjs +71 -0
- package/dist/office-js/fragment.d.mts +7 -5
- package/dist/office-js/fragment.mjs +63 -14
- package/dist/office-js/proxy.d.mts +8 -0
- package/dist/office-js/proxy.mjs +29 -0
- package/dist/office-js/run.mjs +2 -0
- package/dist/office-js/supported.generated.mjs +16 -0
- package/dist/office-js/toApiScript.mjs +21 -5
- package/dist/opc/Load.d.mts +9 -2
- package/dist/opc/Save.d.mts +17 -0
- package/dist/opc/Save.mjs +12 -2
- package/dist/opc/index.d.mts +3 -3
- package/dist/opc/index.mjs +2 -2
- package/dist/opc/mce/preprocessor.d.mts +88 -7
- package/dist/opc/mce/preprocessor.mjs +144 -37
- package/dist/opc/mce/understood.d.mts +34 -0
- package/dist/opc/mce/understood.mjs +55 -3
- package/dist/packages/OpcPackage.d.mts +20 -0
- package/dist/packages/OpcPackage.mjs +16 -3
- package/dist/packages/WordprocessingMLPackage.d.mts +77 -7
- package/dist/packages/WordprocessingMLPackage.mjs +242 -11
- package/dist/parts/DefaultXmlPart.mjs +2 -2
- package/dist/parts/Part.d.mts +10 -2
- package/dist/parts/XmlPart.d.mts +35 -32
- package/dist/parts/XmlPart.mjs +107 -107
- package/dist/parts/wml/comments.mjs +30 -0
- package/dist/parts/wml/index.mjs +12 -0
- package/dist/xml/dom.d.mts +7 -0
- package/dist/xml/dom.mjs +18 -0
- package/package.json +2 -2
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
import { log } from '../properties/log.mjs';
|
|
2
|
+
import { LinkTargets } from './LinkTargets.mjs';
|
|
3
|
+
import { MdDocument, MdParagraph, MdHeading, MdBlockQuote, MdBulletList, MdOrderedList, MdListItem, MdFencedCodeBlock, MdThematicBreak, MdHtmlInline, MdText, MdCode, MdEmphasis, MdStrongEmphasis, MdLink, MdImage, MdHardLineBreak, MdTableBlock, MdTableHead, MdTableBody, MdTableRow, MdTableCell, MdStrikethrough, MdFootnoteDefinition, MdFootnoteReference, MdInlineMath, MdDisplayMath, } from './commonmark.mjs';
|
|
4
|
+
const TWIPS_PER_LEVEL = 720;
|
|
5
|
+
/** The paragraph styles docx4j's importer writes, which the export reads back (docx4j `ImportStyles`). */
|
|
6
|
+
const QUOTE = 'Quote';
|
|
7
|
+
const INTENSE_QUOTE = 'IntenseQuote';
|
|
8
|
+
const LIST_PARAGRAPH = 'ListParagraph';
|
|
9
|
+
const SOURCE_CODE = 'SourceCode';
|
|
10
|
+
/** Paragraph styles which are code blocks by name, whatever their font. */
|
|
11
|
+
const CODE_STYLES = new Set([SOURCE_CODE, 'Code', 'HTMLPreformatted', 'MacroText']);
|
|
12
|
+
/**
|
|
13
|
+
* Writes the text model's block model (`TextResult`) as a Markdown tree (WD REQ-703): a heading from its
|
|
14
|
+
* outline level, not its style's name; a list from its numbering; bold, italic, struck-through and code text
|
|
15
|
+
* from what a run adds to its paragraph's style (so a heading's inherent bold doesn't become `**markers**`);
|
|
16
|
+
* links from a `w:hyperlink` or a HYPERLINK field; images with the describer's text; notes as `[^n]`; in the
|
|
17
|
+
* `markup` view, CriticMarkup (`{++ ++}`, `{-- --}`, `{>> <<}`). A table of contents is the text model's to
|
|
18
|
+
* leave out (`toc: false`, the default). Each node made for a block carries it as its `tag`, so that
|
|
19
|
+
* `MarkdownExporter.blocks` can say where each block's Markdown begins. docx4j `WmlToMarkdown`.
|
|
20
|
+
*/
|
|
21
|
+
export class WmlToMarkdown {
|
|
22
|
+
constructor(result,
|
|
23
|
+
/** For a style's own indent (a block quote's depth); undefined where there are no styles to ask. */
|
|
24
|
+
resolver,
|
|
25
|
+
/** The schemes a link may use beyond `LinkTargets`' defaults (`MarkdownExportOptions.linkSchemes`). */
|
|
26
|
+
linkSchemes = []) {
|
|
27
|
+
this.result = result;
|
|
28
|
+
this.resolver = resolver;
|
|
29
|
+
this.linkSchemes = linkSchemes;
|
|
30
|
+
/** The open lists, the deepest last. */
|
|
31
|
+
this.openLists = [];
|
|
32
|
+
/** The count each list level last showed (`numId/ilvl`), for a parent item reopened after an interruption. */
|
|
33
|
+
this.lastValues = new Map();
|
|
34
|
+
this.openQuoteDepth = 0;
|
|
35
|
+
/** Above 0 while building a GFM header row, whose bold is convention, not markup. */
|
|
36
|
+
this.tableHeaderDepth = 0;
|
|
37
|
+
this.styleIndents = new Map();
|
|
38
|
+
/** The images, in the order written, for their destinations. */
|
|
39
|
+
this.images = [];
|
|
40
|
+
/** Whether a link target was refused (`LinkTargets`): logged once per document. */
|
|
41
|
+
this.linkRefused = false;
|
|
42
|
+
this.textOptions = result.options;
|
|
43
|
+
}
|
|
44
|
+
convert() {
|
|
45
|
+
const document = new MdDocument();
|
|
46
|
+
const blocks = this.result.blocks;
|
|
47
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
48
|
+
const block = blocks[i];
|
|
49
|
+
if (isNote(block)) {
|
|
50
|
+
// a note's blocks are its definition, at the place the text model reads it (after the main story)
|
|
51
|
+
this.closeLists();
|
|
52
|
+
this.closeQuote();
|
|
53
|
+
let last = i;
|
|
54
|
+
while (last + 1 < blocks.length && sameNote(block, blocks[last + 1]))
|
|
55
|
+
last++;
|
|
56
|
+
const definition = tagged(new MdFootnoteDefinition(noteLabel(block.noteType, block.noteNumber, block.noteId)), block);
|
|
57
|
+
this.body(blocks.slice(i, last + 1), definition);
|
|
58
|
+
this.closeLists();
|
|
59
|
+
this.closeQuote();
|
|
60
|
+
trimLeadingMarkerSpace(definition);
|
|
61
|
+
document.appendChild(definition);
|
|
62
|
+
i = last;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
i = this.block(blocks, i, document);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.closeLists();
|
|
69
|
+
this.closeQuote();
|
|
70
|
+
return document;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------- blocks
|
|
73
|
+
body(blocks, container) {
|
|
74
|
+
for (let i = 0; i < blocks.length; i++)
|
|
75
|
+
i = this.block(blocks, i, container);
|
|
76
|
+
}
|
|
77
|
+
/** Returns the index of the last block consumed (a code block spans several). */
|
|
78
|
+
block(blocks, index, container) {
|
|
79
|
+
const block = blocks[index];
|
|
80
|
+
switch (block.kind) {
|
|
81
|
+
case 'TABLE':
|
|
82
|
+
this.closeLists();
|
|
83
|
+
this.closeQuote();
|
|
84
|
+
this.table(block, container);
|
|
85
|
+
return index;
|
|
86
|
+
case 'CHART':
|
|
87
|
+
this.closeLists();
|
|
88
|
+
this.closeQuote();
|
|
89
|
+
this.chart(block, container);
|
|
90
|
+
return index;
|
|
91
|
+
default:
|
|
92
|
+
return this.paragraph(blocks, index, container);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
paragraph(blocks, index, container) {
|
|
96
|
+
const block = blocks[index];
|
|
97
|
+
const style = block.style;
|
|
98
|
+
// a thematic break: an empty paragraph carrying a bottom border of its own
|
|
99
|
+
if (block.bottomBorder && isEmpty(block)) {
|
|
100
|
+
this.closeLists();
|
|
101
|
+
this.closeQuote();
|
|
102
|
+
container.appendChild(tagged(new MdThematicBreak('---'), block)); // else the renderer picks its own idiom
|
|
103
|
+
return index;
|
|
104
|
+
}
|
|
105
|
+
// display math: a paragraph which is one m:oMathPara
|
|
106
|
+
const displayMath = soleDisplayMath(block);
|
|
107
|
+
if (displayMath !== undefined) {
|
|
108
|
+
this.closeLists();
|
|
109
|
+
this.closeQuote();
|
|
110
|
+
container.appendChild(tagged(new MdDisplayMath(latex(displayMath)), block));
|
|
111
|
+
return index;
|
|
112
|
+
}
|
|
113
|
+
// a code block: a code-styled paragraph, or one whose paragraph style's own font is mono; consecutive ones
|
|
114
|
+
// merged into one fence, one line each
|
|
115
|
+
if (isCodeParagraph(block)) {
|
|
116
|
+
this.closeLists();
|
|
117
|
+
this.closeQuote();
|
|
118
|
+
let literal = '';
|
|
119
|
+
let last = index;
|
|
120
|
+
for (let j = index; j < blocks.length; j++) {
|
|
121
|
+
const b = blocks[j];
|
|
122
|
+
if (b.kind !== 'PARAGRAPH' || !isCodeParagraph(b) || !sameStory(block, b))
|
|
123
|
+
break;
|
|
124
|
+
literal += `${this.codeText(b)}\n`;
|
|
125
|
+
last = j;
|
|
126
|
+
}
|
|
127
|
+
container.appendChild(tagged(new MdFencedCodeBlock(literal), block));
|
|
128
|
+
return last;
|
|
129
|
+
}
|
|
130
|
+
// a heading by its effective outlineLvl (0 to 5 are # to ######); before the numbering, since the built-in
|
|
131
|
+
// Heading styles can carry a legacy numPr
|
|
132
|
+
const outlineLevel = block.outlineLevel;
|
|
133
|
+
if (outlineLevel !== undefined && outlineLevel <= 5) {
|
|
134
|
+
this.closeLists();
|
|
135
|
+
this.closeQuote();
|
|
136
|
+
const heading = tagged(new MdHeading(outlineLevel + 1), block);
|
|
137
|
+
this.inlines(block, heading, true);
|
|
138
|
+
container.appendChild(heading);
|
|
139
|
+
return index;
|
|
140
|
+
}
|
|
141
|
+
// a list paragraph
|
|
142
|
+
const numbering = block.numbering;
|
|
143
|
+
if (numbering !== undefined) {
|
|
144
|
+
this.closeQuote();
|
|
145
|
+
if (numbering.label) {
|
|
146
|
+
// a level with numFmt none, or a lvlText with no %n, is a label ("NOTE", "EXAMPLE"), not a list: it
|
|
147
|
+
// leads the paragraph in bold
|
|
148
|
+
this.closeLists();
|
|
149
|
+
const label = numbering.levelText === undefined ? '' : numbering.levelText.trim();
|
|
150
|
+
const labelled = this.inlineParagraph(block, false);
|
|
151
|
+
if (label.length > 0) {
|
|
152
|
+
if (labelled.firstChild !== undefined)
|
|
153
|
+
labelled.prependChild(new MdText(' '));
|
|
154
|
+
const strong = new MdStrongEmphasis();
|
|
155
|
+
strong.appendChild(new MdText(label));
|
|
156
|
+
labelled.prependChild(strong);
|
|
157
|
+
}
|
|
158
|
+
if (labelled.firstChild !== undefined)
|
|
159
|
+
container.appendChild(labelled);
|
|
160
|
+
return index;
|
|
161
|
+
}
|
|
162
|
+
this.listParagraph(block, numbering, container);
|
|
163
|
+
return index;
|
|
164
|
+
}
|
|
165
|
+
// an unlabelled paragraph inside a list (WD REQ-703): Markdown has no empty paragraphs, so an empty one
|
|
166
|
+
// neither ends the list nor joins it; one indented at least to an open item's text (or in the
|
|
167
|
+
// ListParagraph style) is a follow-on paragraph of the deepest such item; any other ends the lists, and
|
|
168
|
+
// the list goes on at its own count where it resumes (listParagraph)
|
|
169
|
+
if (this.openLists.length > 0 && style !== QUOTE && style !== INTENSE_QUOTE) {
|
|
170
|
+
if (isEmpty(block))
|
|
171
|
+
return index;
|
|
172
|
+
const into = this.followOnItem(block, style);
|
|
173
|
+
if (into !== undefined) {
|
|
174
|
+
into.node.tight = false;
|
|
175
|
+
into.lastItem.appendChild(this.inlineParagraph(block, true));
|
|
176
|
+
return index;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.closeLists();
|
|
180
|
+
// a block quote, nested by the indent beyond the quote style's own
|
|
181
|
+
if (style === QUOTE || style === INTENSE_QUOTE) {
|
|
182
|
+
const beyond = value(block.indentLeft) - this.styleIndentLeft(style);
|
|
183
|
+
const depth = 1 + Math.trunc(Math.max(0, beyond) / TWIPS_PER_LEVEL);
|
|
184
|
+
this.appendToQuote(depth, this.inlineParagraph(block, true), container, block);
|
|
185
|
+
return index;
|
|
186
|
+
}
|
|
187
|
+
this.closeQuote();
|
|
188
|
+
const paragraph = this.inlineParagraph(block, true);
|
|
189
|
+
if (paragraph.firstChild !== undefined)
|
|
190
|
+
container.appendChild(paragraph); // Markdown has no empty paragraphs
|
|
191
|
+
return index;
|
|
192
|
+
}
|
|
193
|
+
inlineParagraph(block, withListLabel) {
|
|
194
|
+
const paragraph = tagged(new MdParagraph(), block);
|
|
195
|
+
this.inlines(block, paragraph, withListLabel);
|
|
196
|
+
return paragraph;
|
|
197
|
+
}
|
|
198
|
+
/** A code paragraph's text, unescaped; in `markup`, its revisions marked. */
|
|
199
|
+
codeText(block) {
|
|
200
|
+
let out = '';
|
|
201
|
+
const runs = block.runs;
|
|
202
|
+
let i = 0;
|
|
203
|
+
while (i < runs.length) {
|
|
204
|
+
const revision = runs[i].revision;
|
|
205
|
+
let j = i;
|
|
206
|
+
while (j < runs.length && this.sameMarker(revision, runs[j].revision))
|
|
207
|
+
j++;
|
|
208
|
+
if (revision !== undefined)
|
|
209
|
+
out += this.open(revision);
|
|
210
|
+
for (let k = i; k < j; k++) {
|
|
211
|
+
const r = runs[k];
|
|
212
|
+
if (r.kind === 'TEXT' || r.kind === 'FIELD_CODE')
|
|
213
|
+
out += r.text;
|
|
214
|
+
}
|
|
215
|
+
if (revision !== undefined)
|
|
216
|
+
out += close(revision);
|
|
217
|
+
i = j;
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
/** A paragraph style's own left indent, in twips: 0 where it has none, or there are no styles to ask. */
|
|
222
|
+
styleIndentLeft(styleId) {
|
|
223
|
+
var _a;
|
|
224
|
+
const cached = this.styleIndents.get(styleId);
|
|
225
|
+
if (cached !== undefined)
|
|
226
|
+
return cached;
|
|
227
|
+
let left = 0;
|
|
228
|
+
if (this.resolver) {
|
|
229
|
+
try {
|
|
230
|
+
const ind = this.resolver.getEffectivePPr(styleId).ind;
|
|
231
|
+
const l = (_a = ind === null || ind === void 0 ? void 0 : ind.left) !== null && _a !== void 0 ? _a : ind === null || ind === void 0 ? void 0 : ind.start;
|
|
232
|
+
if (l !== undefined && l !== null && Number.isFinite(Number(l)))
|
|
233
|
+
left = Math.trunc(Number(l));
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// no indent for the style
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
this.styleIndents.set(styleId, left);
|
|
240
|
+
return left;
|
|
241
|
+
}
|
|
242
|
+
// ---------------------------------------------------------------- quotes
|
|
243
|
+
appendToQuote(depth, paragraph, container, block) {
|
|
244
|
+
if (this.openQuote === undefined || depth !== this.openQuoteDepth) {
|
|
245
|
+
this.closeQuote();
|
|
246
|
+
const outer = tagged(new MdBlockQuote(), block);
|
|
247
|
+
let inner = outer;
|
|
248
|
+
for (let d = 1; d < depth; d++) {
|
|
249
|
+
const next = new MdBlockQuote();
|
|
250
|
+
inner.appendChild(next);
|
|
251
|
+
inner = next;
|
|
252
|
+
}
|
|
253
|
+
container.appendChild(outer);
|
|
254
|
+
this.openQuote = inner;
|
|
255
|
+
this.openQuoteDepth = depth;
|
|
256
|
+
}
|
|
257
|
+
this.openQuote.appendChild(paragraph);
|
|
258
|
+
}
|
|
259
|
+
closeQuote() {
|
|
260
|
+
this.openQuote = undefined;
|
|
261
|
+
this.openQuoteDepth = 0;
|
|
262
|
+
}
|
|
263
|
+
// ---------------------------------------------------------------- lists
|
|
264
|
+
/**
|
|
265
|
+
* The open list whose last item an unlabelled paragraph continues: the deepest whose item's text the
|
|
266
|
+
* paragraph is indented to (its left indent at least the item's, and not at the margin), or the deepest
|
|
267
|
+
* open list for a ListParagraph-styled paragraph; the lists deeper than it are closed. Undefined where
|
|
268
|
+
* none: the paragraph ends the lists.
|
|
269
|
+
*/
|
|
270
|
+
followOnItem(block, style) {
|
|
271
|
+
const indent = value(block.indentLeft);
|
|
272
|
+
const listParagraph = style === LIST_PARAGRAPH;
|
|
273
|
+
for (let k = this.openLists.length - 1; k >= 0; k--) { // deepest first
|
|
274
|
+
const open = this.openLists[k];
|
|
275
|
+
if (open.lastItem !== undefined && (listParagraph || (indent > 0 && indent >= open.itemIndent))) {
|
|
276
|
+
this.openLists.length = k + 1;
|
|
277
|
+
return open;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
top() {
|
|
283
|
+
return this.openLists[this.openLists.length - 1];
|
|
284
|
+
}
|
|
285
|
+
listParagraph(block, numbering, container) {
|
|
286
|
+
var _a;
|
|
287
|
+
const ilvl = numbering.level;
|
|
288
|
+
// the list: two w:num on one w:abstractNum are one list, counting on from each other
|
|
289
|
+
const numId = (_a = numbering.abstractNumId) !== null && _a !== void 0 ? _a : numbering.numId;
|
|
290
|
+
const count = numbering.value;
|
|
291
|
+
const bullet = numbering.bullet || numbering.format === undefined;
|
|
292
|
+
// close any deeper (or same-level-but-different-list) contexts
|
|
293
|
+
if (this.openLists.length > ilvl + 1)
|
|
294
|
+
this.openLists.length = ilvl + 1;
|
|
295
|
+
if (this.openLists.length === ilvl + 1 && this.top().numId !== numId && ilvl === 0) {
|
|
296
|
+
this.openLists.pop(); // a different top-level list: restart
|
|
297
|
+
}
|
|
298
|
+
if (this.openLists.length === ilvl + 1 && (this.top().node instanceof MdBulletList) !== bullet) {
|
|
299
|
+
this.openLists.pop(); // a level drawn as the other kind (a w:num overriding it)
|
|
300
|
+
}
|
|
301
|
+
const tight = block.contextualSpacing !== false;
|
|
302
|
+
// the list's items show consecutive counts from its start; an item whose count is not the next one (the
|
|
303
|
+
// list resumed after a paragraph that ended it, a w:num sharing the count, a start override) begins a new
|
|
304
|
+
// list at its count
|
|
305
|
+
if (this.openLists.length === ilvl + 1 && count !== undefined && this.top().nextValue !== undefined
|
|
306
|
+
&& count !== this.top().nextValue) {
|
|
307
|
+
this.openLists.pop();
|
|
308
|
+
}
|
|
309
|
+
// open the missing levels (a level skipped over takes the paragraph's kind of list); a parent level
|
|
310
|
+
// reopened after an interruption is an empty item showing the parent's last count
|
|
311
|
+
while (this.openLists.length < ilvl + 1) {
|
|
312
|
+
const level = this.openLists.length;
|
|
313
|
+
const listBlock = tagged(createListBlock(numbering, level === ilvl, tight, level === ilvl ? count : this.lastValues.get(`${numId}/${level}`)), block);
|
|
314
|
+
if (this.openLists.length === 0) {
|
|
315
|
+
appendList(container, listBlock);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const parent = this.top();
|
|
319
|
+
if (parent.lastItem === undefined) {
|
|
320
|
+
parent.lastItem = tagged(new MdListItem(), block);
|
|
321
|
+
parent.node.appendChild(parent.lastItem);
|
|
322
|
+
}
|
|
323
|
+
appendList(parent.lastItem, listBlock);
|
|
324
|
+
}
|
|
325
|
+
this.openLists.push({ node: listBlock, numId, lastItem: undefined, itemIndent: 0, nextValue: undefined });
|
|
326
|
+
}
|
|
327
|
+
const target = this.top();
|
|
328
|
+
if (!tight)
|
|
329
|
+
target.node.tight = false;
|
|
330
|
+
const item = tagged(new MdListItem(), block);
|
|
331
|
+
item.appendChild(this.inlineParagraph(block, false));
|
|
332
|
+
target.node.appendChild(item);
|
|
333
|
+
target.lastItem = item;
|
|
334
|
+
target.itemIndent = value(block.indentLeft);
|
|
335
|
+
if (target.node instanceof MdOrderedList && count !== undefined) {
|
|
336
|
+
target.nextValue = count + 1;
|
|
337
|
+
this.lastValues.set(`${numId}/${ilvl}`, count);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
closeLists() {
|
|
341
|
+
this.openLists.length = 0;
|
|
342
|
+
}
|
|
343
|
+
// ---------------------------------------------------------------- tables
|
|
344
|
+
/**
|
|
345
|
+
* GFM pipes: the header row is the table's `w:tblHeader` rows, else its first (several `w:tblHeader` rows
|
|
346
|
+
* are one header, their cells' texts ` / ` apart, as plain text's listed format joins them); block content
|
|
347
|
+
* in cells flattens to inline with `<br>`, a nested table's paragraphs included; `w:vMerge` and
|
|
348
|
+
* `w:gridSpan` degrade (top-left wins, empty cells elsewhere), GFM having no spans.
|
|
349
|
+
*
|
|
350
|
+
* The table style's formatting is left out on purpose (docx4j CR-030 decision 4): a header row the style
|
|
351
|
+
* makes bold is not written `**bold**` (its being the GFM header says as much), and GFM has no table
|
|
352
|
+
* styling for the rest.
|
|
353
|
+
*/
|
|
354
|
+
table(table, container) {
|
|
355
|
+
const tableBlock = tagged(new MdTableBlock(), table);
|
|
356
|
+
const head = new MdTableHead();
|
|
357
|
+
const body = new MdTableBody();
|
|
358
|
+
tableBlock.appendChild(head);
|
|
359
|
+
const rows = table.rows;
|
|
360
|
+
let headerRows = 0;
|
|
361
|
+
while (headerRows < rows.length && rows[headerRows].header)
|
|
362
|
+
headerRows++;
|
|
363
|
+
if (headerRows === 0 && rows.length > 0)
|
|
364
|
+
headerRows = 1;
|
|
365
|
+
if (headerRows > 0)
|
|
366
|
+
head.appendChild(this.headerRow(rows.slice(0, headerRows)));
|
|
367
|
+
for (let r = headerRows; r < rows.length; r++) {
|
|
368
|
+
const row = new MdTableRow();
|
|
369
|
+
for (const cell of rows[r].cells) {
|
|
370
|
+
row.appendChild(this.tableCell(cell, false));
|
|
371
|
+
// gridSpan: empty cells after it, so that the columns stay aligned
|
|
372
|
+
for (let s = 1; s < cell.gridSpan; s++)
|
|
373
|
+
row.appendChild(new MdTableCell(false));
|
|
374
|
+
}
|
|
375
|
+
body.appendChild(row);
|
|
376
|
+
}
|
|
377
|
+
if (body.firstChild !== undefined)
|
|
378
|
+
tableBlock.appendChild(body);
|
|
379
|
+
container.appendChild(tableBlock);
|
|
380
|
+
}
|
|
381
|
+
/** The header: one row's cells, or several rows' cells by grid column, ` / ` apart. */
|
|
382
|
+
headerRow(rows) {
|
|
383
|
+
const row = new MdTableRow();
|
|
384
|
+
if (rows.length === 1) {
|
|
385
|
+
for (const cell of rows[0].cells) {
|
|
386
|
+
row.appendChild(this.tableCell(cell, true));
|
|
387
|
+
for (let s = 1; s < cell.gridSpan; s++)
|
|
388
|
+
row.appendChild(new MdTableCell(true));
|
|
389
|
+
}
|
|
390
|
+
return row;
|
|
391
|
+
}
|
|
392
|
+
// each grid column's cells, from the top: a cell is in the column it starts in
|
|
393
|
+
let width = 0;
|
|
394
|
+
for (const r of rows) {
|
|
395
|
+
let w = 0;
|
|
396
|
+
for (const c of r.cells)
|
|
397
|
+
w += Math.max(1, c.gridSpan);
|
|
398
|
+
width = Math.max(width, w);
|
|
399
|
+
}
|
|
400
|
+
const columns = [];
|
|
401
|
+
for (let g = 0; g < width; g++)
|
|
402
|
+
columns.push([]);
|
|
403
|
+
for (const r of rows) {
|
|
404
|
+
let g = 0;
|
|
405
|
+
for (const c of r.cells) {
|
|
406
|
+
if (!c.mergeContinue)
|
|
407
|
+
columns[g].push(c);
|
|
408
|
+
g += Math.max(1, c.gridSpan);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const column of columns) {
|
|
412
|
+
const cell = new MdTableCell(true);
|
|
413
|
+
// the column's alignment is its topmost header cell's, as with one header row
|
|
414
|
+
let alignment;
|
|
415
|
+
if (column.length > 0) {
|
|
416
|
+
const first = paragraphsOf(column[0]);
|
|
417
|
+
if (first.length > 0)
|
|
418
|
+
alignment = cellAlignment(first[0]);
|
|
419
|
+
}
|
|
420
|
+
const sink = new InlineSink(cell);
|
|
421
|
+
this.tableHeaderDepth++;
|
|
422
|
+
try {
|
|
423
|
+
let first = true;
|
|
424
|
+
for (const c of column) {
|
|
425
|
+
if (isEmptyCell(c))
|
|
426
|
+
continue;
|
|
427
|
+
if (!first)
|
|
428
|
+
sink.text(' / ', false, false, false);
|
|
429
|
+
first = false;
|
|
430
|
+
this.cellContent(c, cell, sink);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
this.tableHeaderDepth--;
|
|
435
|
+
}
|
|
436
|
+
sink.flush();
|
|
437
|
+
cell.alignment = alignment;
|
|
438
|
+
row.appendChild(cell);
|
|
439
|
+
}
|
|
440
|
+
return row;
|
|
441
|
+
}
|
|
442
|
+
tableCell(textCell, header) {
|
|
443
|
+
const cell = new MdTableCell(header);
|
|
444
|
+
// a vMerge continuation: the merge's top cell already holds the content
|
|
445
|
+
if (textCell.mergeContinue)
|
|
446
|
+
return cell;
|
|
447
|
+
const sink = new InlineSink(cell);
|
|
448
|
+
if (header)
|
|
449
|
+
this.tableHeaderDepth++;
|
|
450
|
+
try {
|
|
451
|
+
this.cellContent(textCell, cell, sink);
|
|
452
|
+
}
|
|
453
|
+
finally {
|
|
454
|
+
if (header)
|
|
455
|
+
this.tableHeaderDepth--;
|
|
456
|
+
}
|
|
457
|
+
sink.flush();
|
|
458
|
+
return cell;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* A cell's paragraphs, a nested table's included, `<br>` apart (GFM cells are single-line); its empty
|
|
462
|
+
* paragraphs left out, as plain text leaves them out (Word ends every cell with a paragraph, so a nested
|
|
463
|
+
* table or a block-level control comes with an empty one after it).
|
|
464
|
+
*
|
|
465
|
+
* A cell's alignment is its first paragraph's effective `w:jc`, empty or not; GFM aligns a column, from
|
|
466
|
+
* its header cell's (the delimiter row), so a body cell's is not seen.
|
|
467
|
+
*/
|
|
468
|
+
cellContent(textCell, cell, sink) {
|
|
469
|
+
let first = true;
|
|
470
|
+
const paragraphs = paragraphsOf(textCell);
|
|
471
|
+
if (cell.alignment === undefined && paragraphs.length > 0)
|
|
472
|
+
cell.alignment = cellAlignment(paragraphs[0]);
|
|
473
|
+
for (const b of paragraphs) {
|
|
474
|
+
if (isEmpty(b))
|
|
475
|
+
continue;
|
|
476
|
+
if (!first)
|
|
477
|
+
sink.node(new MdHtmlInline('<br>'));
|
|
478
|
+
first = false;
|
|
479
|
+
this.runs(b, sink);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// ---------------------------------------------------------------- charts
|
|
483
|
+
/**
|
|
484
|
+
* A chart: its note (docx4j CR-047: `[chart: clustered column; value axis: Revenue]`), its title and its
|
|
485
|
+
* data as a table, after the paragraph it is in, the categories down and the series across (CR-012 phase
|
|
486
|
+
* 5c fills them in; until then the text model makes no chart block).
|
|
487
|
+
*/
|
|
488
|
+
chart(block, container) {
|
|
489
|
+
if (block.chart !== undefined) {
|
|
490
|
+
const note = tagged(new MdParagraph(), block);
|
|
491
|
+
note.appendChild(new MdText(block.chart.note));
|
|
492
|
+
container.appendChild(note);
|
|
493
|
+
}
|
|
494
|
+
if (block.chartTitle !== undefined && block.chartTitle !== '') {
|
|
495
|
+
const title = tagged(new MdParagraph(), block);
|
|
496
|
+
title.appendChild(new MdText(block.chartTitle));
|
|
497
|
+
container.appendChild(title);
|
|
498
|
+
}
|
|
499
|
+
if (block.chartHeader.length === 0)
|
|
500
|
+
return;
|
|
501
|
+
const table = tagged(new MdTableBlock(), block);
|
|
502
|
+
const head = new MdTableHead();
|
|
503
|
+
head.appendChild(chartRow(block.chartHeader, true, block.chartHeader));
|
|
504
|
+
table.appendChild(head);
|
|
505
|
+
if (block.chartRows.length > 0) {
|
|
506
|
+
const body = new MdTableBody();
|
|
507
|
+
for (const r of block.chartRows)
|
|
508
|
+
body.appendChild(chartRow(r, false, block.chartHeader));
|
|
509
|
+
table.appendChild(body);
|
|
510
|
+
}
|
|
511
|
+
container.appendChild(table);
|
|
512
|
+
}
|
|
513
|
+
// ---------------------------------------------------------------- inlines
|
|
514
|
+
inlines(block, parent, withListLabel) {
|
|
515
|
+
const sink = new InlineSink(parent);
|
|
516
|
+
// under list_labels: true, the label the numbering model computes leads a paragraph that isn't written as
|
|
517
|
+
// a list item (a numbered heading's "1.2")
|
|
518
|
+
if (withListLabel && block.listLabel !== undefined && block.listLabel.trim() !== '') {
|
|
519
|
+
sink.text(block.listLabel.replace(/\t/g, ' '), false, false, false);
|
|
520
|
+
}
|
|
521
|
+
this.runs(block, sink);
|
|
522
|
+
sink.flush();
|
|
523
|
+
}
|
|
524
|
+
/** A block's runs, in `markup` each revision's between its CriticMarkup marks (REQ-703, REQ-702). */
|
|
525
|
+
runs(block, sink) {
|
|
526
|
+
const runs = block.runs;
|
|
527
|
+
let i = 0;
|
|
528
|
+
while (i < runs.length) {
|
|
529
|
+
const revision = runs[i].revision;
|
|
530
|
+
let j = i;
|
|
531
|
+
while (j < runs.length && this.sameMarker(revision, runs[j].revision))
|
|
532
|
+
j++;
|
|
533
|
+
if (revision !== undefined)
|
|
534
|
+
this.openMarker(revision, sink);
|
|
535
|
+
this.linked(runs.slice(i, j), sink);
|
|
536
|
+
if (revision !== undefined)
|
|
537
|
+
sink.node(new MdHtmlInline(close(revision)));
|
|
538
|
+
i = j;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** Runs, those with the same link target in one link. */
|
|
542
|
+
linked(runs, sink) {
|
|
543
|
+
let i = 0;
|
|
544
|
+
while (i < runs.length) {
|
|
545
|
+
let target = runs[i].link;
|
|
546
|
+
let j = i;
|
|
547
|
+
while (j < runs.length && runs[j].link === target)
|
|
548
|
+
j++;
|
|
549
|
+
if (target !== undefined && !LinkTargets.isSafe(target, this.linkSchemes)) {
|
|
550
|
+
// docx4j CR-056: a script-scheme target, its character references decoded too; the text, with no link (CR-012
|
|
551
|
+
// sections 21 and 21.2)
|
|
552
|
+
if (!this.linkRefused)
|
|
553
|
+
log.warn(LinkTargets.refusedMessage(target));
|
|
554
|
+
this.linkRefused = true;
|
|
555
|
+
target = undefined;
|
|
556
|
+
}
|
|
557
|
+
if (target === undefined) {
|
|
558
|
+
for (let k = i; k < j; k++)
|
|
559
|
+
this.run(runs[k], sink);
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
const link = new MdLink(target, undefined);
|
|
563
|
+
const linkSink = new InlineSink(link);
|
|
564
|
+
for (let k = i; k < j; k++)
|
|
565
|
+
this.run(runs[k], linkSink);
|
|
566
|
+
linkSink.flush();
|
|
567
|
+
sink.node(link);
|
|
568
|
+
}
|
|
569
|
+
i = j;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
run(run, sink) {
|
|
573
|
+
switch (run.kind) {
|
|
574
|
+
case 'TEXT':
|
|
575
|
+
this.text(run, sink);
|
|
576
|
+
return;
|
|
577
|
+
case 'FIELD_CODE':
|
|
578
|
+
case 'LIST_LABEL':
|
|
579
|
+
sink.text(run.text, false, false, false);
|
|
580
|
+
return;
|
|
581
|
+
case 'NOTE_REFERENCE':
|
|
582
|
+
if (this.textOptions.notes && this.textOptions.noteReferences) {
|
|
583
|
+
sink.node(new MdFootnoteReference(noteLabel(run.noteType, run.noteNumber, run.noteId)));
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
case 'COMMENT_REFERENCE': {
|
|
587
|
+
const comment = run.commentId === undefined ? undefined : this.result.comments.get(run.commentId);
|
|
588
|
+
// a reply is written after its parent, never at its own reference (REQ-504)
|
|
589
|
+
if (comment === undefined || (comment.parentId !== undefined && this.result.comments.has(comment.parentId)))
|
|
590
|
+
return;
|
|
591
|
+
this.comment(comment, sink);
|
|
592
|
+
for (const reply of comment.replies)
|
|
593
|
+
this.comment(reply, sink);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
case 'IMAGE':
|
|
597
|
+
this.image(run, sink);
|
|
598
|
+
return;
|
|
599
|
+
case 'PAGE_MARKER':
|
|
600
|
+
if (this.textOptions.pageMarkers)
|
|
601
|
+
sink.text(`[page ${run.pageNumber}]`, false, false, false);
|
|
602
|
+
return;
|
|
603
|
+
default:
|
|
604
|
+
// a note's own mark (its definition's label carries it), an object (no text)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
text(run, sink) {
|
|
608
|
+
const value = run.text;
|
|
609
|
+
if (value === '')
|
|
610
|
+
return;
|
|
611
|
+
if (run.math) {
|
|
612
|
+
const l = latex(run);
|
|
613
|
+
if (l !== '')
|
|
614
|
+
sink.node(new MdInlineMath(l, false));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const bold = run.bold && this.tableHeaderDepth === 0; // a header row's bold is convention, not markup
|
|
618
|
+
const lines = value.split('\n');
|
|
619
|
+
for (let l = 0; l < lines.length; l++) {
|
|
620
|
+
if (l > 0)
|
|
621
|
+
sink.node(new MdHardLineBreak());
|
|
622
|
+
const line = lines[l];
|
|
623
|
+
if (line === '')
|
|
624
|
+
continue;
|
|
625
|
+
if (run.monospace) {
|
|
626
|
+
sink.node(new MdCode(line));
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
sink.text(line, bold, run.italic, run.strike);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
comment(comment, sink) {
|
|
634
|
+
sink.node(new MdHtmlInline('{>>'));
|
|
635
|
+
this.detailText(comment.author, comment.date, sink);
|
|
636
|
+
sink.text(comment.text, false, false, false);
|
|
637
|
+
sink.node(new MdHtmlInline('<<}'));
|
|
638
|
+
}
|
|
639
|
+
/** Runs under one marker: the same type, and the same author and date where the detail shows them. */
|
|
640
|
+
sameMarker(a, b) {
|
|
641
|
+
if (a === undefined || b === undefined)
|
|
642
|
+
return a === b;
|
|
643
|
+
if (a.type !== b.type)
|
|
644
|
+
return false;
|
|
645
|
+
switch (this.textOptions.markupDetail) {
|
|
646
|
+
case 'author': return a.author === b.author;
|
|
647
|
+
case 'author_date': return a.author === b.author && a.date === b.date;
|
|
648
|
+
default: return true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** The marker as one string, for a code block, where nothing is markup. */
|
|
652
|
+
open(revision) {
|
|
653
|
+
return (revision.type === 'INSERTION' ? '{++' : '{--') + this.detail(revision.author, revision.date);
|
|
654
|
+
}
|
|
655
|
+
/** The marker in a paragraph: its braces raw, its author and date text (docx4j's `open(revision, sink)`). */
|
|
656
|
+
openMarker(revision, sink) {
|
|
657
|
+
sink.node(new MdHtmlInline(revision.type === 'INSERTION' ? '{++' : '{--'));
|
|
658
|
+
this.detailText(revision.author, revision.date, sink);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* The marker's author and date as text, which the renderer escapes as it does any document text (docx4j CR-056,
|
|
662
|
+
* `30fb41024`; CR-012 section 21.1): only the braces are raw. Before, the detail went out raw with them, so an
|
|
663
|
+
* author of `<script>...</script>` reached a renderer that allows raw HTML as markup.
|
|
664
|
+
*/
|
|
665
|
+
detailText(author, date, sink) {
|
|
666
|
+
const d = this.detail(author, date);
|
|
667
|
+
if (d !== '')
|
|
668
|
+
sink.text(d, false, false, false);
|
|
669
|
+
}
|
|
670
|
+
detail(author, date) {
|
|
671
|
+
switch (this.textOptions.markupDetail) {
|
|
672
|
+
case 'author': return `${author !== null && author !== void 0 ? author : ''}: `;
|
|
673
|
+
case 'author_date': return `${author !== null && author !== void 0 ? author : ''}, ${date !== null && date !== void 0 ? date : ''}: `;
|
|
674
|
+
default: return '';
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* A picture with the describer's text (REQ-703, REQ-514); its destination, which needs its image part's
|
|
679
|
+
* bytes, is the exporter's to fill in (`images`), empty for a picture whose image can't be had.
|
|
680
|
+
*/
|
|
681
|
+
image(run, sink) {
|
|
682
|
+
const image = new MdImage('', undefined);
|
|
683
|
+
image.appendChild(new MdText(run.description === undefined ? '' : run.description.text));
|
|
684
|
+
this.images.push({ node: image, run });
|
|
685
|
+
sink.node(image);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// ---------------------------------------------------------------- helpers
|
|
689
|
+
/** A node made for a block: it begins the block's Markdown (`MarkdownExporter.blocks`). */
|
|
690
|
+
function tagged(node, block) {
|
|
691
|
+
node.tag = block;
|
|
692
|
+
return node;
|
|
693
|
+
}
|
|
694
|
+
/** Accumulates same-formatted text, wrapping it on flush. */
|
|
695
|
+
class InlineSink {
|
|
696
|
+
constructor(parent) {
|
|
697
|
+
this.parent = parent;
|
|
698
|
+
this.buf = '';
|
|
699
|
+
this.bold = false;
|
|
700
|
+
this.italic = false;
|
|
701
|
+
this.strike = false;
|
|
702
|
+
}
|
|
703
|
+
text(s, b, i, st) {
|
|
704
|
+
if ((b !== this.bold || i !== this.italic || st !== this.strike) && this.buf.length > 0)
|
|
705
|
+
this.flush();
|
|
706
|
+
this.bold = b;
|
|
707
|
+
this.italic = i;
|
|
708
|
+
this.strike = st;
|
|
709
|
+
this.buf += s;
|
|
710
|
+
}
|
|
711
|
+
node(n) {
|
|
712
|
+
this.flush();
|
|
713
|
+
this.parent.appendChild(n);
|
|
714
|
+
}
|
|
715
|
+
flush() {
|
|
716
|
+
if (this.buf.length === 0)
|
|
717
|
+
return;
|
|
718
|
+
let n = new MdText(this.buf);
|
|
719
|
+
// strong inside emphasis: the renderer then writes ***both***, rather than switching the inner marker to _
|
|
720
|
+
if (this.bold) {
|
|
721
|
+
const strong = new MdStrongEmphasis();
|
|
722
|
+
strong.appendChild(n);
|
|
723
|
+
n = strong;
|
|
724
|
+
}
|
|
725
|
+
if (this.italic) {
|
|
726
|
+
const em = new MdEmphasis();
|
|
727
|
+
em.appendChild(n);
|
|
728
|
+
n = em;
|
|
729
|
+
}
|
|
730
|
+
if (this.strike) {
|
|
731
|
+
const st = new MdStrikethrough('~~');
|
|
732
|
+
st.appendChild(n);
|
|
733
|
+
n = st;
|
|
734
|
+
}
|
|
735
|
+
this.parent.appendChild(n);
|
|
736
|
+
this.buf = '';
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function isEmpty(block) {
|
|
740
|
+
for (const r of block.runs) {
|
|
741
|
+
if (r.kind === 'TEXT' && r.text !== '')
|
|
742
|
+
return false;
|
|
743
|
+
if (r.kind === 'IMAGE' || r.kind === 'NOTE_REFERENCE')
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
function isEmptyCell(cell) {
|
|
749
|
+
for (const b of cell.blocks) {
|
|
750
|
+
if (b.kind === 'TABLE') {
|
|
751
|
+
for (const r of b.rows)
|
|
752
|
+
for (const c of r.cells)
|
|
753
|
+
if (!isEmptyCell(c))
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
else if (b.kind === 'PARAGRAPH' && !isEmpty(b)) {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
/** The paragraph's one run, a display equation with no revision; else undefined. */
|
|
763
|
+
function soleDisplayMath(block) {
|
|
764
|
+
let found;
|
|
765
|
+
for (const r of block.runs) {
|
|
766
|
+
if (r.kind === 'TEXT' && r.text === '')
|
|
767
|
+
continue;
|
|
768
|
+
if (found !== undefined || !r.displayMath || r.revision !== undefined)
|
|
769
|
+
return undefined;
|
|
770
|
+
found = r;
|
|
771
|
+
}
|
|
772
|
+
return found;
|
|
773
|
+
}
|
|
774
|
+
/** An equation's LaTeX: its text without the delimiters. */
|
|
775
|
+
function latex(run) {
|
|
776
|
+
const s = run.text;
|
|
777
|
+
const d = run.displayMath ? 2 : 1;
|
|
778
|
+
return s.length >= 2 * d ? s.substring(d, s.length - d) : s;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* A code paragraph: styled as one, or in a named paragraph style whose own font is mono (every run
|
|
782
|
+
* monospaced, none of them beyond the style). Inline code is what a run adds to its paragraph style, so a
|
|
783
|
+
* paragraph whose style is already mono would get no code marks at all; this is the paragraph-level
|
|
784
|
+
* counterpart. A mono document default doesn't count (a document set in Courier New throughout is prose, not
|
|
785
|
+
* one fence), which is why a named style is required.
|
|
786
|
+
*/
|
|
787
|
+
function isCodeParagraph(block) {
|
|
788
|
+
const style = block.style;
|
|
789
|
+
if (style === undefined)
|
|
790
|
+
return false;
|
|
791
|
+
if (CODE_STYLES.has(style))
|
|
792
|
+
return true;
|
|
793
|
+
if (!block.monospace)
|
|
794
|
+
return false;
|
|
795
|
+
for (const r of block.runs)
|
|
796
|
+
if (r.kind === 'TEXT' && r.monospace)
|
|
797
|
+
return false;
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
function sameStory(a, b) {
|
|
801
|
+
return a.story === b.story && a.noteId === b.noteId;
|
|
802
|
+
}
|
|
803
|
+
function value(i) {
|
|
804
|
+
return i !== null && i !== void 0 ? i : 0;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* A list block. An ordered one starts at the count given (the item's own, or a reopened parent's last), else
|
|
808
|
+
* (the paragraph's own level at the top) at the level's `w:start`; Markdown numbers the items on from there.
|
|
809
|
+
*/
|
|
810
|
+
function createListBlock(numbering, own, tight, count) {
|
|
811
|
+
// a level the numbering definitions don't give is a bullet list
|
|
812
|
+
const bullet = numbering.bullet || numbering.format === undefined;
|
|
813
|
+
let listBlock;
|
|
814
|
+
if (bullet) {
|
|
815
|
+
listBlock = new MdBulletList();
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
const ordered = new MdOrderedList();
|
|
819
|
+
if (count !== undefined) {
|
|
820
|
+
if (count !== 1)
|
|
821
|
+
ordered.markerStartNumber = count;
|
|
822
|
+
}
|
|
823
|
+
else if (own && numbering.level === 0 && numbering.start > 1) {
|
|
824
|
+
ordered.markerStartNumber = numbering.start;
|
|
825
|
+
}
|
|
826
|
+
listBlock = ordered;
|
|
827
|
+
}
|
|
828
|
+
listBlock.tight = tight;
|
|
829
|
+
return listBlock;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Appends a list. Markdown runs two adjacent lists of one kind together, numbering the second on from the
|
|
833
|
+
* first; a list that directly follows another of its kind therefore takes the other delimiter (`1.` or
|
|
834
|
+
* `1)`) or bullet (`-` or `*`), which CommonMark reads as a new list.
|
|
835
|
+
*/
|
|
836
|
+
function appendList(parent, list) {
|
|
837
|
+
const previous = parent.lastChild;
|
|
838
|
+
if (list instanceof MdOrderedList && previous instanceof MdOrderedList) {
|
|
839
|
+
list.markerDelimiter = previous.markerDelimiter === ')' ? '.' : ')';
|
|
840
|
+
}
|
|
841
|
+
else if (list instanceof MdBulletList && previous instanceof MdBulletList) {
|
|
842
|
+
list.marker = previous.marker === '*' ? '-' : '*';
|
|
843
|
+
}
|
|
844
|
+
parent.appendChild(list);
|
|
845
|
+
}
|
|
846
|
+
function paragraphsOf(cell) {
|
|
847
|
+
const out = [];
|
|
848
|
+
for (const b of cell.blocks) {
|
|
849
|
+
if (b.kind === 'TABLE') {
|
|
850
|
+
for (const r of b.rows)
|
|
851
|
+
for (const c of r.cells)
|
|
852
|
+
out.push(...paragraphsOf(c));
|
|
853
|
+
}
|
|
854
|
+
else if (b.kind === 'PARAGRAPH') {
|
|
855
|
+
out.push(b);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
860
|
+
function cellAlignment(block) {
|
|
861
|
+
switch (block.alignment) {
|
|
862
|
+
case 'center': return 'CENTER';
|
|
863
|
+
case 'right':
|
|
864
|
+
case 'end': return 'RIGHT';
|
|
865
|
+
default: return undefined; // LEFT is GFM's default
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function chartRow(cells, header, headings) {
|
|
869
|
+
const row = new MdTableRow();
|
|
870
|
+
let labels = 0;
|
|
871
|
+
while (labels < headings.length && headings[labels] === '')
|
|
872
|
+
labels++;
|
|
873
|
+
for (let i = 0; i < cells.length; i++) {
|
|
874
|
+
const cell = new MdTableCell(header);
|
|
875
|
+
if (i >= Math.max(labels, 1))
|
|
876
|
+
cell.alignment = 'RIGHT';
|
|
877
|
+
if (cells[i] !== '')
|
|
878
|
+
cell.appendChild(new MdText(cells[i]));
|
|
879
|
+
row.appendChild(cell);
|
|
880
|
+
}
|
|
881
|
+
return row;
|
|
882
|
+
}
|
|
883
|
+
function isNote(block) {
|
|
884
|
+
return block.noteType !== undefined && (block.story === 'FOOTNOTE' || block.story === 'ENDNOTE' || block.story === 'TEXT_BOX');
|
|
885
|
+
}
|
|
886
|
+
function sameNote(a, b) {
|
|
887
|
+
return isNote(b) && a.noteType === b.noteType && a.noteId === b.noteId;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* A note's label: a footnote's number (`[^1]`), an endnote's prefixed `e` (`[^e1]`), since the two are
|
|
891
|
+
* numbered apart (WD REQ-503); a note whose reference carries a custom mark has no number, so its `w:id`
|
|
892
|
+
* labels it (`[^f-5]`, `[^e-5]`).
|
|
893
|
+
*/
|
|
894
|
+
function noteLabel(type, number, id) {
|
|
895
|
+
const foot = type === 'footnote';
|
|
896
|
+
if (number > 0)
|
|
897
|
+
return foot ? String(number) : `e${number}`;
|
|
898
|
+
return `${foot ? 'f-' : 'e-'}${String(id)}`;
|
|
899
|
+
}
|
|
900
|
+
/** Drops the leading space left behind by the note's own mark. */
|
|
901
|
+
function trimLeadingMarkerSpace(definition) {
|
|
902
|
+
const paragraph = definition.firstChild;
|
|
903
|
+
if (paragraph instanceof MdParagraph && paragraph.firstChild instanceof MdText) {
|
|
904
|
+
const text = paragraph.firstChild;
|
|
905
|
+
if (text.literal.startsWith(' '))
|
|
906
|
+
text.literal = text.literal.substring(1);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
function close(revision) {
|
|
910
|
+
return revision.type === 'INSERTION' ? '++}' : '--}';
|
|
911
|
+
}
|