@antora/assembler 1.0.0-alpha.9 → 1.0.0-beta.10
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 +7 -4
- package/adapters/asciidoctor/jsonl-logger.rb +25 -0
- package/lib/assemble-content.js +277 -26
- package/lib/configure.js +93 -0
- package/lib/index.js +2 -2
- package/lib/load-config.js +67 -24
- package/lib/produce-assembly-file.js +825 -0
- package/lib/produce-assembly-files.js +215 -0
- package/lib/util/compute-out.js +57 -0
- package/lib/util/create-asciidoc-file.js +17 -0
- package/lib/util/parse-resource-ref.js +36 -0
- package/package.json +18 -13
- package/lib/asciidoctor/reducer-extension.js +0 -235
- package/lib/produce-aggregate-document.js +0 -599
- package/lib/produce-aggregate-documents.js +0 -145
- package/lib/util/run-command.js +0 -60
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createAsciiDocFile = require('./util/create-asciidoc-file')
|
|
4
|
+
const parseResourceRef = require('./util/parse-resource-ref')
|
|
5
|
+
const path = require('node:path/posix')
|
|
6
|
+
const sanitize = require('./util/sanitize')
|
|
7
|
+
const unconvertInlineAsciiDoc = require('./util/unconvert-inline-asciidoc')
|
|
8
|
+
|
|
9
|
+
const AttributeEntryRx = /^:([^:-][^:]*):(?: .*)?$/
|
|
10
|
+
const BuiltInNamedEntities = { amp: '&', apos: "'", gt: '>', lt: '<', nbsp: ' ', quot: '"' }
|
|
11
|
+
const CharRefRx = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d{2,6})|x([a-z\d]{2,5})));/g
|
|
12
|
+
const DiscardAttributes = 'doctype leveloffset assembly-navtitle assembly-style underscore'.split(' ')
|
|
13
|
+
const ReservedIdNames = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
|
|
14
|
+
|
|
15
|
+
function produceAssemblyFile (
|
|
16
|
+
loadAsciiDoc,
|
|
17
|
+
contentCatalog,
|
|
18
|
+
componentVersion,
|
|
19
|
+
outline,
|
|
20
|
+
files,
|
|
21
|
+
asciidocConfig,
|
|
22
|
+
mutableAttributes,
|
|
23
|
+
assemblyModel
|
|
24
|
+
) {
|
|
25
|
+
const pagesByUrl = files.reduce((map, it) => (it.src.family === 'page' ? map.set(it.pub.url, it) : map), new Map())
|
|
26
|
+
if (outline.urlType === 'internal' && !pagesByUrl.get(outline.url) && !(outline.items || []).length) return
|
|
27
|
+
const pagesInOutline = selectPagesInOutline(outline, pagesByUrl, componentVersion)
|
|
28
|
+
const buffer = mergeAsciiDoc(
|
|
29
|
+
loadAsciiDoc,
|
|
30
|
+
contentCatalog,
|
|
31
|
+
buildAsciiDocHeader(componentVersion, outline.content, assemblyModel),
|
|
32
|
+
componentVersion,
|
|
33
|
+
outline,
|
|
34
|
+
files,
|
|
35
|
+
pagesInOutline,
|
|
36
|
+
asciidocConfig,
|
|
37
|
+
mutableAttributes,
|
|
38
|
+
assemblyModel
|
|
39
|
+
)
|
|
40
|
+
const rootLevel = assemblyModel.rootLevel
|
|
41
|
+
const stem = rootLevel === 0 ? 'index' : generateSlug(buffer.navtitle)
|
|
42
|
+
const downloadStem = [componentVersion.name, componentVersion.version, rootLevel === 0 ? '' : stem]
|
|
43
|
+
.filter((it) => it)
|
|
44
|
+
.join('-')
|
|
45
|
+
return createAsciiDocFile(contentCatalog, {
|
|
46
|
+
asciidoc: asciidocConfig,
|
|
47
|
+
assembler: { assembled: pagesInOutline.assembled, downloadStem, rootLevel },
|
|
48
|
+
contents: Buffer.from(buffer.join('\n') + '\n'),
|
|
49
|
+
src: {
|
|
50
|
+
component: componentVersion.name,
|
|
51
|
+
version: componentVersion.version,
|
|
52
|
+
module: 'ROOT',
|
|
53
|
+
family: 'export',
|
|
54
|
+
relative: stem + '.adoc',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildAsciiDocHeader (componentVersion, navtitle, assemblyModel) {
|
|
60
|
+
const doctype = assemblyModel.doctype ?? 'book'
|
|
61
|
+
const navtitlePlain = sanitize(navtitle)
|
|
62
|
+
const navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
|
|
63
|
+
let doctitle = navtitleAsciiDoc
|
|
64
|
+
if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
|
|
65
|
+
const version = componentVersion.version === 'master' ? '' : componentVersion.version
|
|
66
|
+
const buffer = [
|
|
67
|
+
`= ${doctitle}`,
|
|
68
|
+
...(version ? [`:revnumber: ${version}`] : []),
|
|
69
|
+
...(doctype === 'article' ? [] : [`:doctype: ${doctype ?? 'book'}`]),
|
|
70
|
+
':underscore: _',
|
|
71
|
+
// Q: should we pass these via the CLI so they cannot be modified?
|
|
72
|
+
`:page-component-name: ${componentVersion.name}`,
|
|
73
|
+
`:page-component-version:${version ? ' ' + version : ''}`,
|
|
74
|
+
':page-version: {page-component-version}',
|
|
75
|
+
`:page-component-display-version: ${componentVersion.displayVersion}`,
|
|
76
|
+
`:page-component-title: ${componentVersion.title}`,
|
|
77
|
+
]
|
|
78
|
+
return Object.assign(buffer, { navtitle })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function selectPagesInOutline (outlineEntry, pagesByUrl, componentVersion, accum) {
|
|
82
|
+
accum ??= Object.assign(new Map(), { assembled: { pages: new Map(), assets: new Set() } })
|
|
83
|
+
const page = outlineEntry.urlType === 'internal' ? pagesByUrl.get(outlineEntry.url) : undefined
|
|
84
|
+
if (page) {
|
|
85
|
+
accum.set(createResourceKey(page.src), page)
|
|
86
|
+
accum.set(outlineEntry.url, page)
|
|
87
|
+
}
|
|
88
|
+
for (const item of outlineEntry.items || []) selectPagesInOutline(item, pagesByUrl, componentVersion, accum)
|
|
89
|
+
return accum
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function mergeAsciiDoc (
|
|
93
|
+
loadAsciiDoc,
|
|
94
|
+
contentCatalog,
|
|
95
|
+
buffer,
|
|
96
|
+
componentVersion,
|
|
97
|
+
outlineEntry,
|
|
98
|
+
files,
|
|
99
|
+
pagesInOutline,
|
|
100
|
+
asciidocConfig,
|
|
101
|
+
mutableAttributes,
|
|
102
|
+
assemblyModel,
|
|
103
|
+
lastComponentVersion = componentVersion,
|
|
104
|
+
level = 0,
|
|
105
|
+
supportsParts = false
|
|
106
|
+
) {
|
|
107
|
+
// TODO: we could try to be smart about it and make sure the page with fragment is included at least once
|
|
108
|
+
if (outlineEntry.hash) {
|
|
109
|
+
buffer.inBody ??= false
|
|
110
|
+
return buffer
|
|
111
|
+
}
|
|
112
|
+
let navtitle = outlineEntry.content
|
|
113
|
+
let navtitlePlain = sanitize(navtitle)
|
|
114
|
+
let navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
|
|
115
|
+
const { items = [], unresolved, urlType, url } = outlineEntry
|
|
116
|
+
const {
|
|
117
|
+
doctype,
|
|
118
|
+
filetype,
|
|
119
|
+
embedReferenceStyle: embedRefStyle,
|
|
120
|
+
linkReferenceStyle: linkRefStyle,
|
|
121
|
+
outDirname,
|
|
122
|
+
siteRoot,
|
|
123
|
+
xmlIds,
|
|
124
|
+
} = assemblyModel
|
|
125
|
+
// FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
|
|
126
|
+
const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
|
|
127
|
+
const atDocumentRoot = !buffer.inBody
|
|
128
|
+
const atBookRoot = atDocumentRoot && !level && doctype === 'book' && (supportsParts = true)
|
|
129
|
+
const hasItems = items.length > 0
|
|
130
|
+
const pubRoot = outDirname ? '/' + outDirname : ''
|
|
131
|
+
const idSeparator = xmlIds ? '-' : ':'
|
|
132
|
+
const idScopeSeparator = idSeparator.repeat(3)
|
|
133
|
+
const idCoordinateSeparator = idSeparator === '-' ? '----' : idSeparator
|
|
134
|
+
if (page && !pagesInOutline.assembled.pages.has(page)) {
|
|
135
|
+
const contents = page.src.contents
|
|
136
|
+
if (contents == null) {
|
|
137
|
+
buffer.inBody ??= false
|
|
138
|
+
return buffer
|
|
139
|
+
}
|
|
140
|
+
const { component, version, module: module_, relative, origin, mediaType } = page.src
|
|
141
|
+
const pageAsAsciiDoc = new page.constructor(
|
|
142
|
+
Object.assign({}, page, { contents: trimAsciiDoc(contents), mediaType })
|
|
143
|
+
)
|
|
144
|
+
const doc = loadAsciiDoc(pageAsAsciiDoc, contentCatalog, asciidocConfig)
|
|
145
|
+
if (doc.hasAttribute('assembly-navtitle')) {
|
|
146
|
+
navtitleAsciiDoc = doc.getAttribute('assembly-navtitle')
|
|
147
|
+
navtitlePlain = sanitize((navtitle = doc.$apply_reftext_subs(navtitleAsciiDoc)))
|
|
148
|
+
if (buffer.inBody == null) {
|
|
149
|
+
buffer.navtitle = navtitle
|
|
150
|
+
// Q do we need to assert !level here?
|
|
151
|
+
if (assemblyModel.rootLevel) {
|
|
152
|
+
let doctitle = navtitleAsciiDoc
|
|
153
|
+
if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
|
|
154
|
+
buffer[0] = `= ${doctitle}`
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (atDocumentRoot) {
|
|
159
|
+
const authors = doc.getAuthors()
|
|
160
|
+
if (authors.length) {
|
|
161
|
+
const authorLine = authors
|
|
162
|
+
.map((author) => {
|
|
163
|
+
const email = author.getEmail()
|
|
164
|
+
return email ? `${author.getName()} <${author.getEmail()}>` : author.getName()
|
|
165
|
+
})
|
|
166
|
+
.join('; ')
|
|
167
|
+
buffer.splice(1, 0, authorLine)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// NOTE: in Antora, docname is relative src path from module without file extension
|
|
171
|
+
const docname = doc.getAttribute('docname')
|
|
172
|
+
const { idPrefix, id: idScope } = generateId(page.src, componentVersion, idCoordinateSeparator, idScopeSeparator)
|
|
173
|
+
let pageFragment = ''
|
|
174
|
+
let pageRoles = ''
|
|
175
|
+
let pageStyle = doc.getAttribute('assembly-style', '')
|
|
176
|
+
let part
|
|
177
|
+
if ((part = pageStyle === 'part')) {
|
|
178
|
+
pageStyle = ''
|
|
179
|
+
if (!supportsParts) part = undefined
|
|
180
|
+
} else if ((part = pageStyle.endsWith('-part'))) {
|
|
181
|
+
pageStyle = pageStyle.slice(0, -5)
|
|
182
|
+
if (!supportsParts) part = undefined
|
|
183
|
+
}
|
|
184
|
+
let nextSectionLevel = 1
|
|
185
|
+
const lines = doc.getSourceLines()
|
|
186
|
+
const ignoreLines = []
|
|
187
|
+
buffer.inBody = true
|
|
188
|
+
buffer.push('')
|
|
189
|
+
buffer.push(`:docname: ${docname}`)
|
|
190
|
+
if (component !== lastComponentVersion.name) {
|
|
191
|
+
const thisComponentVersion =
|
|
192
|
+
component === componentVersion.name && version === componentVersion.version
|
|
193
|
+
? componentVersion
|
|
194
|
+
: contentCatalog.getComponentVersion(component, version)
|
|
195
|
+
if (thisComponentVersion) {
|
|
196
|
+
buffer.push(`:page-component-name: ${thisComponentVersion.name}`)
|
|
197
|
+
buffer.push(`:page-component-version:${thisComponentVersion.version ? ' ' + thisComponentVersion.version : ''}`)
|
|
198
|
+
buffer.push(':page-version: {page-component-version}')
|
|
199
|
+
buffer.push(`:page-component-display-version: ${thisComponentVersion.displayVersion}`)
|
|
200
|
+
buffer.push(`:page-component-title: ${thisComponentVersion.title}`)
|
|
201
|
+
lastComponentVersion = thisComponentVersion
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
buffer.push(`:page-module: ${module_}`)
|
|
205
|
+
buffer.push(`:page-relative-src-path: ${relative}`)
|
|
206
|
+
buffer.push(`:page-origin-url: ${origin.url}`)
|
|
207
|
+
buffer.push(`:page-origin-start-path:${origin.startPath && ' '}${origin.startPath}`)
|
|
208
|
+
buffer.push(`:page-origin-refname: ${origin.branch || origin.tag}`)
|
|
209
|
+
buffer.push(`:page-origin-reftype: ${origin.branch ? 'branch' : 'tag'}`)
|
|
210
|
+
buffer.push(`:page-origin-refhash: ${origin.worktree ? '(worktree)' : origin.refhash}`)
|
|
211
|
+
if (doc.hasHeader()) pageRoles = processDocumentHeader(doc, lines, buffer, ignoreLines)
|
|
212
|
+
let heading
|
|
213
|
+
if (pageStyle && (part && level === 1 ? (level = 0) : level) === 0) {
|
|
214
|
+
let htitleAsciiDoc = navtitleAsciiDoc
|
|
215
|
+
let htitlePlain = navtitlePlain
|
|
216
|
+
let htitleOverride
|
|
217
|
+
if (
|
|
218
|
+
(htitleOverride = pageStyle === 'preface' ? doc.getAttribute('preface-title') : undefined) ||
|
|
219
|
+
(atDocumentRoot && (htitleOverride = outlineEntry.navtitle))
|
|
220
|
+
) {
|
|
221
|
+
htitleAsciiDoc = unconvertInlineAsciiDoc(htitleOverride)
|
|
222
|
+
htitlePlain = sanitize(htitleOverride)
|
|
223
|
+
}
|
|
224
|
+
if (atDocumentRoot && pageStyle === 'preface' && htitlePlain === componentVersion.title) {
|
|
225
|
+
assemblyModel = Object.assign({}, assemblyModel, { sectionMergeStrategy: 'discrete' })
|
|
226
|
+
} else {
|
|
227
|
+
if (atDocumentRoot) {
|
|
228
|
+
pageFragment = `#${idPrefix}${doc.getId() ?? pageStyle}`
|
|
229
|
+
buffer.unshift(`[#${idScope}]`)
|
|
230
|
+
} else {
|
|
231
|
+
pageFragment = `#${idScope}`
|
|
232
|
+
}
|
|
233
|
+
heading = { title: htitleAsciiDoc, level: part ? 1 : 2 }
|
|
234
|
+
nextSectionLevel++
|
|
235
|
+
}
|
|
236
|
+
} else if (level) {
|
|
237
|
+
if (atDocumentRoot && navtitlePlain === componentVersion.title) {
|
|
238
|
+
level--
|
|
239
|
+
} else {
|
|
240
|
+
pageFragment = `#${idScope}`
|
|
241
|
+
if (part && level === 1) level--
|
|
242
|
+
if ((heading = { title: navtitleAsciiDoc, level: level + 1 }).level > 6) {
|
|
243
|
+
Object.assign(heading, { level: 6, style: `discrete.h${heading.level}` })
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (heading) {
|
|
248
|
+
buffer.push(`[${heading.style ?? pageStyle}${pageFragment}${pageRoles}]`)
|
|
249
|
+
buffer.push(`${'='.repeat(heading.level)} ${heading.title}`)
|
|
250
|
+
} else if (atDocumentRoot) {
|
|
251
|
+
buffer.unshift(`[#${idScope}]`)
|
|
252
|
+
}
|
|
253
|
+
let enclosed
|
|
254
|
+
if (assemblyModel.sectionMergeStrategy === 'enclose' && hasItems && doc.hasSections()) {
|
|
255
|
+
enclosed = true
|
|
256
|
+
// TODO: make overview section title configurable
|
|
257
|
+
//let overviewTitle = doc.getDocumentTitle()
|
|
258
|
+
//if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
259
|
+
const overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
260
|
+
buffer.push('')
|
|
261
|
+
// NOTE: try to toggle sectids; otherwise, fallback to globally unique synthetic ID
|
|
262
|
+
let toggleSectids, syntheticId
|
|
263
|
+
if (doc.isAttribute('sectids')) {
|
|
264
|
+
if (doc.isAttributeLocked('sectids')) {
|
|
265
|
+
syntheticId = `__object-id-${getObjectId(outlineEntry)}`
|
|
266
|
+
} else {
|
|
267
|
+
buffer.push(':!sectids:')
|
|
268
|
+
toggleSectids = true
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
let hlevel = level + 2
|
|
272
|
+
if (hlevel > 6) {
|
|
273
|
+
const blockStyle = `discrete.h${hlevel}`
|
|
274
|
+
hlevel = 6
|
|
275
|
+
buffer.push(syntheticId ? `[${blockStyle}#${syntheticId}]` : `[${blockStyle}]`)
|
|
276
|
+
} else if (syntheticId) {
|
|
277
|
+
buffer.push(`[#${syntheticId}]`)
|
|
278
|
+
}
|
|
279
|
+
buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
|
|
280
|
+
if (toggleSectids) buffer.push(':sectids:')
|
|
281
|
+
}
|
|
282
|
+
pagesInOutline.assembled.pages.set(page, pageFragment)
|
|
283
|
+
if (doc.hasSections()) {
|
|
284
|
+
fixSectionLevels(doc.getSections(), atBookRoot && !pageStyle ? undefined : nextSectionLevel)
|
|
285
|
+
}
|
|
286
|
+
const allBlocks = doc.findBy({ traverse_documents: true }, (it) =>
|
|
287
|
+
it.getContext() === 'document'
|
|
288
|
+
? it.getDocument().isNested()
|
|
289
|
+
: !(it.getContext() === 'table_cell' && it.getStyle() === 'asciidoc')
|
|
290
|
+
)
|
|
291
|
+
if (doc.getDoctype() === 'manpage') {
|
|
292
|
+
const firstSectionIdx = doc.hasSections() ? doc.getSections()[0].getLineNumber() - 1 : lines.length
|
|
293
|
+
for (let idx = 0; idx < firstSectionIdx; idx++) {
|
|
294
|
+
if (~ignoreLines.indexOf(idx)) continue
|
|
295
|
+
const line = lines[idx]
|
|
296
|
+
if (line.startsWith('== ') && line.length > 3) {
|
|
297
|
+
allBlocks.unshift({
|
|
298
|
+
getContext: () => 'section',
|
|
299
|
+
getDocument: () => doc,
|
|
300
|
+
getId: () => doc.getAttribute('manname-id'),
|
|
301
|
+
getLineNumber: () => idx + 1,
|
|
302
|
+
getSectionName: () => undefined,
|
|
303
|
+
level: 1,
|
|
304
|
+
})
|
|
305
|
+
break
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const refs = doc.getCatalog().refs
|
|
310
|
+
allBlocks.forEach((block) => {
|
|
311
|
+
const contentModel = block.content_model
|
|
312
|
+
if (
|
|
313
|
+
((contentModel === 'verbatim' && block.getContext() !== 'table_cell') ||
|
|
314
|
+
contentModel === 'simple' ||
|
|
315
|
+
contentModel === 'pass') &&
|
|
316
|
+
!block.hasSubstitution('macros')
|
|
317
|
+
) {
|
|
318
|
+
const lineno = block.getLineNumber()
|
|
319
|
+
const idx = typeof lineno === 'number' ? lineno - 1 : undefined
|
|
320
|
+
const startLine = lines[idx]
|
|
321
|
+
// NOTE: one case this happens if when sourcemap isn't enabled when reducing
|
|
322
|
+
if (startLine == null) {
|
|
323
|
+
console.log(`null startLine for ${block.getContext()} at ${lineno} in ${relative}`)
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
const char0 = startLine.charAt()
|
|
327
|
+
// FIXME: needs to be more robust; move logic to helper
|
|
328
|
+
const delimited =
|
|
329
|
+
startLine.length > 3 &&
|
|
330
|
+
startLine === char0.repeat(startLine.length) &&
|
|
331
|
+
(char0 === '-' || char0 === '.' || char0 === '+')
|
|
332
|
+
// QUESTION: exclude block attribute lines too? what about attribute entries?
|
|
333
|
+
for (let i = idx; i < block.lines.length + (delimited ? idx + 2 : idx); i++) ignoreLines.push(i)
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
let skipping
|
|
337
|
+
for (let idx = 0, lastIdx = lines.length - 1; idx <= lastIdx; idx++) {
|
|
338
|
+
if (~ignoreLines.indexOf(idx)) continue
|
|
339
|
+
let line = lines[idx]
|
|
340
|
+
if (line.startsWith('//')) {
|
|
341
|
+
if (line[2] !== '/') continue
|
|
342
|
+
if (line.length > 3 && line === '/'.repeat(line.length)) {
|
|
343
|
+
if (skipping) {
|
|
344
|
+
if (line === skipping) skipping = undefined
|
|
345
|
+
} else {
|
|
346
|
+
skipping = line
|
|
347
|
+
}
|
|
348
|
+
continue
|
|
349
|
+
}
|
|
350
|
+
} else if (skipping) {
|
|
351
|
+
continue
|
|
352
|
+
}
|
|
353
|
+
if (
|
|
354
|
+
line.charAt() === ':' &&
|
|
355
|
+
~line.indexOf(':', 2) &&
|
|
356
|
+
(line.match(AttributeEntryRx) || ['', ''])[1].replace('!', '') === 'leveloffset'
|
|
357
|
+
) {
|
|
358
|
+
if (lines[idx - 1] === '') lines[idx - 1] = undefined
|
|
359
|
+
lines[idx] = undefined
|
|
360
|
+
continue
|
|
361
|
+
}
|
|
362
|
+
if (~line.indexOf('<<')) {
|
|
363
|
+
line = line.replace(/(?<![\\+])<<#?([\p{Alpha}0-9_/.:{][^>,]*?)(?:|, *([^>]+?))?>>/gu, (m, refid, text) => {
|
|
364
|
+
// support natural xref
|
|
365
|
+
if (!refs['$key?'](refid) && (~refid.indexOf(' ') || refid.toLowerCase() !== refid)) {
|
|
366
|
+
if ((refid = doc.$resolve_id(refid))['$nil?']()) return m
|
|
367
|
+
}
|
|
368
|
+
return `<<${idPrefix}${refid}${text ? ',' + text : ''}>>`
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
// NOTE: the next check takes care of inline and block anchors
|
|
372
|
+
if (~line.indexOf('[[')) {
|
|
373
|
+
line = line.replace(/\[\[([\p{Alpha}_:][\p{Alpha}0-9_\-:.]*)(|, *.+?)\]\]/gu, `[[${idPrefix}$1$2]]`)
|
|
374
|
+
}
|
|
375
|
+
if (~line.indexOf('xref:')) {
|
|
376
|
+
// Q: should we allow : as first character of target?
|
|
377
|
+
line = line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|.*?[^\\])\]/gu, (m, target, text) => {
|
|
378
|
+
let fragment, resource, resourceRef
|
|
379
|
+
const hashIdx = target.indexOf('#')
|
|
380
|
+
if (~hashIdx) {
|
|
381
|
+
resourceRef = target.slice(0, hashIdx)
|
|
382
|
+
fragment = target.slice(hashIdx + 1)
|
|
383
|
+
} else if (target.endsWith('.adoc') || ~target.indexOf('$')) {
|
|
384
|
+
resourceRef = target
|
|
385
|
+
fragment = ''
|
|
386
|
+
} else {
|
|
387
|
+
fragment = target
|
|
388
|
+
}
|
|
389
|
+
// Q: should we validate the internal ID here?
|
|
390
|
+
if (!resourceRef) return `xref:${idPrefix}${fragment}[${text}]`
|
|
391
|
+
const resourceId = parseResourceRef(resourceRef, page.src, 'page', contentCatalog)
|
|
392
|
+
if (resourceId.family !== 'page') {
|
|
393
|
+
if (siteRoot && (resource = contentCatalog.getById(resourceId))?.pub) {
|
|
394
|
+
text ||= resource.asciidoc?.xreftext || target
|
|
395
|
+
return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}${fragment && '#' + fragment}[${text}]`
|
|
396
|
+
}
|
|
397
|
+
// TODO: handle unresolved resource better
|
|
398
|
+
return m
|
|
399
|
+
}
|
|
400
|
+
if (!(resource = pagesInOutline.get(createResourceKey(resourceId)))) {
|
|
401
|
+
if (siteRoot && (resource = contentCatalog.getById(resourceId))?.pub) {
|
|
402
|
+
text ||= resource.asciidoc?.xreftext || target
|
|
403
|
+
return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}${fragment && '#' + fragment}[${text}]`
|
|
404
|
+
}
|
|
405
|
+
// TODO: handle unresolved page better
|
|
406
|
+
return m
|
|
407
|
+
}
|
|
408
|
+
if (fragment === resource.asciidoc.id) fragment = ''
|
|
409
|
+
const refid = generateId(resource.src, componentVersion, idCoordinateSeparator, idScopeSeparator, fragment).id
|
|
410
|
+
if (
|
|
411
|
+
text &&
|
|
412
|
+
(assemblyModel.dropExplicitXrefText === 'always' ||
|
|
413
|
+
(assemblyModel.dropExplicitXrefText === 'if-redundant' && text === resource.title))
|
|
414
|
+
) {
|
|
415
|
+
text = ''
|
|
416
|
+
}
|
|
417
|
+
return `xref:${refid}[${text}]`
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
if (~line.indexOf('link:{attachmentsdir}/')) {
|
|
421
|
+
line = line.replace(/(?<![\\+])link:\{attachmentsdir\}\/([^\s[]+)\[(|.*?[^\\])\]/g, (m, relative, text) => {
|
|
422
|
+
const attachment =
|
|
423
|
+
siteRoot &&
|
|
424
|
+
contentCatalog.getById({
|
|
425
|
+
component: componentVersion.name,
|
|
426
|
+
version: componentVersion.version,
|
|
427
|
+
module: module_,
|
|
428
|
+
family: 'attachment',
|
|
429
|
+
relative,
|
|
430
|
+
})
|
|
431
|
+
// TODO: handle unresolved attachment page
|
|
432
|
+
return attachment?.out ? `${resolveLinkTarget(attachment, siteRoot, pubRoot, linkRefStyle)}[${text}]` : m
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
if (~line.indexOf('image:') && !line.startsWith('image::')) {
|
|
436
|
+
line = line.replace(/(?<![\\+])image:([^:\s[](?:[^[]*[^\s[])?)\[([^\]]*)\]/g, (m, target, attrlist) => {
|
|
437
|
+
if (isResourceSpec(target)) {
|
|
438
|
+
const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
|
|
439
|
+
// TODO: handle (or report) unresolved image better
|
|
440
|
+
if (image?.out && (filetype !== 'html' || siteRoot)) {
|
|
441
|
+
pagesInOutline.assembled.assets.add(image)
|
|
442
|
+
return filetype === 'html'
|
|
443
|
+
? `image:${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle)}[${attrlist}]`
|
|
444
|
+
: `image:${resolveEmbedTarget(image, outDirname, embedRefStyle, true)}[${attrlist}]`
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return m
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
lines[idx] = line
|
|
451
|
+
}
|
|
452
|
+
// NOTE: need to do this last since it modifies the line numbers
|
|
453
|
+
// we could remap the line numbers to make them resilient
|
|
454
|
+
// or we could mark which lines to remove and filter them after
|
|
455
|
+
let lastImageMacroAt
|
|
456
|
+
;[...allBlocks].reverse().forEach((block) => {
|
|
457
|
+
const lineno = block.getLineNumber()
|
|
458
|
+
// NOTE: lineno is not defined for preamble
|
|
459
|
+
if (typeof lineno !== 'number') return
|
|
460
|
+
const context = block.getContext()
|
|
461
|
+
let idx = lineno - 1
|
|
462
|
+
if (context === 'section' && !block.getDocument().isNested()) {
|
|
463
|
+
if (block.getSectionName() === 'header') return
|
|
464
|
+
let blockStyle = (assemblyModel.sectionMergeStrategy || 'discrete') === 'discrete' ? 'discrete' : undefined
|
|
465
|
+
lines[idx] = lines[idx].replace(/^=+ (.+)/, (_, rest) => {
|
|
466
|
+
let targetMarkerLength = block.level + 1 + level + (enclosed ? 1 : 0)
|
|
467
|
+
if (targetMarkerLength > 6) {
|
|
468
|
+
blockStyle = `discrete.h${targetMarkerLength}`
|
|
469
|
+
targetMarkerLength = 6
|
|
470
|
+
}
|
|
471
|
+
return '='.repeat(targetMarkerLength) + ' ' + rest
|
|
472
|
+
})
|
|
473
|
+
// NOTE: ID will be undefined if sectids are turned off
|
|
474
|
+
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idPrefix, blockStyle)
|
|
475
|
+
} else {
|
|
476
|
+
if (context === 'image') {
|
|
477
|
+
let line = lines[idx] || ''
|
|
478
|
+
let prefix = ''
|
|
479
|
+
// Q: can we use startsWith('image::') in certain cases?
|
|
480
|
+
let imageMacroOffset = (
|
|
481
|
+
lastImageMacroAt?.[0] === idx ? line.slice(0, lastImageMacroAt[1]) : line
|
|
482
|
+
).lastIndexOf('image::')
|
|
483
|
+
if (imageMacroOffset > 0) {
|
|
484
|
+
if (
|
|
485
|
+
block.getDocument().isNested() &&
|
|
486
|
+
(prefix = line.slice(0, imageMacroOffset)).trimRight().endsWith('|')
|
|
487
|
+
) {
|
|
488
|
+
line = line.slice(prefix.length)
|
|
489
|
+
} else {
|
|
490
|
+
imageMacroOffset = -1
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (imageMacroOffset >= 0) {
|
|
494
|
+
const target = block.getAttribute('target')
|
|
495
|
+
if (isResourceSpec(target)) {
|
|
496
|
+
const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
|
|
497
|
+
// FIXME: handle (or report) case when image is not resolved
|
|
498
|
+
if (image?.out && (filetype !== 'html' || siteRoot)) {
|
|
499
|
+
const attrlist = line.slice(line.indexOf('[') + 1, -1)
|
|
500
|
+
pagesInOutline.assembled.assets.add(image)
|
|
501
|
+
lines[idx] =
|
|
502
|
+
filetype === 'html'
|
|
503
|
+
? `${prefix}image::${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle, false)}[${attrlist}]`
|
|
504
|
+
: `${prefix}image::${resolveEmbedTarget(image, outDirname, embedRefStyle)}[${attrlist}]`
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
lastImageMacroAt = [idx, imageMacroOffset]
|
|
508
|
+
}
|
|
509
|
+
} else if (context === 'document' && block.hasHeader()) {
|
|
510
|
+
// nested document
|
|
511
|
+
idx = (block.getHeader().getLineNumber() || idx + 1) - 1
|
|
512
|
+
}
|
|
513
|
+
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idPrefix)
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
safePush(
|
|
517
|
+
buffer,
|
|
518
|
+
lines.filter((it) => it !== undefined)
|
|
519
|
+
)
|
|
520
|
+
const attributeEntries = Object.entries(doc.source_header_attributes?.$$smap || {})
|
|
521
|
+
if (attributeEntries.length) {
|
|
522
|
+
const resolvedAttributeEntries = attributeEntries.reduce(
|
|
523
|
+
(accum, [name, val]) => {
|
|
524
|
+
// Q: couldn't we just check if attribute is locked?
|
|
525
|
+
if (name in mutableAttributes) {
|
|
526
|
+
const initialVal = mutableAttributes[name]
|
|
527
|
+
if (initialVal == null) {
|
|
528
|
+
if (val != null) accum.push(`:!${name}:`)
|
|
529
|
+
} else if (val !== initialVal) {
|
|
530
|
+
accum.push(`:${name}:${initialVal ? ' ' + initialVal : ''}`)
|
|
531
|
+
}
|
|
532
|
+
} else if (!(val == null || doc.isAttributeLocked(name) || DiscardAttributes.includes(name))) {
|
|
533
|
+
accum.push(`:!${name}:`)
|
|
534
|
+
}
|
|
535
|
+
return accum
|
|
536
|
+
},
|
|
537
|
+
['']
|
|
538
|
+
)
|
|
539
|
+
if (resolvedAttributeEntries.length > 1) safePush(buffer, resolvedAttributeEntries)
|
|
540
|
+
}
|
|
541
|
+
} else if (level) {
|
|
542
|
+
if (atDocumentRoot && navtitlePlain === componentVersion.title) {
|
|
543
|
+
buffer.inBody ??= false
|
|
544
|
+
level--
|
|
545
|
+
} else {
|
|
546
|
+
buffer.inBody = true
|
|
547
|
+
buffer.push('')
|
|
548
|
+
// NOTE: try to toggle sectids; otherwise, fallback to globally unique synthetic ID
|
|
549
|
+
// Q: should we unset docname, page-module, etc?
|
|
550
|
+
let toggleSectids, syntheticId
|
|
551
|
+
if (!('sectids' in asciidocConfig.attributes)) {
|
|
552
|
+
buffer.push(':!sectids:')
|
|
553
|
+
toggleSectids = true
|
|
554
|
+
} else if (typeof asciidocConfig.attributes.sectids === 'string') {
|
|
555
|
+
if ('sectids' in mutableAttributes) {
|
|
556
|
+
buffer.push(':!sectids:')
|
|
557
|
+
toggleSectids = true
|
|
558
|
+
} else {
|
|
559
|
+
syntheticId = `__object-id-${global.Opal.hash(outlineEntry).$object_id()}`
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
let sectionTitle = navtitleAsciiDoc
|
|
563
|
+
if (urlType === 'external') {
|
|
564
|
+
sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
565
|
+
} else if (urlType === 'internal' && !unresolved) {
|
|
566
|
+
const resource = files.find((it) => it.pub.url === url)
|
|
567
|
+
if (resource) {
|
|
568
|
+
if (resource.src.family === 'page' && pagesInOutline.has(resource.pub.url)) {
|
|
569
|
+
const refid = generateId(resource.src, componentVersion, idCoordinateSeparator, idScopeSeparator).id
|
|
570
|
+
sectionTitle = `xref:${refid}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
571
|
+
} else if (siteRoot) {
|
|
572
|
+
sectionTitle = `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
let hlevel = level + 1
|
|
577
|
+
if (hlevel > 6) {
|
|
578
|
+
hlevel = 6
|
|
579
|
+
buffer.push(syntheticId ? `[discrete#${syntheticId}]` : '[discrete]')
|
|
580
|
+
} else if (syntheticId) {
|
|
581
|
+
buffer.push(`[#${syntheticId}]`)
|
|
582
|
+
}
|
|
583
|
+
buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
|
|
584
|
+
if (toggleSectids) buffer.push(':sectids:')
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (hasItems) {
|
|
589
|
+
const nextLevel = level + 1
|
|
590
|
+
// NOTE: drop first child if same as parent; should we keep if content is different?
|
|
591
|
+
;(urlType === 'internal' && urlType === items[0].urlType && url === items[0].url && !items[0].items
|
|
592
|
+
? items.slice(1)
|
|
593
|
+
: items
|
|
594
|
+
).forEach((item) => {
|
|
595
|
+
mergeAsciiDoc(
|
|
596
|
+
loadAsciiDoc,
|
|
597
|
+
contentCatalog,
|
|
598
|
+
buffer,
|
|
599
|
+
componentVersion,
|
|
600
|
+
item,
|
|
601
|
+
files,
|
|
602
|
+
pagesInOutline,
|
|
603
|
+
asciidocConfig,
|
|
604
|
+
mutableAttributes,
|
|
605
|
+
assemblyModel,
|
|
606
|
+
lastComponentVersion,
|
|
607
|
+
nextLevel,
|
|
608
|
+
atBookRoot
|
|
609
|
+
)
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
return buffer
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function processDocumentHeader (doc, lines, buffer, ignoreLines) {
|
|
616
|
+
const doctitleIdx = doc.getHeader().getLineNumber() - 1
|
|
617
|
+
const end = doc.getBlocks()[0]?.getLineNumber() ?? lines.length
|
|
618
|
+
let belowDoctitle
|
|
619
|
+
let open
|
|
620
|
+
const implicitLines = []
|
|
621
|
+
for (let idx = 0; idx < end; idx++) {
|
|
622
|
+
if (idx === doctitleIdx) {
|
|
623
|
+
lines[idx] = undefined
|
|
624
|
+
ignoreLines.push(idx)
|
|
625
|
+
belowDoctitle = true
|
|
626
|
+
continue
|
|
627
|
+
}
|
|
628
|
+
const line = lines[idx]
|
|
629
|
+
if (open === ':' || open === '-:') {
|
|
630
|
+
if (open === ':') buffer.push(line)
|
|
631
|
+
if (!line || !line.endsWith(' \\')) open = undefined
|
|
632
|
+
} else if (line) {
|
|
633
|
+
const chr0 = line.charAt()
|
|
634
|
+
let attributeEntryMatch
|
|
635
|
+
if (chr0 === '/' && line.charAt(1) === '/') {
|
|
636
|
+
if (line.startsWith('////')) {
|
|
637
|
+
open = open ? (open === line ? undefined : open) : line
|
|
638
|
+
} else if (belowDoctitle && !open && line.charAt(2) === '/') {
|
|
639
|
+
break
|
|
640
|
+
}
|
|
641
|
+
buffer.push(line)
|
|
642
|
+
} else if (open) {
|
|
643
|
+
buffer.push(line)
|
|
644
|
+
} else if (chr0 === ':' && ~line.indexOf(':', 2) && (attributeEntryMatch = line.match(AttributeEntryRx))) {
|
|
645
|
+
const attributeName = attributeEntryMatch[1].replace('!', '')
|
|
646
|
+
if (DiscardAttributes.includes(attributeName)) {
|
|
647
|
+
if (line.endsWith(' \\')) open = '-:' // disallow value continuation
|
|
648
|
+
} else {
|
|
649
|
+
if (line.endsWith(' \\')) open = ':'
|
|
650
|
+
buffer.push(line)
|
|
651
|
+
}
|
|
652
|
+
} else if (belowDoctitle) {
|
|
653
|
+
if (implicitLines.length === 2 || !/[\p{Alpha}0-9]/u.test(chr0)) break
|
|
654
|
+
implicitLines.push(line)
|
|
655
|
+
} else if (chr0 === '[' && line.charAt(line.length - 1) === ']') {
|
|
656
|
+
const attrlist = line
|
|
657
|
+
.slice(1, -1)
|
|
658
|
+
.trim()
|
|
659
|
+
.replace(/(?:^\w[\w-]*(?!=|[\w-]))?([#.%]\w[\w-]*)*/, '')
|
|
660
|
+
if (attrlist) buffer.push('[' + attrlist + ']')
|
|
661
|
+
}
|
|
662
|
+
} else if (belowDoctitle) {
|
|
663
|
+
break
|
|
664
|
+
}
|
|
665
|
+
lines[idx] = undefined
|
|
666
|
+
ignoreLines.push(idx)
|
|
667
|
+
}
|
|
668
|
+
return doc
|
|
669
|
+
.getRoles()
|
|
670
|
+
.map((role) => '.' + role)
|
|
671
|
+
.join('')
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function generateSlug (title) {
|
|
675
|
+
return title
|
|
676
|
+
.toLowerCase()
|
|
677
|
+
.replace(/<[^>]+>/g, '')
|
|
678
|
+
.replace(CharRefRx, (_, name, dec, hex) => {
|
|
679
|
+
if (name) return BuiltInNamedEntities[name] ?? '?'
|
|
680
|
+
return String.fromCharCode(dec ? parseInt(dec, 10) : parseInt(hex, 16))
|
|
681
|
+
})
|
|
682
|
+
.replace(/[\x27\u2019]/g, '')
|
|
683
|
+
.replace(/[^\p{Alpha}0-9-]/gu, '-')
|
|
684
|
+
.replace(/^-+|-+$|(-)-+/g, '$1')
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function fixSectionLevels (sections, expectedLevel) {
|
|
688
|
+
sections.forEach((sect) => {
|
|
689
|
+
const forceLevel = expectedLevel ?? Math.min(1, sect.getLevel())
|
|
690
|
+
if (sect.getLevel() !== forceLevel) sect.level = forceLevel
|
|
691
|
+
if (sect.hasSections()) fixSectionLevels(sect.getSections(), forceLevel + 1)
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function rewriteStyleAttribute (block, lines, idx, idPrefix, replacementStyle = '') {
|
|
696
|
+
let prevLine = lines[idx - 1]
|
|
697
|
+
const char0 = prevLine?.charAt()
|
|
698
|
+
if (char0) {
|
|
699
|
+
if (
|
|
700
|
+
(char0 === '.' && /^\.\.?[^ \t.]/.test(prevLine)) ||
|
|
701
|
+
(char0 === '[' &&
|
|
702
|
+
prevLine.charAt(1) === '[' &&
|
|
703
|
+
/^\[\[(?:|[\p{Alpha}_:][\p{Alpha}0-9_\-:.]*(?:, *.+)?)\]\]$/u.test(prevLine))
|
|
704
|
+
) {
|
|
705
|
+
return rewriteStyleAttribute(block, lines, idx - 1, idPrefix, replacementStyle)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
let cellSpec
|
|
709
|
+
if (
|
|
710
|
+
char0 &&
|
|
711
|
+
(char0 === '[' || (block.getDocument().isNested() && (cellSpec = prevLine.match(/^([^[|]*)\| *(\[.+)/)))) &&
|
|
712
|
+
prevLine.charAt(prevLine.length - 1) === ']'
|
|
713
|
+
) {
|
|
714
|
+
if (cellSpec) {
|
|
715
|
+
prevLine = cellSpec[2]
|
|
716
|
+
cellSpec = cellSpec[1]
|
|
717
|
+
}
|
|
718
|
+
let rawStyle
|
|
719
|
+
const commaIdx = prevLine.indexOf(',')
|
|
720
|
+
if (~commaIdx) {
|
|
721
|
+
rawStyle = prevLine.slice(1, commaIdx)
|
|
722
|
+
if (~rawStyle.indexOf('=')) rawStyle = undefined
|
|
723
|
+
} else if (!~prevLine.indexOf('=')) {
|
|
724
|
+
rawStyle = prevLine.slice(1, prevLine.length - 1)
|
|
725
|
+
}
|
|
726
|
+
if (rawStyle) {
|
|
727
|
+
if (~rawStyle.indexOf('#')) {
|
|
728
|
+
prevLine = prevLine.replace(/#[^.%,\]]+/, `#${idPrefix}${block.getId()}`)
|
|
729
|
+
if (replacementStyle) prevLine = prevLine.replace(/^[^#.%,\]]+/, `[${replacementStyle}`)
|
|
730
|
+
} else {
|
|
731
|
+
prevLine = `[${
|
|
732
|
+
replacementStyle ? rawStyle.replace(/^[^.%]*/, replacementStyle) : rawStyle
|
|
733
|
+
}#${idPrefix}${block.getId()}${prevLine.slice(rawStyle.length + 1)}`
|
|
734
|
+
}
|
|
735
|
+
} else {
|
|
736
|
+
prevLine = `[${replacementStyle}#${idPrefix}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.slice(1)}`
|
|
737
|
+
}
|
|
738
|
+
if (cellSpec) prevLine = `${cellSpec}|${prevLine}`
|
|
739
|
+
lines[idx - 1] = prevLine
|
|
740
|
+
} else {
|
|
741
|
+
lines.splice(idx, 0, `[${replacementStyle}#${idPrefix}${block.getId()}]`)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function isResourceSpec (str) {
|
|
746
|
+
return !(~str.indexOf(':') && (~str.indexOf('://') || (str.startsWith('data:') && ~str.indexOf(','))))
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function getObjectId (obj) {
|
|
750
|
+
return global.Opal.id(obj)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// NOTE: blank lines at top and bottom of document create mismatch when using line numbers to navigate source lines
|
|
754
|
+
// IMPORTANT: this must not leave behind lines the parser will drop!
|
|
755
|
+
// IDEA: another option is to capture initial lineno of reader and use as offset (but preseves those blank lines)
|
|
756
|
+
function trimAsciiDoc (buffer) {
|
|
757
|
+
return Buffer.from(
|
|
758
|
+
buffer
|
|
759
|
+
.toString()
|
|
760
|
+
.replace(/^(?:[ \t]*\r\n?|[ \t]*\n)+/, '')
|
|
761
|
+
.trimRight()
|
|
762
|
+
)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function safePush (onto, entries) {
|
|
766
|
+
try {
|
|
767
|
+
onto.push(...entries)
|
|
768
|
+
} catch (err) {
|
|
769
|
+
/* istanbul ignore if */
|
|
770
|
+
if (!(err instanceof RangeError)) throw err
|
|
771
|
+
for (const e of entries) onto.push(e)
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function resolveEmbedTarget (resource, outDirname, referenceStyle, escapeForInline) {
|
|
776
|
+
const target =
|
|
777
|
+
referenceStyle === 'output-relative' ? resource.out.path : path.relative(outDirname + '/', resource.out.path)
|
|
778
|
+
return escapeForInline ? target.replace(/_/g, '{underscore}') : target
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function resolveLinkTarget (resource, siteRoot, pubRoot, referenceStyle, escapeForInline = true) {
|
|
782
|
+
let target
|
|
783
|
+
if (resource.site?.url) {
|
|
784
|
+
target = ['', resource.pub.url]
|
|
785
|
+
} else {
|
|
786
|
+
switch (referenceStyle) {
|
|
787
|
+
case 'absolute':
|
|
788
|
+
target = ['', siteRoot.url + resource.pub.url]
|
|
789
|
+
break
|
|
790
|
+
case 'root-relative':
|
|
791
|
+
target = ['link:', siteRoot.path + resource.pub.url]
|
|
792
|
+
break
|
|
793
|
+
default:
|
|
794
|
+
target = ['link:', computeRelativeUrl(pubRoot + '/', resource.pub.url)]
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (escapeForInline) target[1] = target[1].replace(/_/g, '{underscore}')
|
|
798
|
+
return target.join('')
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function computeRelativeUrl (from, to) {
|
|
802
|
+
const rel = path.relative(from, to)
|
|
803
|
+
return to.charAt(to.length - 1) === '/' ? rel + '/' : rel
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function createResourceKey ({ component, version, module: mod, family, relative }) {
|
|
807
|
+
return `${version}@${component}:${mod === 'ROOT' ? '' : mod}:${family === 'page' ? '' : family + '$'}${relative}`
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function generateId ({ component, module: mod, relative }, componentVersion, coordinateSep, scopeSep, fragment) {
|
|
811
|
+
let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
|
|
812
|
+
if (component !== componentVersion.name) {
|
|
813
|
+
id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSep)
|
|
814
|
+
} else if (mod !== 'ROOT') {
|
|
815
|
+
id = mod + coordinateSep + id
|
|
816
|
+
} else if (ReservedIdNames.includes(id)) {
|
|
817
|
+
id += scopeSep
|
|
818
|
+
scopeSep = ''
|
|
819
|
+
}
|
|
820
|
+
const idPrefix = id + scopeSep
|
|
821
|
+
if (fragment) id = idPrefix + fragment
|
|
822
|
+
return { idPrefix, id }
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
module.exports = produceAssemblyFile
|