@asciidoctor/core 3.0.4 → 4.0.0-alpha.2

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.
Files changed (92) hide show
  1. package/README.md +42 -10
  2. package/build/browser/index.js +24156 -0
  3. package/build/node/index.cjs +24737 -0
  4. package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
  5. package/package.json +55 -97
  6. package/src/abstract_block.js +857 -0
  7. package/src/abstract_node.js +954 -0
  8. package/src/attribute_entry.js +12 -0
  9. package/src/attribute_list.js +380 -0
  10. package/src/block.js +168 -0
  11. package/src/browser/asset.js +22 -0
  12. package/src/browser/reader.js +138 -0
  13. package/src/browser.js +121 -0
  14. package/src/callouts.js +85 -0
  15. package/src/compliance.js +54 -0
  16. package/src/constants.js +665 -0
  17. package/src/convert.js +370 -0
  18. package/src/converter/composite.js +83 -0
  19. package/src/converter/docbook5.js +1031 -0
  20. package/src/converter/html5.js +1893 -0
  21. package/src/converter/manpage.js +935 -0
  22. package/src/converter/template.js +459 -0
  23. package/src/converter.js +478 -0
  24. package/src/data/stylesheet-data.js +2 -0
  25. package/src/document.js +2134 -0
  26. package/src/extensions.js +1952 -0
  27. package/src/footnote.js +28 -0
  28. package/src/helpers.js +355 -0
  29. package/src/index.js +138 -0
  30. package/src/inline.js +158 -0
  31. package/src/list.js +240 -0
  32. package/src/load.js +276 -0
  33. package/src/logging.js +526 -0
  34. package/src/parser.js +3661 -0
  35. package/src/path_resolver.js +472 -0
  36. package/src/reader.js +1755 -0
  37. package/src/rx.js +829 -0
  38. package/src/section.js +354 -0
  39. package/src/stylesheets.js +30 -0
  40. package/src/substitutors.js +2241 -0
  41. package/src/syntaxHighlighter/highlightjs.js +90 -0
  42. package/src/syntaxHighlighter/html_pipeline.js +33 -0
  43. package/src/syntax_highlighter.js +304 -0
  44. package/src/table.js +952 -0
  45. package/src/timings.js +78 -0
  46. package/types/abstract_block.d.ts +346 -0
  47. package/types/abstract_node.d.ts +471 -0
  48. package/types/attribute_entry.d.ts +7 -0
  49. package/types/attribute_list.d.ts +52 -0
  50. package/types/block.d.ts +55 -0
  51. package/types/browser/asset.d.ts +7 -0
  52. package/types/browser/reader.d.ts +29 -0
  53. package/types/callouts.d.ts +36 -0
  54. package/types/compliance.d.ts +23 -0
  55. package/types/constants.d.ts +268 -0
  56. package/types/convert.d.ts +34 -0
  57. package/types/converter/composite.d.ts +20 -0
  58. package/types/converter/docbook5.d.ts +41 -0
  59. package/types/converter/html5.d.ts +51 -0
  60. package/types/converter/manpage.d.ts +59 -0
  61. package/types/converter/template.d.ts +83 -0
  62. package/types/converter.d.ts +150 -0
  63. package/types/data/stylesheet-data.d.ts +2 -0
  64. package/types/document.d.ts +495 -0
  65. package/types/extensions.d.ts +876 -0
  66. package/types/footnote.d.ts +18 -0
  67. package/types/helpers.d.ts +146 -0
  68. package/types/index.d.cts +75 -0
  69. package/types/index.d.ts +73 -3731
  70. package/types/inline.d.ts +69 -0
  71. package/types/list.d.ts +114 -0
  72. package/types/load.d.ts +39 -0
  73. package/types/logging.d.ts +187 -0
  74. package/types/parser.d.ts +114 -0
  75. package/types/path_resolver.d.ts +103 -0
  76. package/types/reader.d.ts +184 -0
  77. package/types/rx.d.ts +513 -0
  78. package/types/section.d.ts +122 -0
  79. package/types/stylesheets.d.ts +10 -0
  80. package/types/substitutors.d.ts +208 -0
  81. package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
  82. package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
  83. package/types/syntax_highlighter.d.ts +167 -0
  84. package/types/table.d.ts +231 -0
  85. package/types/timings.d.ts +25 -0
  86. package/types/tsconfig.json +9 -0
  87. package/LICENSE +0 -21
  88. package/dist/browser/asciidoctor.js +0 -47654
  89. package/dist/browser/asciidoctor.min.js +0 -1452
  90. package/dist/graalvm/asciidoctor.js +0 -47402
  91. package/dist/node/asciidoctor.cjs +0 -21567
  92. package/dist/node/asciidoctor.js +0 -23037
@@ -0,0 +1,1031 @@
1
+ // ESM conversion of converter/docbook5.rb
2
+ // Translated from the Ruby Asciidoctor::Converter::DocBook5Converter.
3
+ // Translation notes:
4
+ // - Ruby symbols (:compound) → strings ('compound')
5
+ // - Ruby predicate methods (title?, attr?, option?, has_role?, blocks?) → hasTitle(), hasAttr(), hasOption(), hasRole(), hasBlocks()
6
+ // - Ruby `node.image_uri` → `await node.imageUri()`; `node.icon_uri` → `await node.iconUri()`
7
+ // - common_attributes(id, role, reftext) kept as private _commonAttributes(id, role, reftext)
8
+ // - blockquote_tag uses a content callback instead of Ruby block
9
+ // - Ruby LF constant → '\n'
10
+ // - document.nested? → doc.isNested(); doc.noheader → doc.isNoheader(); doc.notitle → doc.isNotitle()
11
+
12
+ import { ConverterBase } from '../converter.js'
13
+ import { XmlSanitizeRx } from '../rx.js'
14
+
15
+ const LF = '\n'
16
+
17
+ // default represents variablelist
18
+ const DLIST_TAGS = {
19
+ qanda: {
20
+ list: 'qandaset',
21
+ entry: 'qandaentry',
22
+ label: 'question',
23
+ term: 'simpara',
24
+ item: 'answer',
25
+ },
26
+ glossary: {
27
+ list: null,
28
+ entry: 'glossentry',
29
+ term: 'glossterm',
30
+ item: 'glossdef',
31
+ },
32
+ }
33
+ const DLIST_TAGS_DEFAULT = {
34
+ list: 'variablelist',
35
+ entry: 'varlistentry',
36
+ term: 'term',
37
+ item: 'listitem',
38
+ }
39
+
40
+ const QUOTE_TAGS = {
41
+ monospaced: ['<literal>', '</literal>'],
42
+ emphasis: ['<emphasis>', '</emphasis>', true],
43
+ strong: ['<emphasis role="strong">', '</emphasis>', true],
44
+ double: ['<quote role="double">', '</quote>', true],
45
+ single: ['<quote role="single">', '</quote>', true],
46
+ mark: ['<emphasis role="marked">', '</emphasis>'],
47
+ superscript: ['<superscript>', '</superscript>'],
48
+ subscript: ['<subscript>', '</subscript>'],
49
+ }
50
+ const QUOTE_TAGS_DEFAULT = ['', '', true]
51
+
52
+ const MANPAGE_SECTION_TAGS = {
53
+ section: 'refsection',
54
+ synopsis: 'refsynopsisdiv',
55
+ }
56
+ const TABLE_PI_NAMES = ['dbhtml', 'dbfo', 'dblatex']
57
+
58
+ const CopyrightRx = /^(.+?)(?: ((?:\d{4}-)?\d{4}))?$/
59
+
60
+ export class DocBook5Converter extends ConverterBase {
61
+ constructor(backend, opts = {}) {
62
+ super(backend, opts)
63
+ }
64
+
65
+ async convert_document(node) {
66
+ const result = ['<?xml version="1.0" encoding="UTF-8"?>']
67
+ if (node.hasAttribute('toc')) {
68
+ result.push(
69
+ node.hasAttribute('toclevels')
70
+ ? `<?asciidoc-toc maxdepth="${node.getAttribute('toclevels')}"?>`
71
+ : '<?asciidoc-toc?>'
72
+ )
73
+ }
74
+ if (node.hasAttribute('sectnums')) {
75
+ result.push(
76
+ node.hasAttribute('sectnumlevels')
77
+ ? `<?asciidoc-numbered maxdepth="${node.getAttribute('sectnumlevels')}"?>`
78
+ : '<?asciidoc-numbered?>'
79
+ )
80
+ }
81
+ const langAttribute = node.hasAttribute('nolang')
82
+ ? ''
83
+ : ` xml:lang="${node.getAttribute('lang', 'en')}"`
84
+ let rootTagName = node.doctype
85
+ let manpage = false
86
+ if (rootTagName === 'manpage') {
87
+ manpage = true
88
+ rootTagName = 'article'
89
+ }
90
+ const rootTagIdx = result.length
91
+ const id = node.id
92
+ const abstract = this._findRootAbstract(node)
93
+ if (!node.isNoheader())
94
+ result.push(await this._documentInfoTag(node, abstract))
95
+ if (manpage) {
96
+ result.push('<refentry>')
97
+ result.push('<refmeta>')
98
+ if (node.hasAttribute('mantitle'))
99
+ result.push(
100
+ `<refentrytitle>${await node.applyReftextSubs(node.getAttribute('mantitle'))}</refentrytitle>`
101
+ )
102
+ if (node.hasAttribute('manvolnum'))
103
+ result.push(`<manvolnum>${node.getAttribute('manvolnum')}</manvolnum>`)
104
+ result.push(
105
+ `<refmiscinfo class="source">${node.getAttribute('mansource', '&#160;')}</refmiscinfo>`
106
+ )
107
+ result.push(
108
+ `<refmiscinfo class="manual">${node.getAttribute('manmanual', '&#160;')}</refmiscinfo>`
109
+ )
110
+ result.push('</refmeta>')
111
+ result.push('<refnamediv>')
112
+ if (node.hasAttribute('mannames')) {
113
+ for (const n of node.getAttribute('mannames'))
114
+ result.push(`<refname>${n}</refname>`)
115
+ }
116
+ if (node.hasAttribute('manpurpose'))
117
+ result.push(
118
+ `<refpurpose>${node.getAttribute('manpurpose')}</refpurpose>`
119
+ )
120
+ result.push('</refnamediv>')
121
+ }
122
+ const headerDocinfo = await node.docinfo('header')
123
+ if (headerDocinfo) result.push(headerDocinfo)
124
+ const extractedAbstract = abstract
125
+ ? this._extractAbstract(node, abstract)
126
+ : null
127
+ if (node.hasBlocks()) {
128
+ const blockResults = []
129
+ for (const b of node.blocks) blockResults.push(await b.convert())
130
+ result.push(blockResults.filter((s) => s != null).join(LF))
131
+ }
132
+ if (extractedAbstract) this._restoreAbstract(extractedAbstract)
133
+ const footerDocinfo = await node.docinfo('footer')
134
+ if (footerDocinfo) result.push(footerDocinfo)
135
+ if (manpage) result.push('</refentry>')
136
+ // defer adding root tag in case document ID is auto-generated on demand
137
+ const nodeId = id ?? node.id ?? this._rootDocId
138
+ result.splice(
139
+ rootTagIdx,
140
+ 0,
141
+ `<${rootTagName} xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"${langAttribute}${this._commonAttributes(nodeId)}>`
142
+ )
143
+ result.push(`</${rootTagName}>`)
144
+ return result.join(LF)
145
+ }
146
+
147
+ async convert_embedded(node) {
148
+ // NOTE in DocBook 5, the root abstract must be in the info tag and is thus not part of the body
149
+ let abstract = null
150
+ if (this.backend === 'docbook5') {
151
+ abstract = this._findRootAbstract(node)
152
+ if (abstract) this._extractAbstract(node, abstract)
153
+ }
154
+ const blockParts = []
155
+ for (const b of node.blocks) blockParts.push(await b.convert())
156
+ const result = blockParts.filter((s) => s != null).join(LF)
157
+ if (abstract) this._restoreAbstract(abstract)
158
+ return result
159
+ }
160
+
161
+ async convert_section(node) {
162
+ let tagName = node.sectname
163
+ if (node.document.doctype === 'manpage') {
164
+ tagName = MANPAGE_SECTION_TAGS[tagName] ?? tagName
165
+ }
166
+ const titleEl =
167
+ node.special && (node.hasOption('notitle') || node.hasOption('untitled'))
168
+ ? ''
169
+ : `<title>${node.title}</title>\n`
170
+ return `<${tagName}${this._commonAttributes(node.id, node.role, node.reftext)}>\n${titleEl}${await node.content()}\n</${tagName}>`
171
+ }
172
+
173
+ async convert_admonition(node) {
174
+ const tagName = node.getAttribute('name')
175
+ return `<${tagName}${this._commonAttributes(node.id, node.role, node.reftext)}>\n${this._titleTag(node)}${await this._encloseContent(node)}\n</${tagName}>`
176
+ }
177
+
178
+ async convert_audio(_node) {
179
+ return ''
180
+ }
181
+
182
+ async convert_colist(node) {
183
+ const result = []
184
+ result.push(
185
+ `<calloutlist${this._commonAttributes(node.id, node.role, node.reftext)}>`
186
+ )
187
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
188
+ for (const item of node.getItems()) {
189
+ result.push(`<callout arearefs="${item.getAttribute('coids')}">`)
190
+ result.push(`<para>${item.getText()}</para>`)
191
+ if (item.hasBlocks()) result.push(await item.content())
192
+ result.push('</callout>')
193
+ }
194
+ result.push('</calloutlist>')
195
+ return result.join(LF)
196
+ }
197
+
198
+ async convert_dlist(node) {
199
+ const result = []
200
+ if (node.style === 'horizontal') {
201
+ const tagName = node.hasTitle() ? 'table' : 'informaltable'
202
+ result.push(
203
+ `<${tagName}${this._commonAttributes(node.id, node.role, node.reftext)} tabstyle="horizontal" frame="none" colsep="0" rowsep="0">`
204
+ )
205
+ result.push(`${this._titleTag(node)}<tgroup cols="2">`)
206
+ result.push(
207
+ `<colspec colwidth="${node.getAttribute('labelwidth', 15)}*"/>`
208
+ )
209
+ result.push(
210
+ `<colspec colwidth="${node.getAttribute('itemwidth', 85)}*"/>`
211
+ )
212
+ result.push('<tbody valign="top">')
213
+ for (const [terms, dd] of node.getItems()) {
214
+ result.push('<row>\n<entry>')
215
+ for (const dt of terms)
216
+ result.push(`<simpara>${dt.getText()}</simpara>`)
217
+ result.push('</entry>\n<entry>')
218
+ if (dd) {
219
+ if (dd.hasText()) result.push(`<simpara>${dd.getText()}</simpara>`)
220
+ if (dd.hasBlocks()) result.push(await dd.content())
221
+ }
222
+ result.push('</entry>\n</row>')
223
+ }
224
+ result.push(`</tbody>\n</tgroup>\n</${tagName}>`)
225
+ } else {
226
+ const tags = DLIST_TAGS[node.style] ?? DLIST_TAGS_DEFAULT
227
+ const {
228
+ list: listTag,
229
+ entry: entryTag,
230
+ label: labelTag,
231
+ term: termTag,
232
+ item: itemTag,
233
+ } = tags
234
+ if (listTag) {
235
+ result.push(
236
+ `<${listTag}${this._commonAttributes(node.id, node.role, node.reftext)}>`
237
+ )
238
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
239
+ }
240
+ for (const [terms, dd] of node.getItems()) {
241
+ result.push(`<${entryTag}>`)
242
+ if (labelTag) result.push(`<${labelTag}>`)
243
+ for (const dt of terms)
244
+ result.push(`<${termTag}>${dt.getText()}</${termTag}>`)
245
+ if (labelTag) result.push(`</${labelTag}>`)
246
+ result.push(`<${itemTag}>`)
247
+ if (dd) {
248
+ if (dd.hasText()) result.push(`<simpara>${dd.getText()}</simpara>`)
249
+ if (dd.hasBlocks()) result.push(await dd.content())
250
+ }
251
+ result.push(`</${itemTag}>`)
252
+ result.push(`</${entryTag}>`)
253
+ }
254
+ if (listTag) result.push(`</${listTag}>`)
255
+ }
256
+ return result.join(LF)
257
+ }
258
+
259
+ async convert_example(node) {
260
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
261
+ if (node.hasTitle()) {
262
+ return `<example${commonAttrs}>\n<title>${node.title}</title>\n${await this._encloseContent(node)}\n</example>`
263
+ }
264
+ return `<informalexample${commonAttrs}>\n${await this._encloseContent(node)}\n</informalexample>`
265
+ }
266
+
267
+ async convert_floating_title(node) {
268
+ return `<bridgehead${this._commonAttributes(node.id, node.role, node.reftext)} renderas="sect${node.level}">${node.title}</bridgehead>`
269
+ }
270
+
271
+ async convert_image(node) {
272
+ const alignAttribute = node.hasAttribute('align')
273
+ ? ` align="${node.getAttribute('align')}"`
274
+ : ''
275
+ const mediaobject = `<mediaobject>\n<imageobject>\n<imagedata fileref="${await node.imageUri(node.getAttribute('target'))}"${this._imageSizeAttributes(node.attributes)}${alignAttribute}/>\n</imageobject>\n<textobject><phrase>${node.getAlt()}</phrase></textobject>\n</mediaobject>`
276
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
277
+ if (node.hasTitle()) {
278
+ return `<figure${commonAttrs}>\n<title>${node.title}</title>\n${mediaobject}\n</figure>`
279
+ }
280
+ return `<informalfigure${commonAttrs}>\n${mediaobject}\n</informalfigure>`
281
+ }
282
+
283
+ async convert_listing(node) {
284
+ const informal = !node.hasTitle()
285
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
286
+ let wrappedContent
287
+ if (node.style === 'source') {
288
+ const attrs = node.attributes
289
+ let numberingAttrs
290
+ if (node.hasOption('linenums')) {
291
+ numberingAttrs =
292
+ 'start' in attrs
293
+ ? ` linenumbering="numbered" startinglinenumber="${parseInt(attrs.start, 10)}"`
294
+ : ' linenumbering="numbered"'
295
+ } else {
296
+ numberingAttrs = ' linenumbering="unnumbered"'
297
+ }
298
+ if ('language' in attrs) {
299
+ wrappedContent = `<programlisting${informal ? commonAttrs : ''} language="${attrs.language}"${numberingAttrs}>${await node.content()}</programlisting>`
300
+ } else {
301
+ wrappedContent = `<screen${informal ? commonAttrs : ''}${numberingAttrs}>${await node.content()}</screen>`
302
+ }
303
+ } else {
304
+ wrappedContent = `<screen${informal ? commonAttrs : ''}>${await node.content()}</screen>`
305
+ }
306
+ if (informal) return wrappedContent
307
+ return `<formalpara${commonAttrs}>\n<title>${node.title}</title>\n<para>\n${wrappedContent}\n</para>\n</formalpara>`
308
+ }
309
+
310
+ async convert_literal(node) {
311
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
312
+ if (node.hasTitle()) {
313
+ return `<formalpara${commonAttrs}>\n<title>${node.title}</title>\n<para>\n<literallayout class="monospaced">${await node.content()}</literallayout>\n</para>\n</formalpara>`
314
+ }
315
+ return `<literallayout${commonAttrs} class="monospaced">${await node.content()}</literallayout>`
316
+ }
317
+
318
+ async convert_pass(node) {
319
+ return await node.content()
320
+ }
321
+
322
+ async convert_stem(node) {
323
+ let equation
324
+ const idx = node.subs ? node.subs.indexOf('specialcharacters') : -1
325
+ if (idx !== -1) {
326
+ node.subs.splice(idx, 1)
327
+ equation = await node.content()
328
+ node.subs.splice(idx, 0, 'specialcharacters')
329
+ } else {
330
+ equation = await node.content()
331
+ }
332
+ let equationData
333
+ if (node.style === 'asciimath') {
334
+ // NOTE: No AsciiMath-to-MathML conversion available in JS; use CDATA fallback
335
+ equationData = `<mathphrase><![CDATA[${equation}]]></mathphrase>`
336
+ } else {
337
+ // unhandled math (latexmath); pass source to alt and required mathphrase — dblatex will process alt as LaTeX math
338
+ equationData = `<alt><![CDATA[${equation}]]></alt>\n<mathphrase><![CDATA[${equation}]]></mathphrase>`
339
+ }
340
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
341
+ if (node.hasTitle()) {
342
+ return `<equation${commonAttrs}>\n<title>${node.title}</title>\n${equationData}\n</equation>`
343
+ }
344
+ return `<informalequation${commonAttrs}>\n${equationData}\n</informalequation>`
345
+ }
346
+
347
+ async convert_olist(node) {
348
+ const result = []
349
+ const numAttribute = node.style ? ` numeration="${node.style}"` : ''
350
+ const startAttribute = node.hasAttribute('start')
351
+ ? ` startingnumber="${node.getAttribute('start')}"`
352
+ : ''
353
+ result.push(
354
+ `<orderedlist${this._commonAttributes(node.id, node.role, node.reftext)}${numAttribute}${startAttribute}>`
355
+ )
356
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
357
+ for (const item of node.getItems()) {
358
+ result.push(`<listitem${this._commonAttributes(item.id, item.role)}>`)
359
+ result.push(`<simpara>${item.getText()}</simpara>`)
360
+ if (item.hasBlocks()) result.push(await item.content())
361
+ result.push('</listitem>')
362
+ }
363
+ result.push('</orderedlist>')
364
+ return result.join(LF)
365
+ }
366
+
367
+ async convert_open(node) {
368
+ const id = node.id
369
+ const role = node.role
370
+ const reftext = node.reftext
371
+ switch (node.style) {
372
+ case 'abstract': {
373
+ if (
374
+ node.getParent() === node.document &&
375
+ node.document.doctype === 'book'
376
+ ) {
377
+ this.logger.warn(
378
+ 'abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content.'
379
+ )
380
+ return ''
381
+ }
382
+ let res = `<abstract>\n${this._titleTag(node)}${await this._encloseContent(node)}\n</abstract>`
383
+ const parent = node.getParent()
384
+ if (
385
+ this.backend === 'docbook5' &&
386
+ !node.hasOption('root') &&
387
+ (parent.context === 'open'
388
+ ? parent.style === 'partintro'
389
+ : parent.context === 'section' &&
390
+ parent.sectname === 'partintro') &&
391
+ parent.blocks[0] === node
392
+ ) {
393
+ res = `<info>\n${res}\n</info>`
394
+ }
395
+ return res
396
+ }
397
+ case 'partintro': {
398
+ if (
399
+ node.level === 0 &&
400
+ node.getParent().context === 'section' &&
401
+ node.document.doctype === 'book'
402
+ ) {
403
+ return `<partintro${this._commonAttributes(id, role, reftext)}>\n${this._titleTag(node)}${await this._encloseContent(node)}\n</partintro>`
404
+ }
405
+ this.logger.error(
406
+ 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
407
+ )
408
+ return ''
409
+ }
410
+ default: {
411
+ if (node.hasTitle()) {
412
+ const contentSpacer = node.contentModel === 'compound' ? LF : ''
413
+ return `<formalpara${this._commonAttributes(id, role, reftext)}>\n<title>${node.title}</title>\n<para>${contentSpacer}${await node.content()}${contentSpacer}</para>\n</formalpara>`
414
+ } else if (id || role) {
415
+ if (node.contentModel === 'compound') {
416
+ return `<para${this._commonAttributes(id, role, reftext)}>\n${await node.content()}\n</para>`
417
+ }
418
+ return `<simpara${this._commonAttributes(id, role, reftext)}>${await node.content()}</simpara>`
419
+ }
420
+ return await this._encloseContent(node)
421
+ }
422
+ }
423
+ }
424
+
425
+ async convert_page_break(_node) {
426
+ return '<simpara><?asciidoc-pagebreak?></simpara>'
427
+ }
428
+
429
+ async convert_paragraph(node) {
430
+ const commonAttrs = this._commonAttributes(node.id, node.role, node.reftext)
431
+ if (node.hasTitle()) {
432
+ return `<formalpara${commonAttrs}>\n<title>${node.title}</title>\n<para>${await node.content()}</para>\n</formalpara>`
433
+ }
434
+ return `<simpara${commonAttrs}>${await node.content()}</simpara>`
435
+ }
436
+
437
+ async convert_preamble(node) {
438
+ if (node.document.doctype === 'book') {
439
+ return `<preface${this._commonAttributes(node.id, node.role, node.reftext)}>\n${this._titleTag(node, false)}${await node.content()}\n</preface>`
440
+ }
441
+ return await node.content()
442
+ }
443
+
444
+ async convert_quote(node) {
445
+ return await this._blockquoteTag(
446
+ node,
447
+ node.hasRole('epigraph') ? 'epigraph' : null,
448
+ async () => await this._encloseContent(node)
449
+ )
450
+ }
451
+
452
+ async convert_thematic_break(_node) {
453
+ return '<simpara><?asciidoc-hr?></simpara>'
454
+ }
455
+
456
+ async convert_sidebar(node) {
457
+ return `<sidebar${this._commonAttributes(node.id, node.role, node.reftext)}>\n${this._titleTag(node)}${await this._encloseContent(node)}\n</sidebar>`
458
+ }
459
+
460
+ async convert_table(node) {
461
+ let hasBody = false
462
+ const result = []
463
+ const pgwideAttribute = node.hasOption('pgwide') ? ' pgwide="1"' : ''
464
+ let frame = node.getAttribute('frame', 'all', 'table-frame')
465
+ if (frame === 'ends') frame = 'topbot'
466
+ const grid = node.getAttribute('grid', null, 'table-grid')
467
+ const tagName = node.hasTitle() ? 'table' : 'informaltable'
468
+ const orientAttr = node.hasAttribute(
469
+ 'orientation',
470
+ 'landscape',
471
+ 'table-orientation'
472
+ )
473
+ ? ' orient="land"'
474
+ : ''
475
+ result.push(
476
+ `<${tagName}${this._commonAttributes(node.id, node.role, node.reftext)}${pgwideAttribute} frame="${frame}" rowsep="${['none', 'cols'].includes(grid) ? 0 : 1}" colsep="${['none', 'rows'].includes(grid) ? 0 : 1}"${orientAttr}>`
477
+ )
478
+ if (node.hasOption('unbreakable')) {
479
+ result.push('<?dbfo keep-together="always"?>')
480
+ } else if (node.hasOption('breakable')) {
481
+ result.push('<?dbfo keep-together="auto"?>')
482
+ }
483
+ if (tagName === 'table') result.push(`<title>${node.title}</title>`)
484
+ let colWidthKey
485
+ const width = node.hasAttribute('width') ? node.getAttribute('width') : null
486
+ if (width) {
487
+ for (const piName of TABLE_PI_NAMES)
488
+ result.push(`<?${piName} table-width="${width}"?>`)
489
+ colWidthKey = 'colabswidth'
490
+ } else {
491
+ colWidthKey = 'colpcwidth'
492
+ }
493
+ result.push(`<tgroup cols="${node.getAttribute('colcount')}">`)
494
+ for (const col of node.columns) {
495
+ result.push(
496
+ `<colspec colname="col_${col.getAttribute('colnumber')}" colwidth="${col.getAttribute(colWidthKey)}*"/>`
497
+ )
498
+ }
499
+ for (const [tsec, sectionRows] of node.rows.bySection()) {
500
+ if (!sectionRows || sectionRows.length === 0) continue
501
+ if (tsec === 'body') hasBody = true
502
+ result.push(`<t${tsec}>`)
503
+ for (const row of sectionRows) {
504
+ result.push('<row>')
505
+ for (const cell of row) {
506
+ const colspanAttribute = cell.colspan
507
+ ? ` namest="col_${cell.column.getAttribute('colnumber')}" nameend="col_${cell.column.getAttribute('colnumber') + cell.colspan - 1}"`
508
+ : ''
509
+ const rowspanAttribute = cell.rowspan
510
+ ? ` morerows="${cell.rowspan - 1}"`
511
+ : ''
512
+ const entryStart = `<entry align="${cell.getAttribute('halign')}" valign="${cell.getAttribute('valign')}"${colspanAttribute}${rowspanAttribute}>`
513
+ let cellContent
514
+ if (tsec === 'head') {
515
+ cellContent = cell.text
516
+ } else {
517
+ switch (cell.style) {
518
+ case 'asciidoc':
519
+ cellContent = await cell.content()
520
+ break
521
+ case 'literal':
522
+ cellContent = `<literallayout class="monospaced">${cell.text}</literallayout>`
523
+ break
524
+ case 'header': {
525
+ const parts = await cell.content()
526
+ cellContent =
527
+ parts.length === 0
528
+ ? ''
529
+ : `<simpara><emphasis role="strong">${parts.join('</emphasis></simpara><simpara><emphasis role="strong">')}</emphasis></simpara>`
530
+ break
531
+ }
532
+ default: {
533
+ const parts = await cell.content()
534
+ cellContent =
535
+ parts.length === 0
536
+ ? ''
537
+ : `<simpara>${parts.join('</simpara><simpara>')}</simpara>`
538
+ }
539
+ }
540
+ }
541
+ const entryEnd = node.document.hasAttribute('cellbgcolor')
542
+ ? `<?dbfo bgcolor="${node.document.getAttribute('cellbgcolor')}"?></entry>`
543
+ : '</entry>'
544
+ result.push(`${entryStart}${cellContent}${entryEnd}`)
545
+ }
546
+ result.push('</row>')
547
+ }
548
+ result.push(`</t${tsec}>`)
549
+ }
550
+ result.push('</tgroup>')
551
+ result.push(`</${tagName}>`)
552
+ if (!hasBody) this.logger.warn('tables must have at least one body row')
553
+ return result.join(LF)
554
+ }
555
+
556
+ async convert_toc(_node) {
557
+ return ''
558
+ }
559
+
560
+ async convert_ulist(node) {
561
+ const result = []
562
+ if (node.style === 'bibliography') {
563
+ result.push(
564
+ `<bibliodiv${this._commonAttributes(node.id, node.role, node.reftext)}>`
565
+ )
566
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
567
+ for (const item of node.getItems()) {
568
+ result.push('<bibliomixed>')
569
+ result.push(`<bibliomisc>${item.getText()}</bibliomisc>`)
570
+ if (item.hasBlocks()) result.push(await item.content())
571
+ result.push('</bibliomixed>')
572
+ }
573
+ result.push('</bibliodiv>')
574
+ } else {
575
+ const checklist = node.hasOption('checklist')
576
+ const markType = checklist ? 'none' : node.style
577
+ const markAttribute = markType ? ` mark="${markType}"` : ''
578
+ result.push(
579
+ `<itemizedlist${this._commonAttributes(node.id, node.role, node.reftext)}${markAttribute}>`
580
+ )
581
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
582
+ for (const item of node.getItems()) {
583
+ const textMarker =
584
+ checklist && item.hasAttribute('checkbox')
585
+ ? item.hasAttribute('checked')
586
+ ? '&#10003; '
587
+ : '&#10063; '
588
+ : ''
589
+ result.push(`<listitem${this._commonAttributes(item.id, item.role)}>`)
590
+ result.push(`<simpara>${textMarker}${item.getText()}</simpara>`)
591
+ if (item.hasBlocks()) result.push(await item.content())
592
+ result.push('</listitem>')
593
+ }
594
+ result.push('</itemizedlist>')
595
+ }
596
+ return result.join(LF)
597
+ }
598
+
599
+ async convert_verse(node) {
600
+ return await this._blockquoteTag(
601
+ node,
602
+ node.hasRole('epigraph') ? 'epigraph' : null,
603
+ async () => `<literallayout>${await node.content()}</literallayout>`
604
+ )
605
+ }
606
+
607
+ async convert_video(_node) {
608
+ return ''
609
+ }
610
+
611
+ async convert_inline_anchor(node) {
612
+ switch (node.type) {
613
+ case 'ref':
614
+ return `<anchor${this._commonAttributes(node.id, null, node.reftext || `[${node.id}]`)}/>`
615
+ case 'xref': {
616
+ const path = node.attributes.path
617
+ if (path) {
618
+ return `<link xl:href="${node.target}">${node.text || path}</link>`
619
+ }
620
+ let linkend = node.attributes.refid
621
+ if (!linkend) {
622
+ const rootDoc = this._getRootDocument(node)
623
+ linkend =
624
+ rootDoc.id ??
625
+ (this._rootDocId ??= this._generateDocumentId(rootDoc))
626
+ }
627
+ return node.text
628
+ ? `<link linkend="${linkend}">${node.text}</link>`
629
+ : `<xref linkend="${linkend}"/>`
630
+ }
631
+ case 'link':
632
+ return `<link xl:href="${node.target}">${node.text}</link>`
633
+ case 'bibref': {
634
+ const text = `[${node.reftext || node.id}]`
635
+ return `<anchor${this._commonAttributes(node.id, null, text)}/>${text}`
636
+ }
637
+ default:
638
+ this.logger.warn(`unknown anchor type: ${node.type}`)
639
+ return null
640
+ }
641
+ }
642
+
643
+ async convert_inline_break(node) {
644
+ return `${node.text}<?asciidoc-br?>`
645
+ }
646
+
647
+ async convert_inline_button(node) {
648
+ return `<guibutton>${node.text}</guibutton>`
649
+ }
650
+
651
+ async convert_inline_callout(node) {
652
+ return `<co${this._commonAttributes(node.id)}/>`
653
+ }
654
+
655
+ async convert_inline_footnote(node) {
656
+ if (node.type === 'xref') {
657
+ return `<footnoteref linkend="${node.target}"/>`
658
+ }
659
+ return `<footnote${this._commonAttributes(node.id)}><simpara>${node.text}</simpara></footnote>`
660
+ }
661
+
662
+ async convert_inline_image(node) {
663
+ const fileref =
664
+ node.type === 'icon'
665
+ ? await node.iconUri(node.target)
666
+ : await node.imageUri(node.target)
667
+ const img = `<inlinemediaobject${this._commonAttributes(node.id, node.role)}>\n<imageobject>\n<imagedata fileref="${fileref}"${this._imageSizeAttributes(node.attributes)}/>\n</imageobject>\n<textobject><phrase>${node.getAlt()}</phrase></textobject>\n</inlinemediaobject>`
668
+ if (node.type !== 'icon' && node.hasAttribute('link')) {
669
+ const linkHref = node.getAttribute('link')
670
+ return `<link xl:href="${linkHref === 'self' ? fileref : linkHref}">${img}</link>`
671
+ }
672
+ return img
673
+ }
674
+
675
+ async convert_inline_indexterm(node) {
676
+ let rel = ''
677
+ const see = node.getAttribute('see')
678
+ if (see) {
679
+ rel = `\n<see>${see}</see>`
680
+ } else {
681
+ const seeAlsoList = node.getAttribute('see-also')
682
+ if (seeAlsoList) {
683
+ rel = seeAlsoList.map((s) => `\n<seealso>${s}</seealso>`).join('')
684
+ }
685
+ }
686
+ if (node.type === 'visible') {
687
+ return `<indexterm>\n<primary>${node.text}</primary>${rel}\n</indexterm>${node.text}`
688
+ }
689
+ const terms = node.getAttribute('terms')
690
+ const numterms = terms.length
691
+ const indexPromote = node.document.hasOption('indexterm-promotion')
692
+ if (numterms > 2) {
693
+ return `<indexterm>\n<primary>${terms[0]}</primary><secondary>${terms[1]}</secondary><tertiary>${terms[2]}</tertiary>${rel}\n</indexterm>${indexPromote ? `\n<indexterm>\n<primary>${terms[1]}</primary><secondary>${terms[2]}</secondary>\n</indexterm>\n<indexterm>\n<primary>${terms[2]}</primary>\n</indexterm>` : ''}`
694
+ } else if (numterms > 1) {
695
+ return `<indexterm>\n<primary>${terms[0]}</primary><secondary>${terms[1]}</secondary>${rel}\n</indexterm>${indexPromote ? `\n<indexterm>\n<primary>${terms[1]}</primary>\n</indexterm>` : ''}`
696
+ }
697
+ return `<indexterm>\n<primary>${terms[0]}</primary>${rel}\n</indexterm>`
698
+ }
699
+
700
+ async convert_inline_kbd(node) {
701
+ const keys = node.getAttribute('keys')
702
+ if (keys.length === 1) {
703
+ return `<keycap>${keys[0]}</keycap>`
704
+ }
705
+ return `<keycombo><keycap>${keys.join('</keycap><keycap>')}</keycap></keycombo>`
706
+ }
707
+
708
+ async convert_inline_menu(node) {
709
+ const menu = node.getAttribute('menu')
710
+ const submenus = node.getAttribute('submenus')
711
+ if (!submenus || submenus.length === 0) {
712
+ const menuitem = node.getAttribute('menuitem')
713
+ if (menuitem) {
714
+ return `<menuchoice><guimenu>${menu}</guimenu> <guimenuitem>${menuitem}</guimenuitem></menuchoice>`
715
+ }
716
+ return `<guimenu>${menu}</guimenu>`
717
+ }
718
+ return `<menuchoice><guimenu>${menu}</guimenu> <guisubmenu>${submenus.join('</guisubmenu> <guisubmenu>')}</guisubmenu> <guimenuitem>${node.getAttribute('menuitem')}</guimenuitem></menuchoice>`
719
+ }
720
+
721
+ async convert_inline_quoted(node) {
722
+ const type = node.type
723
+ if (type === 'asciimath' || type === 'latexmath') {
724
+ const equation = node.text
725
+ if (type === 'asciimath') {
726
+ return `<inlineequation><mathphrase><![CDATA[${equation}]]></mathphrase></inlineequation>`
727
+ }
728
+ return `<inlineequation><alt><![CDATA[${equation}]]></alt><mathphrase><![CDATA[${equation}]]></mathphrase></inlineequation>`
729
+ }
730
+ const [open, close, supportsPhrase] = QUOTE_TAGS[type] ?? QUOTE_TAGS_DEFAULT
731
+ const text = node.text
732
+ let quotedText
733
+ if (node.role) {
734
+ if (supportsPhrase) {
735
+ quotedText = `${open}<phrase role="${node.role}">${text}</phrase>${close}`
736
+ } else {
737
+ // chop the closing > from open tag to insert role attribute
738
+ quotedText = `${open.slice(0, -1)} role="${node.role}">${text}${close}`
739
+ }
740
+ } else {
741
+ quotedText = `${open}${text}${close}`
742
+ }
743
+ return node.id
744
+ ? `<anchor${this._commonAttributes(node.id)}/>${quotedText}`
745
+ : quotedText
746
+ }
747
+
748
+ // Private helpers
749
+
750
+ /**
751
+ * @internal
752
+ * @private
753
+ */
754
+ _commonAttributes(id, role = null, reftext = null) {
755
+ let attrs = ''
756
+ if (id) {
757
+ attrs = ` xml:id="${id}"${role ? ` role="${role}"` : ''}`
758
+ } else if (role) {
759
+ attrs = ` role="${role}"`
760
+ }
761
+ if (reftext) {
762
+ let sanitized = reftext
763
+ if (sanitized.includes('<')) {
764
+ sanitized = sanitized.replace(XmlSanitizeRx, '')
765
+ if (sanitized.includes(' '))
766
+ sanitized = sanitized.replace(/ {2,}/g, ' ').trim()
767
+ }
768
+ if (sanitized.includes('"')) sanitized = sanitized.replace(/"/g, '&quot;')
769
+ return `${attrs} xreflabel="${sanitized}"`
770
+ }
771
+ return attrs
772
+ }
773
+
774
+ /**
775
+ * @internal
776
+ * @private
777
+ */
778
+ _imageSizeAttributes(attributes) {
779
+ if ('scaledwidth' in attributes) {
780
+ return ` width="${attributes.scaledwidth}"`
781
+ } else if ('scale' in attributes) {
782
+ return ` scale="${attributes.scale}"`
783
+ }
784
+ const widthAttr =
785
+ 'width' in attributes ? ` contentwidth="${attributes.width}"` : ''
786
+ const depthAttr =
787
+ 'height' in attributes ? ` contentdepth="${attributes.height}"` : ''
788
+ return `${widthAttr}${depthAttr}`
789
+ }
790
+
791
+ /**
792
+ * @internal
793
+ * @private
794
+ */
795
+ _authorTag(doc, author) {
796
+ const result = ['<author>', '<personname>']
797
+ if (author.firstname)
798
+ result.push(
799
+ `<firstname>${doc.subReplacements(author.firstname)}</firstname>`
800
+ )
801
+ if (author.middlename)
802
+ result.push(
803
+ `<othername>${doc.subReplacements(author.middlename)}</othername>`
804
+ )
805
+ if (author.lastname)
806
+ result.push(`<surname>${doc.subReplacements(author.lastname)}</surname>`)
807
+ result.push('</personname>')
808
+ if (author.email) result.push(`<email>${author.email}</email>`)
809
+ result.push('</author>')
810
+ return result.join(LF)
811
+ }
812
+
813
+ /**
814
+ * @internal
815
+ * @private
816
+ */
817
+ async _documentInfoTag(doc, abstract) {
818
+ const result = ['<info>']
819
+ if (!doc.isNotitle()) {
820
+ const title = doc.doctitle({ partition: true, use_fallback: true })
821
+ if (title?.subtitle) {
822
+ result.push(
823
+ `<title>${title.main}</title>\n<subtitle>${title.subtitle}</subtitle>`
824
+ )
825
+ } else if (title) {
826
+ result.push(`<title>${title}</title>`)
827
+ }
828
+ }
829
+ const date = doc.hasAttribute('revdate')
830
+ ? doc.getAttribute('revdate')
831
+ : doc.hasAttribute('reproducible')
832
+ ? null
833
+ : doc.getAttribute('docdate')
834
+ if (date) result.push(`<date>${date}</date>`)
835
+ if (doc.hasAttribute('copyright')) {
836
+ const m = CopyrightRx.exec(doc.getAttribute('copyright'))
837
+ if (m) {
838
+ result.push('<copyright>')
839
+ result.push(`<holder>${m[1]}</holder>`)
840
+ if (m[2]) result.push(`<year>${m[2]}</year>`)
841
+ result.push('</copyright>')
842
+ }
843
+ }
844
+ if (doc.hasHeader()) {
845
+ const authors = doc.authors()
846
+ if (authors.length > 0) {
847
+ if (authors.length > 1) {
848
+ result.push('<authorgroup>')
849
+ for (const author of authors)
850
+ result.push(this._authorTag(doc, author))
851
+ result.push('</authorgroup>')
852
+ } else {
853
+ const author = authors[0]
854
+ result.push(this._authorTag(doc, author))
855
+ if (author.initials)
856
+ result.push(`<authorinitials>${author.initials}</authorinitials>`)
857
+ }
858
+ }
859
+ if (
860
+ doc.hasAttribute('revdate') &&
861
+ (doc.hasAttribute('revnumber') || doc.hasAttribute('revremark'))
862
+ ) {
863
+ result.push('<revhistory>\n<revision>')
864
+ if (doc.hasAttribute('revnumber'))
865
+ result.push(`<revnumber>${doc.getAttribute('revnumber')}</revnumber>`)
866
+ if (doc.hasAttribute('revdate'))
867
+ result.push(`<date>${doc.getAttribute('revdate')}</date>`)
868
+ if (doc.hasAttribute('authorinitials'))
869
+ result.push(
870
+ `<authorinitials>${doc.getAttribute('authorinitials')}</authorinitials>`
871
+ )
872
+ if (doc.hasAttribute('revremark'))
873
+ result.push(`<revremark>${doc.getAttribute('revremark')}</revremark>`)
874
+ result.push('</revision>\n</revhistory>')
875
+ }
876
+ if (
877
+ doc.hasAttribute('front-cover-image') ||
878
+ doc.hasAttribute('back-cover-image')
879
+ ) {
880
+ const backCoverTag = await this._coverTag(doc, 'back')
881
+ if (backCoverTag) {
882
+ result.push(await this._coverTag(doc, 'front', true))
883
+ result.push(backCoverTag)
884
+ } else {
885
+ const frontCoverTag = await this._coverTag(doc, 'front')
886
+ if (frontCoverTag) result.push(frontCoverTag)
887
+ }
888
+ }
889
+ if (doc.hasAttribute('orgname'))
890
+ result.push(`<orgname>${doc.getAttribute('orgname')}</orgname>`)
891
+ const docinfo = await doc.docinfo()
892
+ if (docinfo) result.push(docinfo)
893
+ }
894
+ if (abstract) {
895
+ abstract.setAttribute('root-option', '')
896
+ result.push(await this.convert(abstract, abstract.nodeName))
897
+ abstract.removeAttribute('root-option')
898
+ }
899
+ result.push('</info>')
900
+ return result.join(LF)
901
+ }
902
+
903
+ /**
904
+ * @internal
905
+ * @private
906
+ */
907
+ _findRootAbstract(doc) {
908
+ if (!doc.hasBlocks()) return null
909
+ let firstBlock = doc.blocks[0]
910
+ if (firstBlock.context === 'preamble') {
911
+ if (!firstBlock.hasBlocks()) return null
912
+ firstBlock = firstBlock.blocks[0]
913
+ } else if (firstBlock.context === 'section') {
914
+ if (firstBlock.sectname === 'abstract') return firstBlock
915
+ if (firstBlock.sectname !== 'preface' || !firstBlock.hasBlocks())
916
+ return null
917
+ firstBlock = firstBlock.blocks[0]
918
+ }
919
+ return firstBlock.style === 'abstract' && firstBlock.context === 'open'
920
+ ? firstBlock
921
+ : null
922
+ }
923
+
924
+ /**
925
+ * @internal
926
+ * @private
927
+ */
928
+ _extractAbstract(document, abstract) {
929
+ let parent = abstract.getParent()
930
+ let toDelete = abstract
931
+ while (parent !== document && parent.blocks.length === 1) {
932
+ toDelete = parent
933
+ parent = parent.getParent()
934
+ }
935
+ parent.blocks.splice(parent.blocks.indexOf(toDelete), 1)
936
+ return abstract
937
+ }
938
+
939
+ /**
940
+ * @internal
941
+ * @private
942
+ */
943
+ _restoreAbstract(abstract) {
944
+ abstract.getParent().blocks.unshift(abstract)
945
+ }
946
+
947
+ /**
948
+ * @internal
949
+ * @private
950
+ */
951
+ _getRootDocument(node) {
952
+ let doc = node.document
953
+ while (doc.isNested()) doc = doc.parentDocument
954
+ return doc
955
+ }
956
+
957
+ /**
958
+ * @internal
959
+ * @private
960
+ */
961
+ _generateDocumentId(doc) {
962
+ return `__${doc.doctype}-root__`
963
+ }
964
+
965
+ /**
966
+ * @internal
967
+ * @private
968
+ */
969
+ async _encloseContent(node) {
970
+ return node.contentModel === 'compound'
971
+ ? await node.content()
972
+ : `<simpara>${await node.content()}</simpara>`
973
+ }
974
+
975
+ /**
976
+ * @internal
977
+ * @private
978
+ */
979
+ _titleTag(node, optional = true) {
980
+ if (optional && !node.hasTitle()) return ''
981
+ return `<title>${node.title ?? ''}</title>\n`
982
+ }
983
+
984
+ /**
985
+ * @internal
986
+ * @private
987
+ */
988
+ async _coverTag(doc, face, usePlaceholder = false) {
989
+ const coverImage = doc.getAttribute(`${face}-cover-image`)
990
+ if (coverImage) {
991
+ let fileref = coverImage
992
+ const sizeAttrs = ''
993
+ // Check if it's an image macro (contains ':')
994
+ if (coverImage.includes(':')) {
995
+ const m = /^image::?(\S|\S.*?\S)\[(.*?)?\]$/.exec(coverImage)
996
+ if (m) {
997
+ fileref = await doc.imageUri(m[1])
998
+ // size attrs parsing omitted for simplicity
999
+ }
1000
+ }
1001
+ return `<cover role="${face}">\n<mediaobject>\n<imageobject>\n<imagedata fileref="${fileref}"${sizeAttrs}/>\n</imageobject>\n</mediaobject>\n</cover>`
1002
+ }
1003
+ if (usePlaceholder) return `<cover role="${face}"/>`
1004
+ return null
1005
+ }
1006
+
1007
+ /**
1008
+ * @internal
1009
+ * @private
1010
+ */
1011
+ async _blockquoteTag(node, tagName, contentFn) {
1012
+ const tag = tagName || 'blockquote'
1013
+ const result = [
1014
+ `<${tag}${this._commonAttributes(node.id, node.role, node.reftext)}>`,
1015
+ ]
1016
+ if (node.hasTitle()) result.push(`<title>${node.title}</title>`)
1017
+ if (node.hasAttribute('attribution') || node.hasAttribute('citetitle')) {
1018
+ result.push('<attribution>')
1019
+ if (node.hasAttribute('attribution'))
1020
+ result.push(node.getAttribute('attribution'))
1021
+ if (node.hasAttribute('citetitle'))
1022
+ result.push(`<citetitle>${node.getAttribute('citetitle')}</citetitle>`)
1023
+ result.push('</attribution>')
1024
+ }
1025
+ result.push(await contentFn())
1026
+ result.push(`</${tag}>`)
1027
+ return result.join(LF)
1028
+ }
1029
+ }
1030
+
1031
+ export default DocBook5Converter