@asciidoctor/core 3.0.4 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -10
- package/build/browser/index.js +24154 -0
- package/build/node/index.cjs +24735 -0
- package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
- package/package.json +53 -100
- package/src/abstract_block.js +849 -0
- package/src/abstract_node.js +954 -0
- package/src/attribute_entry.js +12 -0
- package/src/attribute_list.js +380 -0
- package/src/block.js +168 -0
- package/src/browser/asset.js +22 -0
- package/src/browser/reader.js +138 -0
- package/src/browser.js +121 -0
- package/src/callouts.js +85 -0
- package/src/compliance.js +54 -0
- package/src/constants.js +665 -0
- package/src/convert.js +370 -0
- package/src/converter/composite.js +83 -0
- package/src/converter/docbook5.js +1031 -0
- package/src/converter/html5.js +1899 -0
- package/src/converter/manpage.js +935 -0
- package/src/converter/template.js +459 -0
- package/src/converter.js +478 -0
- package/src/data/stylesheet-data.js +2 -0
- package/src/document.js +2134 -0
- package/src/extensions.js +1952 -0
- package/src/footnote.js +28 -0
- package/src/helpers.js +355 -0
- package/src/index.js +138 -0
- package/src/inline.js +158 -0
- package/src/list.js +240 -0
- package/src/load.js +276 -0
- package/src/logging.js +526 -0
- package/src/parser.js +3661 -0
- package/src/path_resolver.js +472 -0
- package/src/reader.js +1755 -0
- package/src/rx.js +829 -0
- package/src/section.js +354 -0
- package/src/stylesheets.js +30 -0
- package/src/substitutors.js +2241 -0
- package/src/syntaxHighlighter/highlightjs.js +90 -0
- package/src/syntaxHighlighter/html_pipeline.js +33 -0
- package/src/syntax_highlighter.js +304 -0
- package/src/table.js +952 -0
- package/src/timings.js +78 -0
- package/types/abstract_block.d.ts +343 -0
- package/types/abstract_node.d.ts +471 -0
- package/types/attribute_entry.d.ts +7 -0
- package/types/attribute_list.d.ts +52 -0
- package/types/block.d.ts +55 -0
- package/types/browser/asset.d.ts +7 -0
- package/types/browser/reader.d.ts +29 -0
- package/types/callouts.d.ts +36 -0
- package/types/compliance.d.ts +23 -0
- package/types/constants.d.ts +268 -0
- package/types/convert.d.ts +34 -0
- package/types/converter/composite.d.ts +20 -0
- package/types/converter/docbook5.d.ts +41 -0
- package/types/converter/html5.d.ts +51 -0
- package/types/converter/manpage.d.ts +59 -0
- package/types/converter/template.d.ts +83 -0
- package/types/converter.d.ts +150 -0
- package/types/data/stylesheet-data.d.ts +2 -0
- package/types/document.d.ts +495 -0
- package/types/extensions.d.ts +876 -0
- package/types/footnote.d.ts +18 -0
- package/types/helpers.d.ts +146 -0
- package/types/index.d.ts +73 -3731
- package/types/inline.d.ts +69 -0
- package/types/list.d.ts +114 -0
- package/types/load.d.ts +39 -0
- package/types/logging.d.ts +187 -0
- package/types/parser.d.ts +114 -0
- package/types/path_resolver.d.ts +103 -0
- package/types/reader.d.ts +184 -0
- package/types/rx.d.ts +513 -0
- package/types/section.d.ts +122 -0
- package/types/stylesheets.d.ts +10 -0
- package/types/substitutors.d.ts +208 -0
- package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
- package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
- package/types/syntax_highlighter.d.ts +167 -0
- package/types/table.d.ts +231 -0
- package/types/timings.d.ts +25 -0
- package/types/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/dist/browser/asciidoctor.js +0 -47654
- package/dist/browser/asciidoctor.min.js +0 -1452
- package/dist/graalvm/asciidoctor.js +0 -47402
- package/dist/node/asciidoctor.cjs +0 -21567
- package/dist/node/asciidoctor.js +0 -23037
|
@@ -0,0 +1,1899 @@
|
|
|
1
|
+
// ESM port of converter/html5.rb
|
|
2
|
+
//
|
|
3
|
+
// Ruby-to-JavaScript notes:
|
|
4
|
+
// - @xml_mode / @void_element_slash → this._xmlMode / this._voidSlash
|
|
5
|
+
// - Ruby symbol keys in QUOTE_TAGS → plain string keys
|
|
6
|
+
// - node.attr? → node.hasAttribute()
|
|
7
|
+
// - node.option? → node.hasOption()
|
|
8
|
+
// - node.title? → node.hasTitle()
|
|
9
|
+
// - node.sections? → node.hasSections()
|
|
10
|
+
// - node.blocks? → node.hasBlocks()
|
|
11
|
+
// - node.footnotes? → node.hasFootnotes()
|
|
12
|
+
// - node.noheader/notitle/nofooter → node.isNoheader()/isNotitle()/isNofooter()
|
|
13
|
+
// - node.sections → node.sections() (method)
|
|
14
|
+
// - await node.content() → await node.content() (method on Block/Document)
|
|
15
|
+
// - alias convert_pass content_only → convert_pass delegates to this.contentOnly()
|
|
16
|
+
// - Stylesheets.instance.primary_stylesheet_data → Stylesheets.instance.primaryStylesheetData() (camelCase, async)
|
|
17
|
+
// - read_svg_contents uses readContents (supports local and remote URIs via allow-uri-read)
|
|
18
|
+
|
|
19
|
+
import { ConverterBase } from '../converter.js'
|
|
20
|
+
import { AbstractNode } from '../abstract_node.js'
|
|
21
|
+
import {
|
|
22
|
+
LF,
|
|
23
|
+
SafeMode,
|
|
24
|
+
DEFAULT_STYLESHEET_KEYS,
|
|
25
|
+
DEFAULT_STYLESHEET_NAME,
|
|
26
|
+
FONT_AWESOME_VERSION,
|
|
27
|
+
MATHJAX_VERSION,
|
|
28
|
+
BLOCK_MATH_DELIMITERS,
|
|
29
|
+
INLINE_MATH_DELIMITERS,
|
|
30
|
+
} from '../constants.js'
|
|
31
|
+
import { XmlSanitizeRx } from '../rx.js'
|
|
32
|
+
import { extname, isUriish } from '../helpers.js'
|
|
33
|
+
import { Stylesheets } from '../stylesheets.js'
|
|
34
|
+
|
|
35
|
+
// ── Local regex constants ─────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const DropAnchorRx = /<(?:a\b[^>]*|\/a)>/g
|
|
38
|
+
const LeadingAnchorsRx = /^(?:<a id="[^"]+"><\/a>)+/
|
|
39
|
+
const StemBreakRx = / *\\\n(?:\\?\n)*|\n\n+/g
|
|
40
|
+
// NOTE In JavaScript ^ matches start of string when the m flag is not set (same as Opal)
|
|
41
|
+
const SvgPreambleRx = /^[\s\S]*?(?=<svg[\s>])/
|
|
42
|
+
const SvgStartTagRx = /^<svg(?:\s[^>]*)?>/
|
|
43
|
+
const DimensionAttributeRx = /\s(?:width|height|style)=(["'])[\s\S]*?\1/g
|
|
44
|
+
|
|
45
|
+
// ── Quote tag table ───────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const QUOTE_TAGS = {
|
|
48
|
+
monospaced: ['<code>', '</code>', true],
|
|
49
|
+
emphasis: ['<em>', '</em>', true],
|
|
50
|
+
strong: ['<strong>', '</strong>', true],
|
|
51
|
+
double: ['“', '”'],
|
|
52
|
+
single: ['‘', '’'],
|
|
53
|
+
mark: ['<mark>', '</mark>', true],
|
|
54
|
+
superscript: ['<sup>', '</sup>', true],
|
|
55
|
+
subscript: ['<sub>', '</sub>', true],
|
|
56
|
+
asciimath: ['\\$', '\\$'],
|
|
57
|
+
latexmath: ['\\(', '\\)'],
|
|
58
|
+
}
|
|
59
|
+
const DEFAULT_QUOTE_TAG = ['', '']
|
|
60
|
+
|
|
61
|
+
// ── Html5Converter ────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export default class Html5Converter extends ConverterBase {
|
|
64
|
+
/**
|
|
65
|
+
* Create a new Html5Converter instance.
|
|
66
|
+
* @param {string} [backend='html5']
|
|
67
|
+
* @param {Object} [opts={}]
|
|
68
|
+
* @returns {Html5Converter}
|
|
69
|
+
*/
|
|
70
|
+
static create(backend = 'html5', opts = {}) {
|
|
71
|
+
return new this(backend, opts)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
constructor(backend, opts = {}) {
|
|
75
|
+
super(backend, opts)
|
|
76
|
+
let syntax
|
|
77
|
+
if (opts.htmlsyntax === 'xml') {
|
|
78
|
+
syntax = 'xml'
|
|
79
|
+
this._xmlMode = true
|
|
80
|
+
this._voidSlash = '/'
|
|
81
|
+
} else {
|
|
82
|
+
syntax = 'html'
|
|
83
|
+
this._xmlMode = false
|
|
84
|
+
this._voidSlash = ''
|
|
85
|
+
}
|
|
86
|
+
this.initBackendTraits({
|
|
87
|
+
basebackend: 'html',
|
|
88
|
+
filetype: 'html',
|
|
89
|
+
htmlsyntax: syntax,
|
|
90
|
+
outfilesuffix: '.html',
|
|
91
|
+
supportsTemplates: true,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async convert_document(node) {
|
|
96
|
+
const slash = this._voidSlash
|
|
97
|
+
const br = `<br${slash}>`
|
|
98
|
+
let assetUriScheme = node.getAttribute('asset-uri-scheme', 'https')
|
|
99
|
+
if (assetUriScheme) assetUriScheme = `${assetUriScheme}:`
|
|
100
|
+
const cdnBaseUrl = `${assetUriScheme}//cdnjs.cloudflare.com/ajax/libs`
|
|
101
|
+
const linkcss = node.hasAttribute('linkcss')
|
|
102
|
+
const maxWidthAttr = node.hasAttribute('max-width')
|
|
103
|
+
? ` style="max-width: ${node.getAttribute('max-width')};"`
|
|
104
|
+
: ''
|
|
105
|
+
const result = ['<!DOCTYPE html>']
|
|
106
|
+
const langAttribute = node.hasAttribute('nolang')
|
|
107
|
+
? ''
|
|
108
|
+
: ` lang="${node.getAttribute('lang', 'en')}"`
|
|
109
|
+
result.push(
|
|
110
|
+
`<html${this._xmlMode ? ' xmlns="http://www.w3.org/1999/xhtml"' : ''}${langAttribute}>`
|
|
111
|
+
)
|
|
112
|
+
result.push(`<head>
|
|
113
|
+
<meta charset="${node.getAttribute('encoding', 'UTF-8')}"${slash}>
|
|
114
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge"${slash}>
|
|
115
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"${slash}>`)
|
|
116
|
+
let reproducible
|
|
117
|
+
if (!(reproducible = node.hasAttribute('reproducible'))) {
|
|
118
|
+
result.push(
|
|
119
|
+
`<meta name="generator" content="Asciidoctor ${node.getAttribute('asciidoctor-version')}"${slash}>`
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
if (node.hasAttribute('app-name')) {
|
|
123
|
+
result.push(
|
|
124
|
+
`<meta name="application-name" content="${node.getAttribute('app-name')}"${slash}>`
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
if (node.hasAttribute('description')) {
|
|
128
|
+
result.push(
|
|
129
|
+
`<meta name="description" content="${node.getAttribute('description')}"${slash}>`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
if (node.hasAttribute('keywords')) {
|
|
133
|
+
result.push(
|
|
134
|
+
`<meta name="keywords" content="${node.getAttribute('keywords')}"${slash}>`
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
if (node.hasAttribute('authors')) {
|
|
138
|
+
let authors = node.subReplacements(node.getAttribute('authors'))
|
|
139
|
+
if (authors.includes('<')) authors = authors.replace(XmlSanitizeRx, '')
|
|
140
|
+
result.push(`<meta name="author" content="${authors}"${slash}>`)
|
|
141
|
+
}
|
|
142
|
+
if (node.hasAttribute('copyright')) {
|
|
143
|
+
result.push(
|
|
144
|
+
`<meta name="copyright" content="${node.getAttribute('copyright')}"${slash}>`
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
if (node.hasAttribute('favicon')) {
|
|
148
|
+
// Access raw attribute value to detect empty string (set without value)
|
|
149
|
+
let iconHref = 'favicon' in node.attributes ? node.attributes.favicon : ''
|
|
150
|
+
let iconType
|
|
151
|
+
if (!iconHref) {
|
|
152
|
+
iconHref = 'favicon.ico'
|
|
153
|
+
iconType = 'image/x-icon'
|
|
154
|
+
} else {
|
|
155
|
+
const iconExt = extname(iconHref, null)
|
|
156
|
+
if (iconExt) {
|
|
157
|
+
iconType =
|
|
158
|
+
iconExt === '.ico' ? 'image/x-icon' : `image/${iconExt.slice(1)}`
|
|
159
|
+
} else {
|
|
160
|
+
iconType = 'image/x-icon'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
result.push(
|
|
164
|
+
`<link rel="icon" type="${iconType}" href="${iconHref}"${slash}>`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
result.push(
|
|
168
|
+
`<title>${node.doctitle({ sanitize: true, use_fallback: true })}</title>`
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// Access raw attribute value; '' means "use default stylesheet"
|
|
172
|
+
const stylesheetRawVal =
|
|
173
|
+
'stylesheet' in node.attributes ? node.attributes.stylesheet : null
|
|
174
|
+
if (DEFAULT_STYLESHEET_KEYS.has(stylesheetRawVal)) {
|
|
175
|
+
if (node.hasAttribute('webfonts')) {
|
|
176
|
+
const webfonts = node.attributes.webfonts ?? ''
|
|
177
|
+
const fontFamily =
|
|
178
|
+
webfonts ||
|
|
179
|
+
'Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CNoto+Sans+Mono:400,700'
|
|
180
|
+
result.push(
|
|
181
|
+
`<link rel="stylesheet" href="${assetUriScheme}//fonts.googleapis.com/css?family=${fontFamily}"${slash}>`
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
if (linkcss) {
|
|
185
|
+
result.push(
|
|
186
|
+
`<link rel="stylesheet" href="${node.normalizeWebPath(DEFAULT_STYLESHEET_NAME, node.getAttribute('stylesdir'), false)}"${slash}>`
|
|
187
|
+
)
|
|
188
|
+
} else {
|
|
189
|
+
result.push(
|
|
190
|
+
`<style>\n${await Stylesheets.instance.primaryStylesheetData()}\n</style>`
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
} else if (node.hasAttribute('stylesheet')) {
|
|
194
|
+
if (linkcss) {
|
|
195
|
+
result.push(
|
|
196
|
+
`<link rel="stylesheet" href="${node.normalizeWebPath(node.getAttribute('stylesheet'), node.getAttribute('stylesdir'))}"${slash}>`
|
|
197
|
+
)
|
|
198
|
+
} else {
|
|
199
|
+
const cssPath = node.normalizeSystemPath(
|
|
200
|
+
node.getAttribute('stylesheet'),
|
|
201
|
+
node.getAttribute('stylesdir')
|
|
202
|
+
)
|
|
203
|
+
const cssData =
|
|
204
|
+
(await node.readAsset(cssPath, {
|
|
205
|
+
warnOnFailure: true,
|
|
206
|
+
label: 'stylesheet',
|
|
207
|
+
})) ?? ''
|
|
208
|
+
result.push(`<style>\n${cssData}\n</style>`)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (node.hasAttribute('icons', 'font')) {
|
|
213
|
+
if (node.hasAttribute('iconfont-remote')) {
|
|
214
|
+
const cdnUrl =
|
|
215
|
+
node.getAttribute('iconfont-cdn') ??
|
|
216
|
+
`${cdnBaseUrl}/font-awesome/${FONT_AWESOME_VERSION}/css/font-awesome.min.css`
|
|
217
|
+
result.push(`<link rel="stylesheet" href="${cdnUrl}"${slash}>`)
|
|
218
|
+
} else {
|
|
219
|
+
const iconfontStylesheet = `${node.getAttribute('iconfont-name', 'font-awesome')}.css`
|
|
220
|
+
result.push(
|
|
221
|
+
`<link rel="stylesheet" href="${node.normalizeWebPath(iconfontStylesheet, node.getAttribute('stylesdir'), false)}"${slash}>`
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const syntaxHl = node.syntaxHighlighter
|
|
227
|
+
let syntaxHlDocinfoHeadIdx
|
|
228
|
+
if (syntaxHl) {
|
|
229
|
+
syntaxHlDocinfoHeadIdx = result.length
|
|
230
|
+
result.push('') // placeholder; replaced or spliced out below
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const docinfoContent = await node.docinfo()
|
|
234
|
+
if (docinfoContent) result.push(docinfoContent)
|
|
235
|
+
|
|
236
|
+
result.push('</head>')
|
|
237
|
+
|
|
238
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
239
|
+
const sectioned = node.hasSections()
|
|
240
|
+
let classes
|
|
241
|
+
if (
|
|
242
|
+
sectioned &&
|
|
243
|
+
node.hasAttribute('toc-class') &&
|
|
244
|
+
node.hasAttribute('toc') &&
|
|
245
|
+
node.hasAttribute('toc-placement', 'auto')
|
|
246
|
+
) {
|
|
247
|
+
classes = [
|
|
248
|
+
node.doctype,
|
|
249
|
+
node.getAttribute('toc-class'),
|
|
250
|
+
`toc-${node.getAttribute('toc-position', 'header')}`,
|
|
251
|
+
]
|
|
252
|
+
} else {
|
|
253
|
+
classes = [node.doctype]
|
|
254
|
+
}
|
|
255
|
+
if (node.role) classes.push(node.role)
|
|
256
|
+
result.push(`<body${idAttr} class="${classes.join(' ')}">`)
|
|
257
|
+
|
|
258
|
+
const headerDocinfo = await node.docinfo('header')
|
|
259
|
+
if (headerDocinfo) result.push(headerDocinfo)
|
|
260
|
+
|
|
261
|
+
if (!node.isNoheader()) {
|
|
262
|
+
result.push(`<div id="header"${maxWidthAttr}>`)
|
|
263
|
+
if (node.doctype === 'manpage') {
|
|
264
|
+
result.push(`<h1>${node.doctitle()} Manual Page</h1>`)
|
|
265
|
+
if (
|
|
266
|
+
sectioned &&
|
|
267
|
+
node.hasAttribute('toc') &&
|
|
268
|
+
node.hasAttribute('toc-placement', 'auto')
|
|
269
|
+
) {
|
|
270
|
+
result.push(`<div id="toc" class="${node.getAttribute('toc-class', 'toc')}">
|
|
271
|
+
<div id="toctitle">${node.getAttribute('toc-title')}</div>
|
|
272
|
+
${await node.converter.convert(node, 'outline')}
|
|
273
|
+
</div>`)
|
|
274
|
+
}
|
|
275
|
+
if (node.hasAttribute('manpurpose'))
|
|
276
|
+
result.push(this._generateMannameSection(node))
|
|
277
|
+
} else {
|
|
278
|
+
if (node.hasHeader()) {
|
|
279
|
+
if (!node.isNotitle()) result.push(`<h1>${node.header.title}</h1>`)
|
|
280
|
+
const details = []
|
|
281
|
+
let idx = 1
|
|
282
|
+
for (const author of node.authors()) {
|
|
283
|
+
details.push(
|
|
284
|
+
`<span id="author${idx > 1 ? idx : ''}" class="author">${node.subReplacements(author.name)}</span>${br}`
|
|
285
|
+
)
|
|
286
|
+
if (author.email) {
|
|
287
|
+
details.push(
|
|
288
|
+
`<span id="email${idx > 1 ? idx : ''}" class="email">${await node.subMacros(author.email)}</span>${br}`
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
idx++
|
|
292
|
+
}
|
|
293
|
+
if (node.hasAttribute('revnumber')) {
|
|
294
|
+
const versionLabel = (
|
|
295
|
+
node.getAttribute('version-label') || ''
|
|
296
|
+
).toLowerCase()
|
|
297
|
+
details.push(
|
|
298
|
+
`<span id="revnumber">${versionLabel} ${node.getAttribute('revnumber')}${node.hasAttribute('revdate') ? ',' : ''}</span>`
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
if (node.hasAttribute('revdate')) {
|
|
302
|
+
details.push(
|
|
303
|
+
`<span id="revdate">${node.getAttribute('revdate')}</span>`
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
if (node.hasAttribute('revremark')) {
|
|
307
|
+
details.push(
|
|
308
|
+
`${br}<span id="revremark">${node.getAttribute('revremark')}</span>`
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
if (details.length > 0) {
|
|
312
|
+
result.push('<div class="details">')
|
|
313
|
+
result.push(...details)
|
|
314
|
+
result.push('</div>')
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (
|
|
318
|
+
sectioned &&
|
|
319
|
+
node.hasAttribute('toc') &&
|
|
320
|
+
node.hasAttribute('toc-placement', 'auto')
|
|
321
|
+
) {
|
|
322
|
+
result.push(`<div id="toc" class="${node.getAttribute('toc-class', 'toc')}">
|
|
323
|
+
<div id="toctitle">${node.getAttribute('toc-title')}</div>
|
|
324
|
+
${await node.converter.convert(node, 'outline')}
|
|
325
|
+
</div>`)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
result.push('</div>')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
result.push(`<div id="content"${maxWidthAttr}>
|
|
332
|
+
${await node.content()}
|
|
333
|
+
</div>`)
|
|
334
|
+
|
|
335
|
+
if (node.hasFootnotes() && !node.hasAttribute('nofootnotes')) {
|
|
336
|
+
result.push(`<div id="footnotes"${maxWidthAttr}>
|
|
337
|
+
<hr${slash}>`)
|
|
338
|
+
for (const footnote of node.footnotes) {
|
|
339
|
+
result.push(`<div class="footnote" id="_footnotedef_${footnote.index}">
|
|
340
|
+
<a href="#_footnoteref_${footnote.index}">${footnote.index}</a>. ${footnote.text}
|
|
341
|
+
</div>`)
|
|
342
|
+
}
|
|
343
|
+
result.push('</div>')
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!node.isNofooter()) {
|
|
347
|
+
result.push(`<div id="footer"${maxWidthAttr}>`)
|
|
348
|
+
result.push('<div id="footer-text">')
|
|
349
|
+
if (node.hasAttribute('revnumber')) {
|
|
350
|
+
result.push(
|
|
351
|
+
`${node.getAttribute('version-label')} ${node.getAttribute('revnumber')}${br}`
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
if (node.hasAttribute('last-update-label') && !reproducible) {
|
|
355
|
+
result.push(
|
|
356
|
+
`${node.getAttribute('last-update-label')} ${node.getAttribute('docdatetime')}`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
result.push('</div>')
|
|
360
|
+
result.push('</div>')
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// JavaScript (and auxiliary stylesheets) loaded at end of body for performance
|
|
364
|
+
if (syntaxHl) {
|
|
365
|
+
if (syntaxHl.hasDocinfo('head')) {
|
|
366
|
+
result[syntaxHlDocinfoHeadIdx] = syntaxHl.docinfo('head', node, {
|
|
367
|
+
cdn_base_url: cdnBaseUrl,
|
|
368
|
+
linkcss,
|
|
369
|
+
self_closing_tag_slash: slash,
|
|
370
|
+
})
|
|
371
|
+
} else {
|
|
372
|
+
result.splice(syntaxHlDocinfoHeadIdx, 1)
|
|
373
|
+
}
|
|
374
|
+
if (syntaxHl.hasDocinfo('footer')) {
|
|
375
|
+
result.push(
|
|
376
|
+
syntaxHl.docinfo('footer', node, {
|
|
377
|
+
cdn_base_url: cdnBaseUrl,
|
|
378
|
+
linkcss,
|
|
379
|
+
self_closing_tag_slash: slash,
|
|
380
|
+
})
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (node.hasAttribute('stem')) {
|
|
386
|
+
let eqnumsVal = node.getAttribute('eqnums', 'none')
|
|
387
|
+
if (!eqnumsVal) eqnumsVal = 'AMS'
|
|
388
|
+
const eqnumsOpt = ` equationNumbers: { autoNumber: "${eqnumsVal}" } `
|
|
389
|
+
// IMPORTANT inspect calls on delimiter arrays are intentional for JavaScript compat (emulates JSON.stringify)
|
|
390
|
+
result.push(`<script type="text/x-mathjax-config">
|
|
391
|
+
MathJax.Hub.Config({
|
|
392
|
+
messageStyle: "none",
|
|
393
|
+
tex2jax: {
|
|
394
|
+
inlineMath: [${JSON.stringify(INLINE_MATH_DELIMITERS.latexmath)}],
|
|
395
|
+
displayMath: [${JSON.stringify(BLOCK_MATH_DELIMITERS.latexmath)}],
|
|
396
|
+
ignoreClass: "nostem|nolatexmath"
|
|
397
|
+
},
|
|
398
|
+
asciimath2jax: {
|
|
399
|
+
delimiters: [${JSON.stringify(BLOCK_MATH_DELIMITERS.asciimath)}],
|
|
400
|
+
ignoreClass: "nostem|noasciimath"
|
|
401
|
+
},
|
|
402
|
+
TeX: {${eqnumsOpt}}
|
|
403
|
+
})
|
|
404
|
+
MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
|
|
405
|
+
MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
|
|
406
|
+
if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains("stemblock")) {
|
|
407
|
+
data.math.root.display = "block"
|
|
408
|
+
}
|
|
409
|
+
return data
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
</script>
|
|
413
|
+
<script src="${cdnBaseUrl}/mathjax/${MATHJAX_VERSION}/MathJax.js?config=TeX-MML-AM_CHTML"></script>`)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const footerDocinfo = await node.docinfo('footer')
|
|
417
|
+
if (footerDocinfo) result.push(footerDocinfo)
|
|
418
|
+
|
|
419
|
+
result.push('</body>')
|
|
420
|
+
result.push('</html>')
|
|
421
|
+
return result.join(LF)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async convert_embedded(node) {
|
|
425
|
+
const result = []
|
|
426
|
+
if (node.doctype === 'manpage') {
|
|
427
|
+
if (!node.isNotitle()) {
|
|
428
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
429
|
+
result.push(`<h1${idAttr}>${node.doctitle()} Manual Page</h1>`)
|
|
430
|
+
}
|
|
431
|
+
if (node.hasAttribute('manpurpose'))
|
|
432
|
+
result.push(this._generateMannameSection(node))
|
|
433
|
+
} else if (node.hasHeader() && !node.isNotitle()) {
|
|
434
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
435
|
+
result.push(`<h1${idAttr}>${node.header.title}</h1>`)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (node.hasSections() && node.hasAttribute('toc')) {
|
|
439
|
+
const tocP = node.getAttribute('toc-placement')
|
|
440
|
+
if (tocP !== 'macro' && tocP !== 'preamble') {
|
|
441
|
+
result.push(`<div id="toc" class="toc">
|
|
442
|
+
<div id="toctitle">${node.getAttribute('toc-title')}</div>
|
|
443
|
+
${await node.converter.convert(node, 'outline')}
|
|
444
|
+
</div>`)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
result.push(await node.content())
|
|
449
|
+
|
|
450
|
+
if (node.hasFootnotes() && !node.hasAttribute('nofootnotes')) {
|
|
451
|
+
result.push(`<div id="footnotes">
|
|
452
|
+
<hr${this._voidSlash}>`)
|
|
453
|
+
for (const footnote of node.footnotes) {
|
|
454
|
+
result.push(`<div class="footnote" id="_footnotedef_${footnote.index}">
|
|
455
|
+
<a href="#_footnoteref_${footnote.index}">${footnote.index}</a>. ${footnote.text}
|
|
456
|
+
</div>`)
|
|
457
|
+
}
|
|
458
|
+
result.push('</div>')
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return result.join(LF)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async convert_outline(node, opts = {}) {
|
|
465
|
+
if (!node.hasSections()) return null
|
|
466
|
+
const sections = node.sections()
|
|
467
|
+
const parts = node.context === 'document' && node.isMultipart()
|
|
468
|
+
const sectlevel = parts ? 0 : sections[0].level
|
|
469
|
+
const sectnumlevels =
|
|
470
|
+
opts.sectnumlevels ??
|
|
471
|
+
parseInt(node.document.attributes.sectnumlevels || 3, 10)
|
|
472
|
+
|
|
473
|
+
let toclevels = opts.toclevels ?? null
|
|
474
|
+
if (toclevels == null) {
|
|
475
|
+
const toclevelAttr = node.document.attributes.toclevels
|
|
476
|
+
if (toclevelAttr) {
|
|
477
|
+
toclevels = parseInt(toclevelAttr, 10)
|
|
478
|
+
if (toclevels < 1 && !parts) toclevels = 1
|
|
479
|
+
} else {
|
|
480
|
+
toclevels = 2
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const result = [`<ul class="sectlevel${sectlevel}">`]
|
|
485
|
+
for (const section of sections) {
|
|
486
|
+
const slevel = section.level
|
|
487
|
+
const stoclevels = section.hasAttribute('toclevels')
|
|
488
|
+
? parseInt(section.getAttribute('toclevels'), 10)
|
|
489
|
+
: toclevels
|
|
490
|
+
if (slevel > stoclevels) continue
|
|
491
|
+
|
|
492
|
+
let stitle
|
|
493
|
+
if (section.caption) {
|
|
494
|
+
stitle = section.captionedTitle()
|
|
495
|
+
} else if (section.numbered && slevel <= sectnumlevels) {
|
|
496
|
+
if (slevel < 2 && node.document.doctype === 'book') {
|
|
497
|
+
const sectname = section.sectname
|
|
498
|
+
if (sectname === 'chapter') {
|
|
499
|
+
const signifier = node.document.attributes['chapter-signifier']
|
|
500
|
+
stitle = `${signifier ? `${signifier} ` : ''}${section.sectnum()} ${section.title}`
|
|
501
|
+
} else if (sectname === 'part') {
|
|
502
|
+
const signifier = node.document.attributes['part-signifier']
|
|
503
|
+
stitle = `${signifier ? `${signifier} ` : ''}${section.sectnum(null, ':')} ${section.title}`
|
|
504
|
+
} else {
|
|
505
|
+
stitle = `${section.sectnum()} ${section.title}`
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
stitle = `${section.sectnum()} ${section.title}`
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
stitle = section.title
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (stitle?.includes('<a')) {
|
|
515
|
+
stitle = stitle.replace(new RegExp(DropAnchorRx.source, 'g'), '')
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const otag =
|
|
519
|
+
slevel === sectlevel ? '<li>' : `<li class="sectlevel${slevel}">`
|
|
520
|
+
if (slevel < stoclevels) {
|
|
521
|
+
const childTocLevel = await this.convert_outline(section, {
|
|
522
|
+
toclevels: stoclevels,
|
|
523
|
+
sectnumlevels,
|
|
524
|
+
})
|
|
525
|
+
if (childTocLevel) {
|
|
526
|
+
result.push(`${otag}<a href="#${section.id}">${stitle}</a>`)
|
|
527
|
+
result.push(childTocLevel)
|
|
528
|
+
result.push('</li>')
|
|
529
|
+
continue
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
result.push(`${otag}<a href="#${section.id}">${stitle}</a></li>`)
|
|
533
|
+
}
|
|
534
|
+
result.push('</ul>')
|
|
535
|
+
return result.join(LF)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async convert_section(node) {
|
|
539
|
+
const docAttrs = node.document.attributes
|
|
540
|
+
const level = node.level
|
|
541
|
+
let title
|
|
542
|
+
if (node.caption) {
|
|
543
|
+
title = node.captionedTitle()
|
|
544
|
+
} else if (
|
|
545
|
+
node.numbered &&
|
|
546
|
+
level <= parseInt(docAttrs.sectnumlevels || 3, 10)
|
|
547
|
+
) {
|
|
548
|
+
if (level < 2 && node.document.doctype === 'book') {
|
|
549
|
+
const sectname = node.sectname
|
|
550
|
+
if (sectname === 'chapter') {
|
|
551
|
+
const signifier = docAttrs['chapter-signifier']
|
|
552
|
+
title = `${signifier ? `${signifier} ` : ''}${node.sectnum()} ${node.title}`
|
|
553
|
+
} else if (sectname === 'part') {
|
|
554
|
+
const signifier = docAttrs['part-signifier']
|
|
555
|
+
title = `${signifier ? `${signifier} ` : ''}${node.sectnum(null, ':')} ${node.title}`
|
|
556
|
+
} else {
|
|
557
|
+
title = `${node.sectnum()} ${node.title}`
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
title = `${node.sectnum()} ${node.title}`
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
title = node.title
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
let idAttr = ''
|
|
567
|
+
if (node.id) {
|
|
568
|
+
const id = node.id
|
|
569
|
+
idAttr = ` id="${id}"`
|
|
570
|
+
if ('sectlinks' in docAttrs) {
|
|
571
|
+
let m
|
|
572
|
+
if (title.startsWith('<a ') && (m = title.match(LeadingAnchorsRx))) {
|
|
573
|
+
title = `${m[0]}<a class="link" href="#${id}">${title.slice(m[0].length)}</a>`
|
|
574
|
+
} else {
|
|
575
|
+
title = `<a class="link" href="#${id}">${title}</a>`
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if ('sectanchors' in docAttrs) {
|
|
579
|
+
if (docAttrs.sectanchors === 'after') {
|
|
580
|
+
title = `${title}<a class="anchor" href="#${id}"></a>`
|
|
581
|
+
} else {
|
|
582
|
+
title = `<a class="anchor" href="#${id}"></a>${title}`
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const role = node.role
|
|
588
|
+
if (level === 0) {
|
|
589
|
+
return `<h1${idAttr} class="sect0${role ? ` ${role}` : ''}">${title}</h1>
|
|
590
|
+
${await node.content()}`
|
|
591
|
+
}
|
|
592
|
+
return `<div class="sect${level}${role ? ` ${role}` : ''}">
|
|
593
|
+
<h${level + 1}${idAttr}>${title}</h${level + 1}>
|
|
594
|
+
${
|
|
595
|
+
level === 1
|
|
596
|
+
? `<div class="sectionbody">
|
|
597
|
+
${await node.content()}
|
|
598
|
+
</div>`
|
|
599
|
+
: await node.content()
|
|
600
|
+
}
|
|
601
|
+
</div>`
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async convert_admonition(node) {
|
|
605
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
606
|
+
const name = node.getAttribute('name')
|
|
607
|
+
const titleElement = node.hasTitle()
|
|
608
|
+
? `<div class="title">${node.title}</div>\n`
|
|
609
|
+
: ''
|
|
610
|
+
let label
|
|
611
|
+
if (node.document.hasAttribute('icons')) {
|
|
612
|
+
if (
|
|
613
|
+
node.document.hasAttribute('icons', 'font') &&
|
|
614
|
+
!node.hasAttribute('icon')
|
|
615
|
+
) {
|
|
616
|
+
label = `<i class="fa icon-${name}" title="${node.getAttribute('textlabel')}"></i>`
|
|
617
|
+
} else {
|
|
618
|
+
label = `<img src="${await node.iconUri(name)}" alt="${node.getAttribute('textlabel')}"${this._voidSlash}>`
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
label = `<div class="title">${node.getAttribute('textlabel')}</div>`
|
|
622
|
+
}
|
|
623
|
+
return `<div${idAttr} class="admonitionblock ${name}${node.role ? ` ${node.role}` : ''}">
|
|
624
|
+
<table>
|
|
625
|
+
<tr>
|
|
626
|
+
<td class="icon">
|
|
627
|
+
${label}
|
|
628
|
+
</td>
|
|
629
|
+
<td class="content">
|
|
630
|
+
${titleElement}${await node.content()}
|
|
631
|
+
</td>
|
|
632
|
+
</tr>
|
|
633
|
+
</table>
|
|
634
|
+
</div>`
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
async convert_audio(node) {
|
|
638
|
+
const xml = this._xmlMode
|
|
639
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
640
|
+
const classes = ['audioblock', node.role].filter(Boolean)
|
|
641
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
642
|
+
const titleElement = node.hasTitle()
|
|
643
|
+
? `<div class="title">${node.title}</div>\n`
|
|
644
|
+
: ''
|
|
645
|
+
const startT = node.getAttribute('start')
|
|
646
|
+
const endT = node.getAttribute('end')
|
|
647
|
+
const timeAnchor =
|
|
648
|
+
startT || endT ? `#t=${startT || ''}${endT ? `,${endT}` : ''}` : ''
|
|
649
|
+
return `<div${idAttribute}${classAttribute}>
|
|
650
|
+
${titleElement}<div class="content">
|
|
651
|
+
<audio src="${node.mediaUri(node.getAttribute('target'))}${timeAnchor}"${node.hasOption('autoplay') ? this._appendBooleanAttr('autoplay', xml) : ''}${node.hasOption('nocontrols') ? '' : this._appendBooleanAttr('controls', xml)}${node.hasOption('loop') ? this._appendBooleanAttr('loop', xml) : ''}>
|
|
652
|
+
Your browser does not support the audio tag.
|
|
653
|
+
</audio>
|
|
654
|
+
</div>
|
|
655
|
+
</div>`
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async convert_colist(node) {
|
|
659
|
+
const result = []
|
|
660
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
661
|
+
const classes = ['colist', node.style, node.role].filter(Boolean)
|
|
662
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
663
|
+
|
|
664
|
+
result.push(`<div${idAttribute}${classAttribute}>`)
|
|
665
|
+
if (node.hasTitle()) result.push(`<div class="title">${node.title}</div>`)
|
|
666
|
+
|
|
667
|
+
if (node.document.hasAttribute('icons')) {
|
|
668
|
+
result.push('<table>')
|
|
669
|
+
const fontIcons = node.document.hasAttribute('icons', 'font')
|
|
670
|
+
let num = 0
|
|
671
|
+
for (const item of node.getItems()) {
|
|
672
|
+
num++
|
|
673
|
+
let numLabel
|
|
674
|
+
if (fontIcons) {
|
|
675
|
+
numLabel = `<i class="conum" data-value="${num}"></i><b>${num}</b>`
|
|
676
|
+
} else {
|
|
677
|
+
numLabel = `<img src="${await node.iconUri(`callouts/${num}`)}" alt="${num}"${this._voidSlash}>`
|
|
678
|
+
}
|
|
679
|
+
result.push(`<tr>
|
|
680
|
+
<td>${numLabel}</td>
|
|
681
|
+
<td>${item.getText()}${item.hasBlocks() ? LF + (await item.content()) : ''}</td>
|
|
682
|
+
</tr>`)
|
|
683
|
+
}
|
|
684
|
+
result.push('</table>')
|
|
685
|
+
} else {
|
|
686
|
+
result.push('<ol>')
|
|
687
|
+
for (const item of node.getItems()) {
|
|
688
|
+
result.push(`<li>
|
|
689
|
+
<p>${item.getText()}</p>${item.hasBlocks() ? LF + (await item.content()) : ''}
|
|
690
|
+
</li>`)
|
|
691
|
+
}
|
|
692
|
+
result.push('</ol>')
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
result.push('</div>')
|
|
696
|
+
return result.join(LF)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async convert_dlist(node) {
|
|
700
|
+
const result = []
|
|
701
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
702
|
+
let classes
|
|
703
|
+
switch (node.style) {
|
|
704
|
+
case 'qanda':
|
|
705
|
+
classes = ['qlist', 'qanda', node.role]
|
|
706
|
+
break
|
|
707
|
+
case 'horizontal':
|
|
708
|
+
classes = ['hdlist', node.role]
|
|
709
|
+
break
|
|
710
|
+
default:
|
|
711
|
+
classes = ['dlist', node.style, node.role]
|
|
712
|
+
}
|
|
713
|
+
const classAttribute = ` class="${classes.filter(Boolean).join(' ')}"`
|
|
714
|
+
|
|
715
|
+
result.push(`<div${idAttribute}${classAttribute}>`)
|
|
716
|
+
if (node.hasTitle()) result.push(`<div class="title">${node.title}</div>`)
|
|
717
|
+
|
|
718
|
+
switch (node.style) {
|
|
719
|
+
case 'qanda':
|
|
720
|
+
result.push('<ol>')
|
|
721
|
+
for (const [terms, dd] of node.getItems()) {
|
|
722
|
+
result.push('<li>')
|
|
723
|
+
for (const dt of terms) {
|
|
724
|
+
result.push(`<p><em>${dt.getText()}</em></p>`)
|
|
725
|
+
}
|
|
726
|
+
if (dd) {
|
|
727
|
+
if (dd.hasText()) result.push(`<p>${dd.getText()}</p>`)
|
|
728
|
+
if (dd.hasBlocks()) result.push(await dd.content())
|
|
729
|
+
}
|
|
730
|
+
result.push('</li>')
|
|
731
|
+
}
|
|
732
|
+
result.push('</ol>')
|
|
733
|
+
break
|
|
734
|
+
case 'horizontal': {
|
|
735
|
+
const slash = this._voidSlash
|
|
736
|
+
result.push('<table>')
|
|
737
|
+
if (node.hasAttribute('labelwidth') || node.hasAttribute('itemwidth')) {
|
|
738
|
+
result.push('<colgroup>')
|
|
739
|
+
const labelWidthAttr = node.hasAttribute('labelwidth')
|
|
740
|
+
? ` width="${node.getAttribute('labelwidth').replace(/%$/, '')}%"`
|
|
741
|
+
: ''
|
|
742
|
+
result.push(`<col${labelWidthAttr}${slash}>`)
|
|
743
|
+
const itemWidthAttr = node.hasAttribute('itemwidth')
|
|
744
|
+
? ` width="${node.getAttribute('itemwidth').replace(/%$/, '')}%"`
|
|
745
|
+
: ''
|
|
746
|
+
result.push(`<col${itemWidthAttr}${slash}>`)
|
|
747
|
+
result.push('</colgroup>')
|
|
748
|
+
}
|
|
749
|
+
for (const [terms, dd] of node.getItems()) {
|
|
750
|
+
result.push('<tr>')
|
|
751
|
+
result.push(
|
|
752
|
+
`<td class="hdlist1${node.hasOption('strong') ? ' strong' : ''}">`
|
|
753
|
+
)
|
|
754
|
+
let firstTerm = true
|
|
755
|
+
for (const dt of terms) {
|
|
756
|
+
if (!firstTerm) result.push(`<br${slash}>`)
|
|
757
|
+
result.push(dt.getText())
|
|
758
|
+
firstTerm = false
|
|
759
|
+
}
|
|
760
|
+
result.push('</td>')
|
|
761
|
+
result.push('<td class="hdlist2">')
|
|
762
|
+
if (dd) {
|
|
763
|
+
if (dd.hasText()) result.push(`<p>${dd.getText()}</p>`)
|
|
764
|
+
if (dd.hasBlocks()) result.push(await dd.content())
|
|
765
|
+
}
|
|
766
|
+
result.push('</td>')
|
|
767
|
+
result.push('</tr>')
|
|
768
|
+
}
|
|
769
|
+
result.push('</table>')
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
default: {
|
|
773
|
+
result.push('<dl>')
|
|
774
|
+
const dtStyleAttribute = node.style ? '' : ' class="hdlist1"'
|
|
775
|
+
for (const [terms, dd] of node.getItems()) {
|
|
776
|
+
for (const dt of terms) {
|
|
777
|
+
result.push(`<dt${dtStyleAttribute}>${dt.getText()}</dt>`)
|
|
778
|
+
}
|
|
779
|
+
if (!dd) continue
|
|
780
|
+
result.push('<dd>')
|
|
781
|
+
if (dd.hasText()) result.push(`<p>${dd.getText()}</p>`)
|
|
782
|
+
if (dd.hasBlocks()) result.push(await dd.content())
|
|
783
|
+
result.push('</dd>')
|
|
784
|
+
}
|
|
785
|
+
result.push('</dl>')
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
result.push('</div>')
|
|
790
|
+
return result.join(LF)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async convert_example(node) {
|
|
794
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
795
|
+
if (node.hasOption('collapsible')) {
|
|
796
|
+
const classAttribute = node.role ? ` class="${node.role}"` : ''
|
|
797
|
+
const summaryElement = node.hasTitle()
|
|
798
|
+
? `<summary class="title">${node.title}</summary>`
|
|
799
|
+
: '<summary class="title">Details</summary>'
|
|
800
|
+
return `<details${idAttribute}${classAttribute}${node.hasOption('open') ? ' open' : ''}>
|
|
801
|
+
${summaryElement}
|
|
802
|
+
<div class="content">
|
|
803
|
+
${await node.content()}
|
|
804
|
+
</div>
|
|
805
|
+
</details>`
|
|
806
|
+
}
|
|
807
|
+
const titleElement = node.hasTitle()
|
|
808
|
+
? `<div class="title">${node.captionedTitle()}</div>\n`
|
|
809
|
+
: ''
|
|
810
|
+
const role = node.role
|
|
811
|
+
return `<div${idAttribute} class="exampleblock${role ? ` ${role}` : ''}">
|
|
812
|
+
${titleElement}<div class="content">
|
|
813
|
+
${await node.content()}
|
|
814
|
+
</div>
|
|
815
|
+
</div>`
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async convert_floating_title(node) {
|
|
819
|
+
const tagName = `h${node.level + 1}`
|
|
820
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
821
|
+
const classes = [node.style, node.role].filter(Boolean)
|
|
822
|
+
return `<${tagName}${idAttribute} class="${classes.join(' ')}">${node.title}</${tagName}>`
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async convert_image(node) {
|
|
826
|
+
const target = node.getAttribute('target')
|
|
827
|
+
const widthAttr = node.hasAttribute('width')
|
|
828
|
+
? ` width="${node.getAttribute('width')}"`
|
|
829
|
+
: ''
|
|
830
|
+
const heightAttr = node.hasAttribute('height')
|
|
831
|
+
? ` height="${node.getAttribute('height')}"`
|
|
832
|
+
: ''
|
|
833
|
+
const slash = this._voidSlash
|
|
834
|
+
let img, src
|
|
835
|
+
if (
|
|
836
|
+
(node.hasAttribute('format', 'svg') || target.includes('.svg')) &&
|
|
837
|
+
node.document.safe < SafeMode.SECURE
|
|
838
|
+
) {
|
|
839
|
+
if (node.hasOption('inline')) {
|
|
840
|
+
img =
|
|
841
|
+
(await this.readSvgContents(node, target)) ||
|
|
842
|
+
`<span class="alt">${node.getAlt()}</span>`
|
|
843
|
+
} else if (
|
|
844
|
+
node.hasOption('interactive') &&
|
|
845
|
+
node.document.safe >= SafeMode.SERVER
|
|
846
|
+
) {
|
|
847
|
+
const fallback = node.hasAttribute('fallback')
|
|
848
|
+
? `<img src="${await node.imageUri(node.getAttribute('fallback'))}" alt="${this._encodeAttrValue(node.getAlt())}"${widthAttr}${heightAttr}${slash}>`
|
|
849
|
+
: `<span class="alt">${node.getAlt()}</span>`
|
|
850
|
+
src = await node.imageUri(target)
|
|
851
|
+
img = `<object type="image/svg+xml" data="${src}"${widthAttr}${heightAttr}>${fallback}</object>`
|
|
852
|
+
} else {
|
|
853
|
+
src = await node.imageUri(target)
|
|
854
|
+
img = `<img src="${src}" alt="${this._encodeAttrValue(node.getAlt())}"${widthAttr}${heightAttr}${slash}>`
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
src = await node.imageUri(target)
|
|
858
|
+
img = `<img src="${src}" alt="${this._encodeAttrValue(node.getAlt())}"${widthAttr}${heightAttr}${slash}>`
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (node.hasAttribute('link')) {
|
|
862
|
+
let hrefAttrVal = node.getAttribute('link')
|
|
863
|
+
if (hrefAttrVal === 'self') hrefAttrVal = src
|
|
864
|
+
if (hrefAttrVal) {
|
|
865
|
+
img = `<a class="image" href="${hrefAttrVal}"${this._appendLinkConstraintAttrs(node).join('')}>${img}</a>`
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
870
|
+
const classes = ['imageblock']
|
|
871
|
+
if (node.hasAttribute('float')) classes.push(node.getAttribute('float'))
|
|
872
|
+
if (node.hasAttribute('align'))
|
|
873
|
+
classes.push(`text-${node.getAttribute('align')}`)
|
|
874
|
+
if (node.role) classes.push(node.role)
|
|
875
|
+
const classAttr = ` class="${classes.join(' ')}"`
|
|
876
|
+
const titleEl = node.hasTitle()
|
|
877
|
+
? `\n<div class="title">${node.captionedTitle()}</div>`
|
|
878
|
+
: ''
|
|
879
|
+
return `<div${idAttr}${classAttr}>
|
|
880
|
+
<div class="content">
|
|
881
|
+
${img}
|
|
882
|
+
</div>${titleEl}
|
|
883
|
+
</div>`
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
async convert_listing(node) {
|
|
887
|
+
const nowrap =
|
|
888
|
+
node.hasOption('nowrap') || !node.document.hasAttribute('prewrap')
|
|
889
|
+
let preOpen, preClose, syntaxHl, lang, opts
|
|
890
|
+
if (node.style === 'source') {
|
|
891
|
+
lang = node.getAttribute('language')
|
|
892
|
+
syntaxHl = node.document.syntaxHighlighter
|
|
893
|
+
if (syntaxHl) {
|
|
894
|
+
if (syntaxHl.handlesHighlighting()) {
|
|
895
|
+
const docAttrs = node.document.attributes
|
|
896
|
+
opts = {
|
|
897
|
+
css_mode: docAttrs[`${syntaxHl.name}-css`] || 'class',
|
|
898
|
+
style: docAttrs[`${syntaxHl.name}-style`],
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
opts = {}
|
|
902
|
+
}
|
|
903
|
+
opts.nowrap = nowrap
|
|
904
|
+
} else {
|
|
905
|
+
preOpen = `<pre class="highlight${nowrap ? ' nowrap' : ''}"><code${lang ? ` class="language-${lang}" data-lang="${lang}"` : ''}>`
|
|
906
|
+
preClose = '</code></pre>'
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
preOpen = `<pre${nowrap ? ' class="nowrap"' : ''}>`
|
|
910
|
+
preClose = '</pre>'
|
|
911
|
+
}
|
|
912
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
913
|
+
const titleElement = node.hasTitle()
|
|
914
|
+
? `<div class="title">${node.captionedTitle()}</div>\n`
|
|
915
|
+
: ''
|
|
916
|
+
const role = node.role
|
|
917
|
+
const inner = syntaxHl
|
|
918
|
+
? await syntaxHl.format(node, lang, opts)
|
|
919
|
+
: `${preOpen}${await node.content()}${preClose}`
|
|
920
|
+
return `<div${idAttribute} class="listingblock${role ? ` ${role}` : ''}">
|
|
921
|
+
${titleElement}<div class="content">
|
|
922
|
+
${inner}
|
|
923
|
+
</div>
|
|
924
|
+
</div>`
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async convert_literal(node) {
|
|
928
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
929
|
+
const titleElement = node.hasTitle()
|
|
930
|
+
? `<div class="title">${node.title}</div>\n`
|
|
931
|
+
: ''
|
|
932
|
+
const nowrap =
|
|
933
|
+
!node.document.hasAttribute('prewrap') || node.hasOption('nowrap')
|
|
934
|
+
const role = node.role
|
|
935
|
+
return `<div${idAttribute} class="literalblock${role ? ` ${role}` : ''}">
|
|
936
|
+
${titleElement}<div class="content">
|
|
937
|
+
<pre${nowrap ? ' class="nowrap"' : ''}>${await node.content()}</pre>
|
|
938
|
+
</div>
|
|
939
|
+
</div>`
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async convert_stem(node) {
|
|
943
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
944
|
+
const titleElement = node.hasTitle()
|
|
945
|
+
? `<div class="title">${node.title}</div>\n`
|
|
946
|
+
: ''
|
|
947
|
+
const style = node.style
|
|
948
|
+
const [open, close] = BLOCK_MATH_DELIMITERS[style] ?? ['', '']
|
|
949
|
+
let equation = await node.content()
|
|
950
|
+
if (equation) {
|
|
951
|
+
if (style === 'asciimath' && equation.includes(LF)) {
|
|
952
|
+
const br = `${LF}<br${this._voidSlash}>`
|
|
953
|
+
equation = equation.replace(StemBreakRx, (match) => {
|
|
954
|
+
const newlineCount = (match.match(/\n/g) || []).length
|
|
955
|
+
// Blank lines (\n\n+) produce newlineCount <br>; escaped newlines produce newlineCount - 1.
|
|
956
|
+
const brCount = match[0] === '\n' ? newlineCount : newlineCount - 1
|
|
957
|
+
return `${close}${br.repeat(brCount)}${LF}${open}`
|
|
958
|
+
})
|
|
959
|
+
}
|
|
960
|
+
if (!equation.startsWith(open) || !equation.endsWith(close)) {
|
|
961
|
+
equation = `${open}${equation}${close}`
|
|
962
|
+
}
|
|
963
|
+
} else {
|
|
964
|
+
equation = ''
|
|
965
|
+
}
|
|
966
|
+
const role = node.role
|
|
967
|
+
return `<div${idAttribute} class="stemblock${role ? ` ${role}` : ''}">
|
|
968
|
+
${titleElement}<div class="content">
|
|
969
|
+
${equation}
|
|
970
|
+
</div>
|
|
971
|
+
</div>`
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async convert_olist(node) {
|
|
975
|
+
const result = []
|
|
976
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
977
|
+
const classes = ['olist', node.style, node.role].filter(Boolean)
|
|
978
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
979
|
+
|
|
980
|
+
result.push(`<div${idAttribute}${classAttribute}>`)
|
|
981
|
+
if (node.hasTitle()) result.push(`<div class="title">${node.title}</div>`)
|
|
982
|
+
|
|
983
|
+
const keyword = node.listMarkerKeyword()
|
|
984
|
+
const typeAttribute = keyword ? ` type="${keyword}"` : ''
|
|
985
|
+
const startAttribute = node.hasAttribute('start')
|
|
986
|
+
? ` start="${node.getAttribute('start')}"`
|
|
987
|
+
: ''
|
|
988
|
+
const reversedAttribute = node.hasOption('reversed')
|
|
989
|
+
? this._appendBooleanAttr('reversed', this._xmlMode)
|
|
990
|
+
: ''
|
|
991
|
+
result.push(
|
|
992
|
+
`<ol class="${node.style}"${typeAttribute}${startAttribute}${reversedAttribute}>`
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
for (const item of node.getItems()) {
|
|
996
|
+
if (item.id) {
|
|
997
|
+
result.push(
|
|
998
|
+
`<li id="${item.id}"${item.role ? ` class="${item.role}"` : ''}>`
|
|
999
|
+
)
|
|
1000
|
+
} else if (item.role) {
|
|
1001
|
+
result.push(`<li class="${item.role}">`)
|
|
1002
|
+
} else {
|
|
1003
|
+
result.push('<li>')
|
|
1004
|
+
}
|
|
1005
|
+
result.push(`<p>${item.getText()}</p>`)
|
|
1006
|
+
if (item.hasBlocks()) result.push(await item.content())
|
|
1007
|
+
result.push('</li>')
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
result.push('</ol>')
|
|
1011
|
+
result.push('</div>')
|
|
1012
|
+
return result.join(LF)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async convert_open(node) {
|
|
1016
|
+
const style = node.style
|
|
1017
|
+
if (style === 'abstract') {
|
|
1018
|
+
if (
|
|
1019
|
+
node.getParent() === node.document &&
|
|
1020
|
+
node.document.doctype === 'book'
|
|
1021
|
+
) {
|
|
1022
|
+
this.logger.warn(
|
|
1023
|
+
'abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content.'
|
|
1024
|
+
)
|
|
1025
|
+
return ''
|
|
1026
|
+
}
|
|
1027
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
1028
|
+
const titleEl = node.hasTitle()
|
|
1029
|
+
? `<div class="title">${node.title}</div>\n`
|
|
1030
|
+
: ''
|
|
1031
|
+
const role = node.role
|
|
1032
|
+
return `<div${idAttr} class="quoteblock abstract${role ? ` ${role}` : ''}">
|
|
1033
|
+
${titleEl}<blockquote>
|
|
1034
|
+
${await node.content()}
|
|
1035
|
+
</blockquote>
|
|
1036
|
+
</div>`
|
|
1037
|
+
}
|
|
1038
|
+
if (
|
|
1039
|
+
style === 'partintro' &&
|
|
1040
|
+
(node.level > 0 ||
|
|
1041
|
+
node.getParent().context !== 'section' ||
|
|
1042
|
+
node.document.doctype !== 'book')
|
|
1043
|
+
) {
|
|
1044
|
+
this.logger.error(
|
|
1045
|
+
'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
|
|
1046
|
+
)
|
|
1047
|
+
return ''
|
|
1048
|
+
}
|
|
1049
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
1050
|
+
const titleEl = node.hasTitle()
|
|
1051
|
+
? `<div class="title">${node.title}</div>\n`
|
|
1052
|
+
: ''
|
|
1053
|
+
const role = node.role
|
|
1054
|
+
return `<div${idAttr} class="openblock${style && style !== 'open' ? ` ${style}` : ''}${role ? ` ${role}` : ''}">
|
|
1055
|
+
${titleEl}<div class="content">
|
|
1056
|
+
${await node.content()}
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>`
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
async convert_page_break(_node) {
|
|
1062
|
+
return '<div class="page-break"></div>'
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async convert_paragraph(node) {
|
|
1066
|
+
let attributes
|
|
1067
|
+
if (node.role) {
|
|
1068
|
+
attributes = `${node.id ? ` id="${node.id}"` : ''} class="paragraph ${node.role}"`
|
|
1069
|
+
} else if (node.id) {
|
|
1070
|
+
attributes = ` id="${node.id}" class="paragraph"`
|
|
1071
|
+
} else {
|
|
1072
|
+
attributes = ' class="paragraph"'
|
|
1073
|
+
}
|
|
1074
|
+
if (node.hasTitle()) {
|
|
1075
|
+
return `<div${attributes}>
|
|
1076
|
+
<div class="title">${node.title}</div>
|
|
1077
|
+
<p>${await node.content()}</p>
|
|
1078
|
+
</div>`
|
|
1079
|
+
}
|
|
1080
|
+
return `<div${attributes}>
|
|
1081
|
+
<p>${await node.content()}</p>
|
|
1082
|
+
</div>`
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// alias convert_pass → content_only
|
|
1086
|
+
async convert_pass(node) {
|
|
1087
|
+
return this.contentOnly(node)
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
async convert_preamble(node) {
|
|
1091
|
+
let toc = ''
|
|
1092
|
+
const doc = node.document
|
|
1093
|
+
if (
|
|
1094
|
+
doc.hasAttribute('toc-placement', 'preamble') &&
|
|
1095
|
+
doc.hasSections() &&
|
|
1096
|
+
doc.hasAttribute('toc')
|
|
1097
|
+
) {
|
|
1098
|
+
toc = `
|
|
1099
|
+
<div id="toc" class="${doc.getAttribute('toc-class', 'toc')}">
|
|
1100
|
+
<div id="toctitle">${doc.getAttribute('toc-title')}</div>
|
|
1101
|
+
${await doc.converter.convert(doc, 'outline')}
|
|
1102
|
+
</div>`
|
|
1103
|
+
}
|
|
1104
|
+
return `<div id="preamble">
|
|
1105
|
+
<div class="sectionbody">
|
|
1106
|
+
${await node.content()}
|
|
1107
|
+
</div>${toc}
|
|
1108
|
+
</div>`
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
async convert_quote(node) {
|
|
1112
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1113
|
+
const classes = ['quoteblock', node.role].filter(Boolean)
|
|
1114
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
1115
|
+
const titleElement = node.hasTitle()
|
|
1116
|
+
? `\n<div class="title">${node.title}</div>`
|
|
1117
|
+
: ''
|
|
1118
|
+
const attribution = node.hasAttribute('attribution')
|
|
1119
|
+
? node.getAttribute('attribution')
|
|
1120
|
+
: null
|
|
1121
|
+
const citetitle = node.hasAttribute('citetitle')
|
|
1122
|
+
? node.getAttribute('citetitle')
|
|
1123
|
+
: null
|
|
1124
|
+
let attributionElement = ''
|
|
1125
|
+
if (attribution || citetitle) {
|
|
1126
|
+
const citeElement = citetitle ? `<cite>${citetitle}</cite>` : ''
|
|
1127
|
+
const attributionText = attribution
|
|
1128
|
+
? `— ${attribution}${citetitle ? `<br${this._voidSlash}>\n` : ''}`
|
|
1129
|
+
: ''
|
|
1130
|
+
attributionElement = `\n<div class="attribution">\n${attributionText}${citeElement}\n</div>`
|
|
1131
|
+
}
|
|
1132
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1133
|
+
<blockquote>
|
|
1134
|
+
${await node.content()}
|
|
1135
|
+
</blockquote>${attributionElement}
|
|
1136
|
+
</div>`
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async convert_thematic_break(node) {
|
|
1140
|
+
const classAttribute = node.role ? ` class="${node.role}"` : ''
|
|
1141
|
+
return `<hr${classAttribute}${this._voidSlash}>`
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async convert_sidebar(node) {
|
|
1145
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1146
|
+
const titleElement = node.hasTitle()
|
|
1147
|
+
? `<div class="title">${node.title}</div>\n`
|
|
1148
|
+
: ''
|
|
1149
|
+
const role = node.role
|
|
1150
|
+
return `<div${idAttribute} class="sidebarblock${role ? ` ${role}` : ''}">
|
|
1151
|
+
<div class="content">
|
|
1152
|
+
${titleElement}${await node.content()}
|
|
1153
|
+
</div>
|
|
1154
|
+
</div>`
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
async convert_table(node) {
|
|
1158
|
+
const result = []
|
|
1159
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1160
|
+
let frame = node.getAttribute('frame', 'all', 'table-frame')
|
|
1161
|
+
if (frame === 'topbot') frame = 'ends'
|
|
1162
|
+
const classes = [
|
|
1163
|
+
'tableblock',
|
|
1164
|
+
`frame-${frame}`,
|
|
1165
|
+
`grid-${node.getAttribute('grid', 'all', 'table-grid')}`,
|
|
1166
|
+
]
|
|
1167
|
+
const stripes = node.getAttribute('stripes', null, 'table-stripes')
|
|
1168
|
+
if (stripes) classes.push(`stripes-${stripes}`)
|
|
1169
|
+
let widthAttribute = ''
|
|
1170
|
+
const autowidth = node.hasOption('autowidth')
|
|
1171
|
+
if (autowidth && !node.hasAttribute('width')) {
|
|
1172
|
+
classes.push('fit-content')
|
|
1173
|
+
} else {
|
|
1174
|
+
const tablewidth = node.getAttribute('tablepcwidth')
|
|
1175
|
+
if (Number(tablewidth) === 100) {
|
|
1176
|
+
classes.push('stretch')
|
|
1177
|
+
} else {
|
|
1178
|
+
widthAttribute = ` width="${tablewidth}%"`
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (node.hasAttribute('float')) classes.push(node.getAttribute('float'))
|
|
1182
|
+
if (node.role) classes.push(node.role)
|
|
1183
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
1184
|
+
|
|
1185
|
+
result.push(`<table${idAttribute}${classAttribute}${widthAttribute}>`)
|
|
1186
|
+
if (node.hasTitle())
|
|
1187
|
+
result.push(`<caption class="title">${node.captionedTitle()}</caption>`)
|
|
1188
|
+
|
|
1189
|
+
if (node.getAttribute('rowcount') > 0) {
|
|
1190
|
+
const slash = this._voidSlash
|
|
1191
|
+
result.push('<colgroup>')
|
|
1192
|
+
if (autowidth) {
|
|
1193
|
+
for (let i = 0; i < node.columns.length; i++)
|
|
1194
|
+
result.push(`<col${slash}>`)
|
|
1195
|
+
} else {
|
|
1196
|
+
for (const col of node.columns) {
|
|
1197
|
+
result.push(
|
|
1198
|
+
col.hasOption('autowidth')
|
|
1199
|
+
? `<col${slash}>`
|
|
1200
|
+
: `<col width="${col.getAttribute('colpcwidth')}%"${slash}>`
|
|
1201
|
+
)
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
result.push('</colgroup>')
|
|
1205
|
+
|
|
1206
|
+
for (const [tsec, rows] of node.rows.bySection()) {
|
|
1207
|
+
if (rows.length === 0) continue
|
|
1208
|
+
result.push(`<t${tsec}>`)
|
|
1209
|
+
for (const row of rows) {
|
|
1210
|
+
result.push('<tr>')
|
|
1211
|
+
for (const cell of row) {
|
|
1212
|
+
let cellContent
|
|
1213
|
+
if (tsec === 'head') {
|
|
1214
|
+
cellContent = cell.text
|
|
1215
|
+
} else {
|
|
1216
|
+
switch (cell.style) {
|
|
1217
|
+
case 'asciidoc':
|
|
1218
|
+
cellContent = `<div class="content">${await cell.content()}</div>`
|
|
1219
|
+
break
|
|
1220
|
+
case 'literal':
|
|
1221
|
+
cellContent = `<div class="literal"><pre>${cell.text}</pre></div>`
|
|
1222
|
+
break
|
|
1223
|
+
default: {
|
|
1224
|
+
const parts = await cell.content()
|
|
1225
|
+
cellContent =
|
|
1226
|
+
parts.length === 0
|
|
1227
|
+
? ''
|
|
1228
|
+
: `<p class="tableblock">${parts.join('</p>\n<p class="tableblock">')}</p>`
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const cellTagName =
|
|
1233
|
+
tsec === 'head' || cell.style === 'header' ? 'th' : 'td'
|
|
1234
|
+
const cellClassAttr = ` class="tableblock halign-${cell.getAttribute('halign')} valign-${cell.getAttribute('valign')}"`
|
|
1235
|
+
const cellColspanAttr = cell.colspan
|
|
1236
|
+
? ` colspan="${cell.colspan}"`
|
|
1237
|
+
: ''
|
|
1238
|
+
const cellRowspanAttr = cell.rowspan
|
|
1239
|
+
? ` rowspan="${cell.rowspan}"`
|
|
1240
|
+
: ''
|
|
1241
|
+
// Use the per-cell captured cellbgcolor (set by {set:cellbgcolor:...} in cell text
|
|
1242
|
+
// during precomputeText). Fall back to the current document attribute if not captured.
|
|
1243
|
+
const cellbgcolor =
|
|
1244
|
+
'_cellbgcolor' in cell
|
|
1245
|
+
? cell._cellbgcolor
|
|
1246
|
+
: node.document.attributes.cellbgcolor
|
|
1247
|
+
const cellStyleAttr = cellbgcolor
|
|
1248
|
+
? ` style="background-color: ${cellbgcolor};"`
|
|
1249
|
+
: ''
|
|
1250
|
+
result.push(
|
|
1251
|
+
`<${cellTagName}${cellClassAttr}${cellColspanAttr}${cellRowspanAttr}${cellStyleAttr}>${cellContent}</${cellTagName}>`
|
|
1252
|
+
)
|
|
1253
|
+
}
|
|
1254
|
+
result.push('</tr>')
|
|
1255
|
+
}
|
|
1256
|
+
result.push(`</t${tsec}>`)
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
result.push('</table>')
|
|
1260
|
+
return result.join(LF)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async convert_toc(node) {
|
|
1264
|
+
const doc = node.document
|
|
1265
|
+
if (
|
|
1266
|
+
!doc.hasAttribute('toc-placement', 'macro') ||
|
|
1267
|
+
!doc.hasSections() ||
|
|
1268
|
+
!doc.hasAttribute('toc')
|
|
1269
|
+
) {
|
|
1270
|
+
return '<!-- toc disabled -->'
|
|
1271
|
+
}
|
|
1272
|
+
let idAttr, titleIdAttr
|
|
1273
|
+
if (node.id) {
|
|
1274
|
+
idAttr = ` id="${node.id}"`
|
|
1275
|
+
titleIdAttr = ` id="${node.id}title"`
|
|
1276
|
+
} else {
|
|
1277
|
+
idAttr = ' id="toc"'
|
|
1278
|
+
titleIdAttr = ' id="toctitle"'
|
|
1279
|
+
}
|
|
1280
|
+
const title = node.hasTitle() ? node.title : doc.getAttribute('toc-title')
|
|
1281
|
+
const levels = node.hasAttribute('levels')
|
|
1282
|
+
? parseInt(node.getAttribute('levels'), 10)
|
|
1283
|
+
: null
|
|
1284
|
+
const role = node.hasRoleAttribute()
|
|
1285
|
+
? node.role
|
|
1286
|
+
: doc.getAttribute('toc-class', 'toc')
|
|
1287
|
+
return `<div${idAttr} class="${role}">
|
|
1288
|
+
<div${titleIdAttr} class="title">${title}</div>
|
|
1289
|
+
${await doc.converter.convert(doc, 'outline', levels != null ? { toclevels: levels } : {})}
|
|
1290
|
+
</div>`
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
async convert_ulist(node) {
|
|
1294
|
+
const result = []
|
|
1295
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1296
|
+
const divClasses = ['ulist', node.style, node.role].filter(Boolean)
|
|
1297
|
+
let markerChecked = ''
|
|
1298
|
+
let markerUnchecked = ''
|
|
1299
|
+
let ulClassAttribute
|
|
1300
|
+
const checklist = node.hasOption('checklist')
|
|
1301
|
+
if (checklist) {
|
|
1302
|
+
divClasses.splice(1, 0, 'checklist')
|
|
1303
|
+
ulClassAttribute = ' class="checklist"'
|
|
1304
|
+
if (node.hasOption('interactive')) {
|
|
1305
|
+
if (this._xmlMode) {
|
|
1306
|
+
markerChecked =
|
|
1307
|
+
'<input type="checkbox" data-item-complete="1" checked="checked"/> '
|
|
1308
|
+
markerUnchecked = '<input type="checkbox" data-item-complete="0"/> '
|
|
1309
|
+
} else {
|
|
1310
|
+
markerChecked =
|
|
1311
|
+
'<input type="checkbox" data-item-complete="1" checked> '
|
|
1312
|
+
markerUnchecked = '<input type="checkbox" data-item-complete="0"> '
|
|
1313
|
+
}
|
|
1314
|
+
} else if (node.document.hasAttribute('icons', 'font')) {
|
|
1315
|
+
markerChecked = '<i class="fa fa-check-square-o"></i> '
|
|
1316
|
+
markerUnchecked = '<i class="fa fa-square-o"></i> '
|
|
1317
|
+
} else {
|
|
1318
|
+
markerChecked = '✓ '
|
|
1319
|
+
markerUnchecked = '❏ '
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
ulClassAttribute = node.style ? ` class="${node.style}"` : ''
|
|
1323
|
+
}
|
|
1324
|
+
result.push(`<div${idAttribute} class="${divClasses.join(' ')}">`)
|
|
1325
|
+
if (node.hasTitle()) result.push(`<div class="title">${node.title}</div>`)
|
|
1326
|
+
result.push(`<ul${ulClassAttribute}>`)
|
|
1327
|
+
|
|
1328
|
+
for (const item of node.getItems()) {
|
|
1329
|
+
if (item.id) {
|
|
1330
|
+
result.push(
|
|
1331
|
+
`<li id="${item.id}"${item.role ? ` class="${item.role}"` : ''}>`
|
|
1332
|
+
)
|
|
1333
|
+
} else if (item.role) {
|
|
1334
|
+
result.push(`<li class="${item.role}">`)
|
|
1335
|
+
} else {
|
|
1336
|
+
result.push('<li>')
|
|
1337
|
+
}
|
|
1338
|
+
if (checklist && item.hasAttribute('checkbox')) {
|
|
1339
|
+
result.push(
|
|
1340
|
+
`<p>${item.hasAttribute('checked') ? markerChecked : markerUnchecked}${item.getText()}</p>`
|
|
1341
|
+
)
|
|
1342
|
+
} else {
|
|
1343
|
+
result.push(`<p>${item.getText()}</p>`)
|
|
1344
|
+
}
|
|
1345
|
+
if (item.hasBlocks()) result.push(await item.content())
|
|
1346
|
+
result.push('</li>')
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
result.push('</ul>')
|
|
1350
|
+
result.push('</div>')
|
|
1351
|
+
return result.join(LF)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
async convert_verse(node) {
|
|
1355
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1356
|
+
const classes = ['verseblock', node.role].filter(Boolean)
|
|
1357
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
1358
|
+
const titleElement = node.hasTitle()
|
|
1359
|
+
? `\n<div class="title">${node.title}</div>`
|
|
1360
|
+
: ''
|
|
1361
|
+
const attribution = node.hasAttribute('attribution')
|
|
1362
|
+
? node.getAttribute('attribution')
|
|
1363
|
+
: null
|
|
1364
|
+
const citetitle = node.hasAttribute('citetitle')
|
|
1365
|
+
? node.getAttribute('citetitle')
|
|
1366
|
+
: null
|
|
1367
|
+
let attributionElement = ''
|
|
1368
|
+
if (attribution || citetitle) {
|
|
1369
|
+
const citeElement = citetitle ? `<cite>${citetitle}</cite>` : ''
|
|
1370
|
+
const attributionText = attribution
|
|
1371
|
+
? `— ${attribution}${citetitle ? `<br${this._voidSlash}>\n` : ''}`
|
|
1372
|
+
: ''
|
|
1373
|
+
attributionElement = `\n<div class="attribution">\n${attributionText}${citeElement}\n</div>`
|
|
1374
|
+
}
|
|
1375
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1376
|
+
<pre class="content">${await node.content()}</pre>${attributionElement}
|
|
1377
|
+
</div>`
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
async convert_video(node) {
|
|
1381
|
+
const xml = this._xmlMode
|
|
1382
|
+
const idAttribute = node.id ? ` id="${node.id}"` : ''
|
|
1383
|
+
const classes = ['videoblock']
|
|
1384
|
+
if (node.hasAttribute('float')) classes.push(node.getAttribute('float'))
|
|
1385
|
+
if (node.hasAttribute('align'))
|
|
1386
|
+
classes.push(`text-${node.getAttribute('align')}`)
|
|
1387
|
+
if (node.role) classes.push(node.role)
|
|
1388
|
+
const classAttribute = ` class="${classes.join(' ')}"`
|
|
1389
|
+
const titleElement = node.hasTitle()
|
|
1390
|
+
? `\n<div class="title">${node.title}</div>`
|
|
1391
|
+
: ''
|
|
1392
|
+
const widthAttribute = node.hasAttribute('width')
|
|
1393
|
+
? ` width="${node.getAttribute('width')}"`
|
|
1394
|
+
: ''
|
|
1395
|
+
const heightAttribute = node.hasAttribute('height')
|
|
1396
|
+
? ` height="${node.getAttribute('height')}"`
|
|
1397
|
+
: ''
|
|
1398
|
+
|
|
1399
|
+
switch (node.getAttribute('poster')) {
|
|
1400
|
+
case 'vimeo': {
|
|
1401
|
+
let assetUriScheme = node.document.getAttribute(
|
|
1402
|
+
'asset-uri-scheme',
|
|
1403
|
+
'https'
|
|
1404
|
+
)
|
|
1405
|
+
if (assetUriScheme) assetUriScheme = `${assetUriScheme}:`
|
|
1406
|
+
const startAnchor = node.hasAttribute('start')
|
|
1407
|
+
? `#at=${node.getAttribute('start')}`
|
|
1408
|
+
: ''
|
|
1409
|
+
const delimiter = ['?']
|
|
1410
|
+
let [target, hash] = node.getAttribute('target').split('/', 2)
|
|
1411
|
+
hash ||= node.getAttribute('hash')
|
|
1412
|
+
const hashParam = hash ? `${delimiter.pop() || '&'}h=${hash}` : ''
|
|
1413
|
+
const autoplayParam = node.hasOption('autoplay')
|
|
1414
|
+
? `${delimiter.pop() || '&'}autoplay=1`
|
|
1415
|
+
: ''
|
|
1416
|
+
const loopParam = node.hasOption('loop')
|
|
1417
|
+
? `${delimiter.pop() || '&'}loop=1`
|
|
1418
|
+
: ''
|
|
1419
|
+
const mutedParam = node.hasOption('muted')
|
|
1420
|
+
? `${delimiter.pop() || '&'}muted=1`
|
|
1421
|
+
: ''
|
|
1422
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1423
|
+
<div class="content">
|
|
1424
|
+
<iframe${widthAttribute}${heightAttribute} src="${assetUriScheme}//player.vimeo.com/video/${target}${hashParam}${autoplayParam}${loopParam}${mutedParam}${startAnchor}" frameborder="0"${node.hasOption('nofullscreen') ? '' : this._appendBooleanAttr('allowfullscreen', xml)}></iframe>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>`
|
|
1427
|
+
}
|
|
1428
|
+
case 'youtube': {
|
|
1429
|
+
let assetUriScheme = node.document.getAttribute(
|
|
1430
|
+
'asset-uri-scheme',
|
|
1431
|
+
'https'
|
|
1432
|
+
)
|
|
1433
|
+
if (assetUriScheme) assetUriScheme = `${assetUriScheme}:`
|
|
1434
|
+
const relParamVal = node.hasOption('related') ? 1 : 0
|
|
1435
|
+
const startParam = node.hasAttribute('start')
|
|
1436
|
+
? `&start=${node.getAttribute('start')}`
|
|
1437
|
+
: ''
|
|
1438
|
+
const endParam = node.hasAttribute('end')
|
|
1439
|
+
? `&end=${node.getAttribute('end')}`
|
|
1440
|
+
: ''
|
|
1441
|
+
const autoplayParam = node.hasOption('autoplay')
|
|
1442
|
+
? '&autoplay=1'
|
|
1443
|
+
: ''
|
|
1444
|
+
const hasLoopParam = node.hasOption('loop')
|
|
1445
|
+
const loopParam = hasLoopParam ? '&loop=1' : ''
|
|
1446
|
+
const muteParam = node.hasOption('muted') ? '&mute=1' : ''
|
|
1447
|
+
const controlsParam = node.hasOption('nocontrols')
|
|
1448
|
+
? '&controls=0'
|
|
1449
|
+
: ''
|
|
1450
|
+
let fsParam, fsAttribute
|
|
1451
|
+
if (node.hasOption('nofullscreen')) {
|
|
1452
|
+
fsParam = '&fs=0'
|
|
1453
|
+
fsAttribute = ''
|
|
1454
|
+
} else {
|
|
1455
|
+
fsParam = ''
|
|
1456
|
+
fsAttribute = this._appendBooleanAttr('allowfullscreen', xml)
|
|
1457
|
+
}
|
|
1458
|
+
const modestParam = node.hasOption('modest')
|
|
1459
|
+
? '&modestbranding=1'
|
|
1460
|
+
: ''
|
|
1461
|
+
const themeParam = node.hasAttribute('theme')
|
|
1462
|
+
? `&theme=${node.getAttribute('theme')}`
|
|
1463
|
+
: ''
|
|
1464
|
+
const hlParam = node.hasAttribute('lang')
|
|
1465
|
+
? `&hl=${node.getAttribute('lang')}`
|
|
1466
|
+
: ''
|
|
1467
|
+
let [target, list] = node.getAttribute('target').split('/', 2)
|
|
1468
|
+
list ||= node.getAttribute('list')
|
|
1469
|
+
let listParam
|
|
1470
|
+
if (list) {
|
|
1471
|
+
listParam = `&list=${list}`
|
|
1472
|
+
} else {
|
|
1473
|
+
let playlist
|
|
1474
|
+
const videoParts = target.split(',')
|
|
1475
|
+
target = videoParts[0]
|
|
1476
|
+
playlist =
|
|
1477
|
+
videoParts.length > 1 ? videoParts.slice(1).join(',') : null
|
|
1478
|
+
playlist ||= node.getAttribute('playlist')
|
|
1479
|
+
if (playlist) {
|
|
1480
|
+
listParam = `&playlist=${target},${playlist}`
|
|
1481
|
+
} else {
|
|
1482
|
+
listParam = hasLoopParam ? `&playlist=${target}` : ''
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1486
|
+
<div class="content">
|
|
1487
|
+
<iframe${widthAttribute}${heightAttribute} src="${assetUriScheme}//www.youtube.com/embed/${target}?rel=${relParamVal}${startParam}${endParam}${autoplayParam}${loopParam}${muteParam}${controlsParam}${listParam}${fsParam}${modestParam}${themeParam}${hlParam}" frameborder="0"${fsAttribute}></iframe>
|
|
1488
|
+
</div>
|
|
1489
|
+
</div>`
|
|
1490
|
+
}
|
|
1491
|
+
case 'wistia': {
|
|
1492
|
+
let assetUriScheme = node.document.getAttribute(
|
|
1493
|
+
'asset-uri-scheme',
|
|
1494
|
+
'https'
|
|
1495
|
+
)
|
|
1496
|
+
if (assetUriScheme) assetUriScheme = `${assetUriScheme}:`
|
|
1497
|
+
const delimiter = ['?']
|
|
1498
|
+
const startAnchor = node.hasAttribute('start')
|
|
1499
|
+
? `${delimiter.pop() || '&'}time=${node.getAttribute('start')}`
|
|
1500
|
+
: ''
|
|
1501
|
+
const endVideoBehaviorParam = node.hasOption('loop')
|
|
1502
|
+
? `${delimiter.pop() || '&'}endVideoBehavior=loop`
|
|
1503
|
+
: node.hasOption('reset')
|
|
1504
|
+
? `${delimiter.pop() || '&'}endVideoBehavior=reset`
|
|
1505
|
+
: ''
|
|
1506
|
+
const target = node.getAttribute('target')
|
|
1507
|
+
const autoplayParam = node.hasOption('autoplay')
|
|
1508
|
+
? `${delimiter.pop() || '&'}autoPlay=true`
|
|
1509
|
+
: ''
|
|
1510
|
+
const mutedParam = node.hasOption('muted')
|
|
1511
|
+
? `${delimiter.pop() || '&'}muted=true`
|
|
1512
|
+
: ''
|
|
1513
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1514
|
+
<div class="content">
|
|
1515
|
+
<iframe${widthAttribute}${heightAttribute} src="${assetUriScheme}//fast.wistia.com/embed/iframe/${target}${startAnchor}${autoplayParam}${endVideoBehaviorParam}${mutedParam}" frameborder="0"${node.hasOption('nofullscreen') ? '' : this._appendBooleanAttr('allowfullscreen', xml)} class="wistia_embed" name="wistia_embed"></iframe>
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>`
|
|
1518
|
+
}
|
|
1519
|
+
default: {
|
|
1520
|
+
const posterVal = node.getAttribute('poster')
|
|
1521
|
+
const posterAttribute = !posterVal
|
|
1522
|
+
? ''
|
|
1523
|
+
: ` poster="${node.mediaUri(posterVal)}"`
|
|
1524
|
+
const preloadVal = node.getAttribute('preload')
|
|
1525
|
+
const preloadAttribute = !preloadVal ? '' : ` preload="${preloadVal}"`
|
|
1526
|
+
const startT = node.getAttribute('start')
|
|
1527
|
+
const endT = node.getAttribute('end')
|
|
1528
|
+
const timeAnchor =
|
|
1529
|
+
startT || endT ? `#t=${startT || ''}${endT ? `,${endT}` : ''}` : ''
|
|
1530
|
+
return `<div${idAttribute}${classAttribute}>${titleElement}
|
|
1531
|
+
<div class="content">
|
|
1532
|
+
<video src="${node.mediaUri(node.getAttribute('target'))}${timeAnchor}"${widthAttribute}${heightAttribute}${posterAttribute}${node.hasOption('autoplay') ? this._appendBooleanAttr('autoplay', xml) : ''}${node.hasOption('muted') ? this._appendBooleanAttr('muted', xml) : ''}${node.hasOption('nocontrols') ? '' : this._appendBooleanAttr('controls', xml)}${node.hasOption('loop') ? this._appendBooleanAttr('loop', xml) : ''}${preloadAttribute}>
|
|
1533
|
+
Your browser does not support the video tag.
|
|
1534
|
+
</video>
|
|
1535
|
+
</div>
|
|
1536
|
+
</div>`
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
async convert_inline_anchor(node) {
|
|
1542
|
+
switch (node.type) {
|
|
1543
|
+
case 'xref': {
|
|
1544
|
+
let attrs, text
|
|
1545
|
+
if (node.attributes.path) {
|
|
1546
|
+
attrs = this._appendLinkConstraintAttrs(
|
|
1547
|
+
node,
|
|
1548
|
+
node.role ? [` class="${node.role}"`] : []
|
|
1549
|
+
).join('')
|
|
1550
|
+
text = node.text || node.attributes.path
|
|
1551
|
+
} else {
|
|
1552
|
+
attrs = node.role ? ` class="${node.role}"` : ''
|
|
1553
|
+
if (!(text = node.text)) {
|
|
1554
|
+
const refs = (this._refs ??= node.document.catalog.refs)
|
|
1555
|
+
const refid = node.attributes.refid
|
|
1556
|
+
let top
|
|
1557
|
+
const ref =
|
|
1558
|
+
refs[refid] ??
|
|
1559
|
+
(!refid ? (top = this._getRootDocument(node)) : null)
|
|
1560
|
+
if (ref instanceof AbstractNode) {
|
|
1561
|
+
const resolvingSet = (this._resolvingXrefs ??= new Set())
|
|
1562
|
+
if (!resolvingSet.has(refid)) {
|
|
1563
|
+
resolvingSet.add(refid)
|
|
1564
|
+
const resolved = await ref.xreftext(
|
|
1565
|
+
node.getAttribute('xrefstyle', null, true)
|
|
1566
|
+
)
|
|
1567
|
+
resolvingSet.delete(refid)
|
|
1568
|
+
if (resolved) {
|
|
1569
|
+
text = resolved.includes('<a')
|
|
1570
|
+
? resolved.replace(new RegExp(DropAnchorRx.source, 'g'), '')
|
|
1571
|
+
: resolved
|
|
1572
|
+
} else {
|
|
1573
|
+
text = top ? '[^top]' : `[${refid}]`
|
|
1574
|
+
}
|
|
1575
|
+
} else {
|
|
1576
|
+
text = top ? '[^top]' : `[${refid}]`
|
|
1577
|
+
}
|
|
1578
|
+
} else {
|
|
1579
|
+
text = `[${refid}]`
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return `<a href="${node.target}"${attrs}>${text}</a>`
|
|
1584
|
+
}
|
|
1585
|
+
case 'ref':
|
|
1586
|
+
return `<a id="${node.id}"></a>`
|
|
1587
|
+
case 'link': {
|
|
1588
|
+
const attrs = node.id ? [` id="${node.id}"`] : []
|
|
1589
|
+
if (node.role) attrs.push(` class="${node.role}"`)
|
|
1590
|
+
if (node.hasAttribute('title'))
|
|
1591
|
+
attrs.push(` title="${node.getAttribute('title')}"`)
|
|
1592
|
+
return `<a href="${node.target}"${this._appendLinkConstraintAttrs(node, attrs).join('')}>${node.text ?? ''}</a>`
|
|
1593
|
+
}
|
|
1594
|
+
case 'bibref':
|
|
1595
|
+
return `<a id="${node.id}"></a>[${node.reftext || node.id}]`
|
|
1596
|
+
default:
|
|
1597
|
+
this.logger.warn(`unknown anchor type: ${node.type}`)
|
|
1598
|
+
return null
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
async convert_inline_break(node) {
|
|
1603
|
+
return `${node.text}<br${this._voidSlash}>`
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
async convert_inline_button(node) {
|
|
1607
|
+
return `<b class="button">${node.text}</b>`
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
async convert_inline_callout(node) {
|
|
1611
|
+
if (node.document.hasAttribute('icons', 'font')) {
|
|
1612
|
+
return `<i class="conum" data-value="${node.text}"></i><b>(${node.text})</b>`
|
|
1613
|
+
}
|
|
1614
|
+
if (node.document.hasAttribute('icons')) {
|
|
1615
|
+
const src = await node.iconUri(`callouts/${node.text}`)
|
|
1616
|
+
return `<img src="${src}" alt="${node.text}"${this._voidSlash}>`
|
|
1617
|
+
}
|
|
1618
|
+
const guard = node.attributes.guard
|
|
1619
|
+
if (Array.isArray(guard)) {
|
|
1620
|
+
return `<!--<b class="conum">(${node.text})</b>-->`
|
|
1621
|
+
}
|
|
1622
|
+
return `${guard ?? ''}<b class="conum">(${node.text})</b>`
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
async convert_inline_footnote(node) {
|
|
1626
|
+
const index = node.getAttribute('index')
|
|
1627
|
+
if (index) {
|
|
1628
|
+
if (node.type === 'xref') {
|
|
1629
|
+
return `<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_${index}" title="View footnote.">${index}</a>]</sup>`
|
|
1630
|
+
}
|
|
1631
|
+
const idAttr = node.id ? ` id="_footnote_${node.id}"` : ''
|
|
1632
|
+
return `<sup class="footnote"${idAttr}>[<a id="_footnoteref_${index}" class="footnote" href="#_footnotedef_${index}" title="View footnote.">${index}</a>]</sup>`
|
|
1633
|
+
}
|
|
1634
|
+
if (node.type === 'xref') {
|
|
1635
|
+
return `<sup class="footnoteref red" title="Unresolved footnote reference.">[${node.text}]</sup>`
|
|
1636
|
+
}
|
|
1637
|
+
return null
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
async convert_inline_image(node) {
|
|
1641
|
+
const target = node.target
|
|
1642
|
+
const type = node.type || 'image'
|
|
1643
|
+
let img, src
|
|
1644
|
+
if (type === 'icon') {
|
|
1645
|
+
const icons = node.document.getAttribute('icons')
|
|
1646
|
+
if (icons === 'font') {
|
|
1647
|
+
let iClassAttrVal = `fa fa-${target}`
|
|
1648
|
+
if (node.hasAttribute('size'))
|
|
1649
|
+
iClassAttrVal += ` fa-${node.getAttribute('size')}`
|
|
1650
|
+
if (node.hasAttribute('flip')) {
|
|
1651
|
+
iClassAttrVal += ` fa-flip-${node.getAttribute('flip')}`
|
|
1652
|
+
} else if (node.hasAttribute('rotate')) {
|
|
1653
|
+
iClassAttrVal += ` fa-rotate-${node.getAttribute('rotate')}`
|
|
1654
|
+
}
|
|
1655
|
+
const titleAttr = node.hasAttribute('title')
|
|
1656
|
+
? ` title="${node.getAttribute('title')}"`
|
|
1657
|
+
: ''
|
|
1658
|
+
img = `<i class="${iClassAttrVal}"${titleAttr}></i>`
|
|
1659
|
+
} else if (icons != null) {
|
|
1660
|
+
let attrs = node.hasAttribute('width')
|
|
1661
|
+
? ` width="${node.getAttribute('width')}"`
|
|
1662
|
+
: ''
|
|
1663
|
+
if (node.hasAttribute('height'))
|
|
1664
|
+
attrs += ` height="${node.getAttribute('height')}"`
|
|
1665
|
+
if (node.hasAttribute('title'))
|
|
1666
|
+
attrs += ` title="${node.getAttribute('title')}"`
|
|
1667
|
+
img = `<img src="${await node.iconUri(target)}" alt="${this._encodeAttrValue(node.getAlt())}"${attrs}${this._voidSlash}>`
|
|
1668
|
+
} else {
|
|
1669
|
+
img = `[${node.getAlt()}]`
|
|
1670
|
+
}
|
|
1671
|
+
} else {
|
|
1672
|
+
let attrs = node.hasAttribute('width')
|
|
1673
|
+
? ` width="${node.getAttribute('width')}"`
|
|
1674
|
+
: ''
|
|
1675
|
+
if (node.hasAttribute('height'))
|
|
1676
|
+
attrs += ` height="${node.getAttribute('height')}"`
|
|
1677
|
+
if (node.hasAttribute('title'))
|
|
1678
|
+
attrs += ` title="${node.getAttribute('title')}"`
|
|
1679
|
+
if (
|
|
1680
|
+
(node.hasAttribute('format', 'svg') || target.includes('.svg')) &&
|
|
1681
|
+
node.document.safe < SafeMode.SECURE
|
|
1682
|
+
) {
|
|
1683
|
+
if (node.hasOption('inline')) {
|
|
1684
|
+
img =
|
|
1685
|
+
(await this.readSvgContents(node, target)) ||
|
|
1686
|
+
`<span class="alt">${node.getAlt()}</span>`
|
|
1687
|
+
} else if (
|
|
1688
|
+
node.hasOption('interactive') &&
|
|
1689
|
+
node.document.safe >= SafeMode.SERVER
|
|
1690
|
+
) {
|
|
1691
|
+
const fallback = node.hasAttribute('fallback')
|
|
1692
|
+
? `<img src="${await node.imageUri(node.getAttribute('fallback'))}" alt="${this._encodeAttrValue(node.getAlt())}"${attrs}${this._voidSlash}>`
|
|
1693
|
+
: `<span class="alt">${node.getAlt()}</span>`
|
|
1694
|
+
src = await node.imageUri(target)
|
|
1695
|
+
img = `<object type="image/svg+xml" data="${src}"${attrs}>${fallback}</object>`
|
|
1696
|
+
} else {
|
|
1697
|
+
src = await node.imageUri(target)
|
|
1698
|
+
img = `<img src="${src}" alt="${this._encodeAttrValue(node.getAlt())}"${attrs}${this._voidSlash}>`
|
|
1699
|
+
}
|
|
1700
|
+
} else {
|
|
1701
|
+
src = await node.imageUri(target)
|
|
1702
|
+
img = `<img src="${src}" alt="${this._encodeAttrValue(node.getAlt())}"${attrs}${this._voidSlash}>`
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
if (node.hasAttribute('link')) {
|
|
1707
|
+
let hrefAttrVal = node.getAttribute('link')
|
|
1708
|
+
if (hrefAttrVal === 'self') hrefAttrVal = src
|
|
1709
|
+
if (hrefAttrVal) {
|
|
1710
|
+
img = `<a class="image" href="${hrefAttrVal}"${this._appendLinkConstraintAttrs(node).join('')}>${img}</a>`
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
const idAttr = node.id ? ` id="${node.id}"` : ''
|
|
1715
|
+
let classAttrVal = type
|
|
1716
|
+
const role = node.role
|
|
1717
|
+
if (role) {
|
|
1718
|
+
classAttrVal = node.hasAttribute('float')
|
|
1719
|
+
? `${classAttrVal} ${node.getAttribute('float')} ${role}`
|
|
1720
|
+
: `${classAttrVal} ${role}`
|
|
1721
|
+
} else if (node.hasAttribute('float')) {
|
|
1722
|
+
classAttrVal = `${classAttrVal} ${node.getAttribute('float')}`
|
|
1723
|
+
}
|
|
1724
|
+
return `<span${idAttr} class="${classAttrVal}">${img}</span>`
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
async convert_inline_indexterm(node) {
|
|
1728
|
+
return node.type === 'visible' ? node.text : ''
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
async convert_inline_kbd(node) {
|
|
1732
|
+
const keys = node.getAttribute('keys')
|
|
1733
|
+
if (keys.length === 1) {
|
|
1734
|
+
return `<kbd>${keys[0]}</kbd>`
|
|
1735
|
+
}
|
|
1736
|
+
return `<span class="keyseq"><kbd>${keys.join('</kbd>+<kbd>')}</kbd></span>`
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
async convert_inline_menu(node) {
|
|
1740
|
+
const caret = node.document.hasAttribute('icons', 'font')
|
|
1741
|
+
? ' <i class="fa fa-angle-right caret"></i> '
|
|
1742
|
+
: ' <b class="caret">›</b> '
|
|
1743
|
+
const submenuJoiner = `</b>${caret}<b class="submenu">`
|
|
1744
|
+
const menu = node.getAttribute('menu')
|
|
1745
|
+
const submenus = node.getAttribute('submenus')
|
|
1746
|
+
if (!submenus || submenus.length === 0) {
|
|
1747
|
+
const menuitem = node.getAttribute('menuitem')
|
|
1748
|
+
if (menuitem) {
|
|
1749
|
+
return `<span class="menuseq"><b class="menu">${menu}</b>${caret}<b class="menuitem">${menuitem}</b></span>`
|
|
1750
|
+
}
|
|
1751
|
+
return `<b class="menuref">${menu}</b>`
|
|
1752
|
+
}
|
|
1753
|
+
return `<span class="menuseq"><b class="menu">${menu}</b>${caret}<b class="submenu">${submenus.join(submenuJoiner)}</b>${caret}<b class="menuitem">${node.getAttribute('menuitem')}</b></span>`
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
async convert_inline_quoted(node) {
|
|
1757
|
+
const [open, close, tag] = QUOTE_TAGS[node.type] ?? DEFAULT_QUOTE_TAG
|
|
1758
|
+
if (node.id) {
|
|
1759
|
+
const classAttr = node.role ? ` class="${node.role}"` : ''
|
|
1760
|
+
if (tag) {
|
|
1761
|
+
return `${open.slice(0, -1)} id="${node.id}"${classAttr}>${node.text}${close}`
|
|
1762
|
+
}
|
|
1763
|
+
return `<span id="${node.id}"${classAttr}>${open}${node.text}${close}</span>`
|
|
1764
|
+
}
|
|
1765
|
+
if (node.role) {
|
|
1766
|
+
if (tag) {
|
|
1767
|
+
return `${open.slice(0, -1)} class="${node.role}">${node.text}${close}`
|
|
1768
|
+
}
|
|
1769
|
+
return `<span class="${node.role}">${open}${node.text}${close}</span>`
|
|
1770
|
+
}
|
|
1771
|
+
return `${open}${node.text}${close}`
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// NOTE expose readSvgContents for Bespoke converter
|
|
1775
|
+
async readSvgContents(node, target) {
|
|
1776
|
+
const imagesdir = node.document.getAttribute('imagesdir')
|
|
1777
|
+
let resolvedPath
|
|
1778
|
+
let svg
|
|
1779
|
+
if (isUriish(target) || (imagesdir && isUriish(imagesdir))) {
|
|
1780
|
+
svg = await node.readContents(target, {
|
|
1781
|
+
start: imagesdir,
|
|
1782
|
+
normalize: true,
|
|
1783
|
+
warnOnFailure: true,
|
|
1784
|
+
label: 'SVG',
|
|
1785
|
+
})
|
|
1786
|
+
resolvedPath = target
|
|
1787
|
+
} else {
|
|
1788
|
+
resolvedPath = node.normalizeSystemPath(target, imagesdir, null, {
|
|
1789
|
+
targetName: 'image',
|
|
1790
|
+
})
|
|
1791
|
+
svg = await node.readAsset(resolvedPath, {
|
|
1792
|
+
normalize: true,
|
|
1793
|
+
warnOnFailure: true,
|
|
1794
|
+
label: 'SVG',
|
|
1795
|
+
})
|
|
1796
|
+
}
|
|
1797
|
+
if (svg == null) return null // file not found/readable; warning already emitted
|
|
1798
|
+
if (!svg) {
|
|
1799
|
+
node.logger.warn(`contents of SVG is empty: ${resolvedPath}`)
|
|
1800
|
+
return null
|
|
1801
|
+
}
|
|
1802
|
+
if (!svg.startsWith('<svg')) svg = svg.replace(SvgPreambleRx, '')
|
|
1803
|
+
// Fix incomplete SVG start tag (missing closing >) by inserting > before the first child element.
|
|
1804
|
+
// This handles cases like: <svg width="500"\n<circle .../> where the > is missing.
|
|
1805
|
+
svg = svg.replace(
|
|
1806
|
+
/^(<svg\b[^<>]*?)(\s*<[^/!])/s,
|
|
1807
|
+
(_, pre, rest) => `${pre.trimEnd()}>${rest}`
|
|
1808
|
+
)
|
|
1809
|
+
let oldStartTag = null
|
|
1810
|
+
let newStartTag = null
|
|
1811
|
+
let startTagMatch = null
|
|
1812
|
+
for (const dim of ['width', 'height']) {
|
|
1813
|
+
if (!node.hasAttribute(dim)) continue
|
|
1814
|
+
if (!newStartTag) {
|
|
1815
|
+
if (startTagMatch === null)
|
|
1816
|
+
startTagMatch = svg.match(SvgStartTagRx) || false
|
|
1817
|
+
if (!startTagMatch) continue
|
|
1818
|
+
oldStartTag = startTagMatch[0]
|
|
1819
|
+
newStartTag = oldStartTag.replace(
|
|
1820
|
+
new RegExp(DimensionAttributeRx.source, 'g'),
|
|
1821
|
+
''
|
|
1822
|
+
)
|
|
1823
|
+
}
|
|
1824
|
+
newStartTag = `${newStartTag.slice(0, -1)} ${dim}="${node.getAttribute(dim)}">`
|
|
1825
|
+
}
|
|
1826
|
+
if (newStartTag) svg = `${newStartTag}${svg.slice(oldStartTag.length)}`
|
|
1827
|
+
return svg
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* @internal
|
|
1834
|
+
* @private
|
|
1835
|
+
*/
|
|
1836
|
+
_appendBooleanAttr(name, xml) {
|
|
1837
|
+
return xml ? ` ${name}="${name}"` : ` ${name}`
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* @internal
|
|
1842
|
+
* @private
|
|
1843
|
+
*/
|
|
1844
|
+
_appendLinkConstraintAttrs(node, attrs = []) {
|
|
1845
|
+
const rel = node.hasOption('nofollow') ? 'nofollow' : null
|
|
1846
|
+
const window = node.attributes.window
|
|
1847
|
+
if (window) {
|
|
1848
|
+
attrs.push(` target="${window}"`)
|
|
1849
|
+
if (window === '_blank' || node.hasOption('noopener')) {
|
|
1850
|
+
attrs.push(rel ? ` rel="${rel} noopener"` : ' rel="noopener"')
|
|
1851
|
+
}
|
|
1852
|
+
} else if (rel) {
|
|
1853
|
+
attrs.push(` rel="${rel}"`)
|
|
1854
|
+
}
|
|
1855
|
+
return attrs
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* @internal
|
|
1860
|
+
* @private
|
|
1861
|
+
*/
|
|
1862
|
+
_encodeAttrValue(val) {
|
|
1863
|
+
return val.includes('"') ? val.replace(/"/g, '"') : val
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* @internal
|
|
1868
|
+
* @private
|
|
1869
|
+
*/
|
|
1870
|
+
_generateMannameSection(node) {
|
|
1871
|
+
let mannameTitle = node.getAttribute('manname-title', 'Name')
|
|
1872
|
+
const sections = node.sections()
|
|
1873
|
+
if (sections.length > 0) {
|
|
1874
|
+
const nextSectionTitle = sections[0].title
|
|
1875
|
+
if (nextSectionTitle === nextSectionTitle.toUpperCase()) {
|
|
1876
|
+
mannameTitle = mannameTitle.toUpperCase()
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const mannameId = node.getAttribute('manname-id')
|
|
1880
|
+
const mannameIdAttr = mannameId ? ` id="${mannameId}"` : ''
|
|
1881
|
+
return `<h2${mannameIdAttr}>${mannameTitle}</h2>
|
|
1882
|
+
<div class="sectionbody">
|
|
1883
|
+
<p>${node.getAttribute('mannames').join(', ')} - ${node.getAttribute('manpurpose')}</p>
|
|
1884
|
+
</div>`
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/**
|
|
1888
|
+
* @internal
|
|
1889
|
+
* @private
|
|
1890
|
+
*/
|
|
1891
|
+
_getRootDocument(node) {
|
|
1892
|
+
while ((node = node.document).isNested()) {
|
|
1893
|
+
node = node.parentDocument
|
|
1894
|
+
}
|
|
1895
|
+
return node
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
Html5Converter.registerFor('html5')
|