@antora/assembler 1.0.0-beta.16 → 1.0.0-beta.18
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/lib/assemble-content.js +14 -5
- package/lib/configure.js +11 -2
- package/lib/load-config.js +21 -11
- package/lib/produce-assembly-file.js +86 -290
- package/lib/produce-assembly-files.js +44 -35
- package/lib/util/create-resource-key.js +7 -0
- package/lib/util/generate-scoped-id.js +25 -0
- package/lib/util/resolver.js +36 -0
- package/lib/util/rewriter.js +205 -0
- package/lib/util/rx.js +5 -0
- package/package.json +3 -2
package/lib/assemble-content.js
CHANGED
|
@@ -57,12 +57,12 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
|
|
|
57
57
|
intrinsicAttributes[`assembler-filetype-${targetFiletype}`] = ''
|
|
58
58
|
intrinsicAttributes['assembler-filetype'] = targetFiletype
|
|
59
59
|
}
|
|
60
|
-
Object.assign(assemblerConfig.
|
|
60
|
+
Object.assign(assemblerConfig.assembly.attributes, intrinsicAttributes)
|
|
61
61
|
const assemblyFiles = produceAssemblyFiles(
|
|
62
62
|
loadAsciiDoc,
|
|
63
63
|
contentCatalog,
|
|
64
64
|
assemblerConfig,
|
|
65
|
-
|
|
65
|
+
generateSelectAssemblyProfile(context, contentCatalog, assemblyConfig, intrinsicAttributes, navigationCatalog)
|
|
66
66
|
)
|
|
67
67
|
if (!(assemblyFiles.length && typeof convert === 'function')) return assemblyFiles
|
|
68
68
|
if (buildConfig.command == null && typeof getDefaultCommand === 'function') {
|
|
@@ -123,24 +123,33 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
|
|
|
123
123
|
})
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Generates a function that selects the active assembly profile, initializes an assembly model using
|
|
128
|
+
* the keys from the profile as well as any inherited shared keys, builds the navigation for the assembly, and
|
|
129
|
+
* returns the initialized assembly model. The assembly model is further populated after the call to this function.
|
|
130
|
+
*/
|
|
131
|
+
function generateSelectAssemblyProfile (context, contentCatalog, base, intrinsicAttributes, navigationCatalog) {
|
|
127
132
|
const logger = context.getLogger?.(PACKAGE_NAME)
|
|
128
133
|
const { assemblerProfiles } = context.getVariables()
|
|
129
134
|
if (!assemblerProfiles) {
|
|
130
135
|
return (componentVersion) => {
|
|
131
136
|
const navigation =
|
|
132
137
|
navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version) ?? componentVersion.navigation
|
|
133
|
-
return Object.assign({
|
|
138
|
+
return Object.assign({}, base, { attributes: Object.assign({}, base.attributes), navigation, logger })
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
const boundSendToLog = sendToLog.bind(logger)
|
|
137
142
|
const { buildNavigation = require('@antora/navigation-builder') } = context.getFunctions()
|
|
138
143
|
return (componentVersion) => {
|
|
144
|
+
const attributes = Object.assign({}, base.attributes)
|
|
139
145
|
const componentVersionProfiles = assemblerProfiles.get(componentVersion.version + '@' + componentVersion.name)
|
|
140
146
|
const overrides =
|
|
141
147
|
componentVersionProfiles?.get(intrinsicAttributes['assembler-profile']) ?? componentVersionProfiles?.get() ?? {}
|
|
142
148
|
const { navFiles, messages } = overrides
|
|
143
|
-
|
|
149
|
+
Object.entries(overrides.attributes ?? {}).forEach(([name, val]) => {
|
|
150
|
+
attributes[name] = val
|
|
151
|
+
})
|
|
152
|
+
const model = Object.assign({}, base, overrides, { attributes, logger })
|
|
144
153
|
delete model.navFiles
|
|
145
154
|
delete model.messages
|
|
146
155
|
const navigationOverride = navigationCatalog?.getNavigation(componentVersion.name, componentVersion.version)
|
package/lib/configure.js
CHANGED
|
@@ -16,8 +16,17 @@ function internalConfigure (converter, config = {}, providers = {}) {
|
|
|
16
16
|
|
|
17
17
|
this.once('navigationBuilt', async ({ playbook, contentCatalog }) => {
|
|
18
18
|
const { assembleContent = require('./assemble-content'), ...assembleContentConfig } = providers
|
|
19
|
-
assembleContentConfig.configSource
|
|
20
|
-
|
|
19
|
+
if (assembleContentConfig.configSource?.constructor === Object) {
|
|
20
|
+
await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentConfig)
|
|
21
|
+
} else {
|
|
22
|
+
const singleConfig = !('configFiles' in config)
|
|
23
|
+
const configFiles = singleConfig ? config.configFile : config.configFiles
|
|
24
|
+
for (const configSource of Array.isArray(configFiles) ? configFiles : [configFiles]) {
|
|
25
|
+
const assembleContentConfigWithConfigSource = Object.assign({}, assembleContentConfig, { configSource })
|
|
26
|
+
await assembleContent.call(this, playbook, contentCatalog, converter, assembleContentConfigWithConfigSource)
|
|
27
|
+
if (singleConfig) break
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
})
|
|
22
31
|
}
|
|
23
32
|
|
package/lib/load-config.js
CHANGED
|
@@ -14,6 +14,7 @@ const ASSEMBLY_KEYS = [
|
|
|
14
14
|
'linkReferenceStyle',
|
|
15
15
|
'dropExplicitXrefText',
|
|
16
16
|
]
|
|
17
|
+
const CAMEL_CASE_STOP_PATHS = ['asciidoc.attributes', 'assembly.attributes']
|
|
17
18
|
|
|
18
19
|
function loadConfig (playbook, configSource) {
|
|
19
20
|
let resolvedConfigSource
|
|
@@ -38,19 +39,11 @@ function loadConfig (playbook, configSource) {
|
|
|
38
39
|
return fsp
|
|
39
40
|
.readFile(resolvedConfigSource)
|
|
40
41
|
.then((data) =>
|
|
41
|
-
Object.assign(camelCaseKeys(yaml.load(data),
|
|
42
|
+
Object.assign(camelCaseKeys(yaml.load(data), CAMEL_CASE_STOP_PATHS), { file: resolvedConfigSource })
|
|
42
43
|
)
|
|
43
44
|
})
|
|
44
45
|
).then((config) => {
|
|
45
46
|
if (config.enabled === false) return config
|
|
46
|
-
let asciidocAttrs
|
|
47
|
-
if (!config.asciidoc) {
|
|
48
|
-
config.asciidoc = { attributes: (asciidocAttrs = {}) }
|
|
49
|
-
} else if (!(asciidocAttrs = config.asciidoc.attributes)) {
|
|
50
|
-
config.asciidoc.attributes = asciidocAttrs = {}
|
|
51
|
-
}
|
|
52
|
-
if (!('revdate' in asciidocAttrs)) asciidocAttrs.revdate = getLocalDate()
|
|
53
|
-
asciidocAttrs['page-partial'] = null
|
|
54
47
|
const remapComponentVersionsKey = !('componentVersionFilter' in config)
|
|
55
48
|
const componentVersionFilter = (config.componentVersionFilter ??= {})
|
|
56
49
|
if (remapComponentVersionsKey && 'componentVersions' in config) {
|
|
@@ -71,8 +64,24 @@ function loadConfig (playbook, configSource) {
|
|
|
71
64
|
delete config[key]
|
|
72
65
|
}
|
|
73
66
|
}
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
let assemblyAttrs
|
|
68
|
+
if ('attributes' in assembly) {
|
|
69
|
+
assemblyAttrs = assembly.attributes ?? {}
|
|
70
|
+
if ('asciidoc' in config) delete config.asciidoc
|
|
71
|
+
} else if ('asciidoc' in config) {
|
|
72
|
+
assemblyAttrs = assembly.attributes = config.asciidoc?.attributes ?? {}
|
|
73
|
+
delete config.asciidoc
|
|
74
|
+
} else {
|
|
75
|
+
assemblyAttrs = assembly.attributes = {}
|
|
76
|
+
}
|
|
77
|
+
assemblyAttrs['page-partial'] = null
|
|
78
|
+
config.asciidoc = Object.defineProperty({}, 'attributes', {
|
|
79
|
+
get: function () {
|
|
80
|
+
return this.assembly.attributes
|
|
81
|
+
}.bind(config),
|
|
82
|
+
})
|
|
83
|
+
if (!('doctype' in assembly)) assembly.doctype = 'doctype' in assemblyAttrs ? assemblyAttrs.doctype : 'book'
|
|
84
|
+
delete assemblyAttrs.doctype
|
|
76
85
|
if (!('rootLevel' in assembly)) assembly.rootLevel = 0
|
|
77
86
|
if (!('insertStartPage' in assembly)) assembly.insertStartPage = true
|
|
78
87
|
if (['discrete', 'fuse', 'enclose'].indexOf(assembly.sectionMergeStrategy) < 0) {
|
|
@@ -84,6 +93,7 @@ function loadConfig (playbook, configSource) {
|
|
|
84
93
|
if (['always', 'if-redundant', 'never'].indexOf(assembly.dropExplicitXrefText) < 0) {
|
|
85
94
|
assembly.dropExplicitXrefText = 'never'
|
|
86
95
|
}
|
|
96
|
+
assembly.revdate = getLocalDate()
|
|
87
97
|
const build = (config.build ??= {})
|
|
88
98
|
if (build.dir === '$' + '{playbook.output.dir}') {
|
|
89
99
|
throw new Error('Not implemented')
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const createAsciiDocFile = require('./util/create-asciidoc-file')
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const createResourceKey = require('./util/create-resource-key')
|
|
5
|
+
const generateScopedId = require('./util/generate-scoped-id')
|
|
6
|
+
const { resolveLinkTarget } = require('./util/resolver')
|
|
7
|
+
const { rewriteXrefs, rewriteImageRef, rewriteInlineImages, rewriteStyleAttribute } = require('./util/rewriter')
|
|
8
|
+
const { NamedIdAttrRx } = require('./util/rx')
|
|
6
9
|
const sanitize = require('./util/sanitize')
|
|
7
10
|
const unconvertInlineAsciiDoc = require('./util/unconvert-inline-asciidoc')
|
|
8
11
|
|
|
@@ -10,7 +13,6 @@ const AttributeEntryRx = /^:([^:-][^:]*):(?: .*)?$/
|
|
|
10
13
|
const BuiltInNamedEntities = { amp: '&', apos: "'", gt: '>', lt: '<', nbsp: ' ', quot: '"' }
|
|
11
14
|
const CharRefRx = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d{2,6})|x([a-z\d]{2,5})));/g
|
|
12
15
|
const DiscardAttributes = 'doctype leveloffset assembly-navtitle assembly-style underscore'.split(' ')
|
|
13
|
-
const ReservedIdNames = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
|
|
14
16
|
|
|
15
17
|
function produceAssemblyFile (
|
|
16
18
|
loadAsciiDoc,
|
|
@@ -24,7 +26,14 @@ function produceAssemblyFile (
|
|
|
24
26
|
) {
|
|
25
27
|
const pagesByUrl = files.reduce((map, it) => (it.src.family === 'page' ? map.set(it.pub.url, it) : map), new Map())
|
|
26
28
|
if (outline.urlType === 'internal' && !pagesByUrl.get(outline.url) && !(outline.items || []).length) return
|
|
27
|
-
const pagesInOutline = selectPagesInOutline(outline, pagesByUrl
|
|
29
|
+
const pagesInOutline = selectPagesInOutline(outline, pagesByUrl)
|
|
30
|
+
pagesInOutline.generateIdFromTitle = generateIdFromTitle.bind(
|
|
31
|
+
loadAsciiDoc(
|
|
32
|
+
{ contents: Buffer.alloc(0), src: { family: 'page', relative: 'generate-id.adoc' } },
|
|
33
|
+
undefined,
|
|
34
|
+
asciidocConfig
|
|
35
|
+
)
|
|
36
|
+
)
|
|
28
37
|
const buffer = mergeAsciiDoc(
|
|
29
38
|
loadAsciiDoc,
|
|
30
39
|
contentCatalog,
|
|
@@ -63,29 +72,30 @@ function buildAsciiDocHeader (componentVersion, navtitle, assemblyModel) {
|
|
|
63
72
|
let doctitle = navtitleAsciiDoc
|
|
64
73
|
if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
|
|
65
74
|
const version = componentVersion.version === 'master' ? '' : componentVersion.version
|
|
75
|
+
const displayVersion = componentVersion.displayVersion
|
|
66
76
|
const buffer = [
|
|
67
77
|
`= ${doctitle}`,
|
|
68
|
-
...(version ? [`:revnumber: ${
|
|
78
|
+
...(version ? [`:revnumber: ${displayVersion}`] : []),
|
|
69
79
|
...(doctype === 'article' ? [] : [`:doctype: ${doctype ?? 'book'}`]),
|
|
70
80
|
':underscore: _',
|
|
71
81
|
// Q: should we pass these via the CLI so they cannot be modified?
|
|
72
82
|
`:page-component-name: ${componentVersion.name}`,
|
|
73
83
|
`:page-component-version:${version ? ' ' + version : ''}`,
|
|
74
84
|
':page-version: {page-component-version}',
|
|
75
|
-
`:page-component-display-version: ${
|
|
85
|
+
`:page-component-display-version: ${displayVersion}`,
|
|
76
86
|
`:page-component-title: ${componentVersion.title}`,
|
|
77
87
|
]
|
|
78
88
|
return Object.assign(buffer, { navtitle })
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
function selectPagesInOutline (outlineEntry, pagesByUrl,
|
|
91
|
+
function selectPagesInOutline (outlineEntry, pagesByUrl, accum) {
|
|
82
92
|
accum ??= Object.assign(new Map(), { assembled: { pages: new Map(), assets: new Set() } })
|
|
83
93
|
const page = outlineEntry.urlType === 'internal' ? pagesByUrl.get(outlineEntry.url) : undefined
|
|
84
94
|
if (page) {
|
|
85
95
|
accum.set(createResourceKey(page.src), page)
|
|
86
96
|
accum.set(outlineEntry.url, page)
|
|
87
97
|
}
|
|
88
|
-
for (const item of outlineEntry.items || []) selectPagesInOutline(item, pagesByUrl,
|
|
98
|
+
for (const item of outlineEntry.items || []) selectPagesInOutline(item, pagesByUrl, accum)
|
|
89
99
|
return accum
|
|
90
100
|
}
|
|
91
101
|
|
|
@@ -113,26 +123,19 @@ function mergeAsciiDoc (
|
|
|
113
123
|
let navtitlePlain = sanitize(navtitle)
|
|
114
124
|
let navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
|
|
115
125
|
const { items = [], unresolved, urlType, url } = outlineEntry
|
|
116
|
-
const {
|
|
117
|
-
|
|
118
|
-
filetype,
|
|
119
|
-
embedReferenceStyle: embedRefStyle,
|
|
120
|
-
linkReferenceStyle: linkRefStyle,
|
|
121
|
-
outDirname,
|
|
122
|
-
siteRoot,
|
|
123
|
-
xmlIds,
|
|
124
|
-
logger,
|
|
125
|
-
} = assemblyModel
|
|
126
|
+
const { doctype, filetype, linkReferenceStyle, pubRoot, siteRoot, xmlIds, logger } = assemblyModel
|
|
127
|
+
const assembled = pagesInOutline.assembled
|
|
126
128
|
// FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
|
|
127
129
|
const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
|
|
128
130
|
const atDocumentRoot = !buffer.inBody
|
|
129
131
|
const atBookRoot = atDocumentRoot && !level && doctype === 'book' && (supportsParts = true)
|
|
130
132
|
const hasItems = items.length > 0
|
|
131
|
-
const
|
|
133
|
+
const idPrefix =
|
|
134
|
+
asciidocConfig.attributes['assembler-idprefix'] ?? ('assembler-idprefix' in asciidocConfig.attributes ? '' : '_')
|
|
132
135
|
const idSeparator = xmlIds ? '-' : ':'
|
|
133
136
|
const idScopeSeparator = idSeparator.repeat(3)
|
|
134
137
|
const idCoordinateSeparator = idSeparator === '-' ? '----' : idSeparator
|
|
135
|
-
if (page && !
|
|
138
|
+
if (page && !assembled.pages.has(page)) {
|
|
136
139
|
const contents = page.src.contents
|
|
137
140
|
if (contents == null) {
|
|
138
141
|
buffer.inBody ??= false
|
|
@@ -143,16 +146,15 @@ function mergeAsciiDoc (
|
|
|
143
146
|
Object.assign({}, page, { contents: trimAsciiDoc(contents), mediaType })
|
|
144
147
|
)
|
|
145
148
|
const doc = loadAsciiDoc(pageAsAsciiDoc, contentCatalog, asciidocConfig)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
doc.logger = logger
|
|
150
|
+
? Object.assign(doc.getLogger().$dup(), {
|
|
151
|
+
delegate: {
|
|
152
|
+
warn () {
|
|
153
|
+
return logger.warn.apply(logger, arguments)
|
|
154
|
+
},
|
|
152
155
|
},
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
+
})
|
|
157
|
+
: doc.getLogger()
|
|
156
158
|
if (doc.hasAttribute('assembly-navtitle')) {
|
|
157
159
|
navtitleAsciiDoc = doc.getAttribute('assembly-navtitle')
|
|
158
160
|
navtitlePlain = sanitize((navtitle = doc.$apply_reftext_subs(navtitleAsciiDoc)))
|
|
@@ -180,7 +182,14 @@ function mergeAsciiDoc (
|
|
|
180
182
|
}
|
|
181
183
|
// NOTE: in Antora, docname is relative src path from module without file extension
|
|
182
184
|
const docname = doc.getAttribute('docname')
|
|
183
|
-
const {
|
|
185
|
+
const { idLeader: idScopeLeader, id: idScope } = generateScopedId(
|
|
186
|
+
page.src,
|
|
187
|
+
componentVersion,
|
|
188
|
+
idCoordinateSeparator,
|
|
189
|
+
idScopeSeparator,
|
|
190
|
+
idPrefix,
|
|
191
|
+
filetype
|
|
192
|
+
)
|
|
184
193
|
let pageFragment = ''
|
|
185
194
|
let pageRoles = ''
|
|
186
195
|
let pageStyle = doc.getAttribute('assembly-style', '')
|
|
@@ -236,7 +245,7 @@ function mergeAsciiDoc (
|
|
|
236
245
|
assemblyModel = Object.assign({}, assemblyModel, { sectionMergeStrategy: 'discrete' })
|
|
237
246
|
} else {
|
|
238
247
|
if (atDocumentRoot) {
|
|
239
|
-
pageFragment = `#${
|
|
248
|
+
pageFragment = `#${idScopeLeader}${doc.getId() ?? pageStyle}`
|
|
240
249
|
buffer.unshift(`[#${idScope}]`)
|
|
241
250
|
} else {
|
|
242
251
|
pageFragment = `#${idScope}`
|
|
@@ -269,28 +278,18 @@ function mergeAsciiDoc (
|
|
|
269
278
|
//if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
270
279
|
const overviewTitle = doc.getAttribute('overview-title', 'Overview')
|
|
271
280
|
buffer.push('')
|
|
272
|
-
|
|
273
|
-
let toggleSectids, syntheticId
|
|
274
|
-
if (doc.isAttribute('sectids')) {
|
|
275
|
-
if (doc.isAttributeLocked('sectids')) {
|
|
276
|
-
syntheticId = `__object-id-${getObjectId(outlineEntry)}`
|
|
277
|
-
} else {
|
|
278
|
-
buffer.push(':!sectids:')
|
|
279
|
-
toggleSectids = true
|
|
280
|
-
}
|
|
281
|
-
}
|
|
281
|
+
const syntheticId = pagesInOutline.generateIdFromTitle(navtitleAsciiDoc, idCoordinateSeparator)
|
|
282
282
|
let hlevel = level + 2
|
|
283
283
|
if (hlevel > 6) {
|
|
284
284
|
const blockStyle = `discrete.h${hlevel}`
|
|
285
285
|
hlevel = 6
|
|
286
|
-
buffer.push(
|
|
287
|
-
} else
|
|
286
|
+
buffer.push(`[${blockStyle}#${syntheticId}]`)
|
|
287
|
+
} else {
|
|
288
288
|
buffer.push(`[#${syntheticId}]`)
|
|
289
289
|
}
|
|
290
290
|
buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
|
|
291
|
-
if (toggleSectids) buffer.push(':sectids:')
|
|
292
291
|
}
|
|
293
|
-
|
|
292
|
+
assembled.pages.set(page, pageFragment)
|
|
294
293
|
if (doc.hasSections()) {
|
|
295
294
|
fixSectionLevels(doc.getSections(), atBookRoot && !pageStyle ? undefined : nextSectionLevel)
|
|
296
295
|
}
|
|
@@ -318,10 +317,6 @@ function mergeAsciiDoc (
|
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
319
|
const refs = doc.getCatalog().refs
|
|
321
|
-
const scannedInlineAnchors = Object.values(refs.$$smap).reduce((accum, it) => {
|
|
322
|
-
if (it.node_name === 'inline_anchor') accum[it.id] = it
|
|
323
|
-
return accum
|
|
324
|
-
}, {})
|
|
325
320
|
allBlocks.forEach((block) => {
|
|
326
321
|
const contentModel = block.content_model
|
|
327
322
|
if (
|
|
@@ -380,112 +375,33 @@ function mergeAsciiDoc (
|
|
|
380
375
|
if (!refs['$key?'](refid) && (~refid.indexOf(' ') || refid.toLowerCase() !== refid)) {
|
|
381
376
|
if ((refid = doc.$resolve_id(refid))['$nil?']()) return m
|
|
382
377
|
}
|
|
383
|
-
|
|
384
|
-
return `<<${alphaPrefix}${idPrefix}${refid}${text ? ',' + text : ''}>>`
|
|
378
|
+
return `<<${idScopeLeader}${refid}${text ? ',' + text : ''}>>`
|
|
385
379
|
})
|
|
386
380
|
}
|
|
387
381
|
// NOTE: the next check takes care of inline and block anchors
|
|
388
382
|
if (~line.indexOf('[[')) {
|
|
389
|
-
line = line.replace(/\[\[([\p{Alpha}_:][\p{Alpha}0-9_\-:.]*)(|, *.+?)\]\]/gu, (
|
|
390
|
-
|
|
391
|
-
return `[[${alphaPrefix}${idPrefix}${refid}${text}]]`
|
|
383
|
+
line = line.replace(/\[\[([\p{Alpha}_:][\p{Alpha}0-9_\-:.]*)(|, *.+?)\]\]/gu, (_, refid, text) => {
|
|
384
|
+
return `[[${idScopeLeader}${refid}${text}]]`
|
|
392
385
|
})
|
|
393
386
|
}
|
|
394
387
|
if (~line.indexOf('xref:')) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
// Q: should we validate the internal ID here?
|
|
411
|
-
if (!resourceRef) return `xref:${idPrefix}${fragment}[${text}]`
|
|
412
|
-
const resourceId = parseResourceRef(resourceRef, page.src, 'page', contentCatalog)
|
|
413
|
-
if (resourceId.family !== 'page' || !(resource = pagesInOutline.get(createResourceKey(resourceId)))) {
|
|
414
|
-
if ((resource = contentCatalog.getById(resourceId))?.pub) {
|
|
415
|
-
text ||= resource.asciidoc?.xreftext || target_
|
|
416
|
-
if (siteRoot || linkRefStyle === 'relative') {
|
|
417
|
-
return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkRefStyle)}${fragment && '#' + fragment}[${text}]`
|
|
418
|
-
}
|
|
419
|
-
asciidoctorLogger.warn(
|
|
420
|
-
doc.createLogMessage(
|
|
421
|
-
`Cannot create external ${resourceId.family} reference in assembly because site URL is unknown: ${target_}`,
|
|
422
|
-
{ source_location: { file: relative, lineno: idx + 1 } }
|
|
423
|
-
)
|
|
424
|
-
)
|
|
425
|
-
}
|
|
426
|
-
const linkAttrlist = text
|
|
427
|
-
? (~text.indexOf(',') ? `"${text}"` : text) + ',role=unresolved'
|
|
428
|
-
: (~target.indexOf(':') ? `${target},` : '') + 'role=unresolved'
|
|
429
|
-
return `link:${target_}[${linkAttrlist}]`
|
|
430
|
-
}
|
|
431
|
-
if (fragment === resource.asciidoc.id) fragment = ''
|
|
432
|
-
if (
|
|
433
|
-
text &&
|
|
434
|
-
(assemblyModel.dropExplicitXrefText === 'always' ||
|
|
435
|
-
(assemblyModel.dropExplicitXrefText === 'if-redundant' && text === resource.title))
|
|
436
|
-
) {
|
|
437
|
-
text = ''
|
|
438
|
-
}
|
|
439
|
-
const refid = generateId(
|
|
440
|
-
resource.src,
|
|
441
|
-
componentVersion,
|
|
442
|
-
idCoordinateSeparator,
|
|
443
|
-
idScopeSeparator,
|
|
444
|
-
text.length > 0,
|
|
445
|
-
fragment
|
|
446
|
-
).id
|
|
447
|
-
return `xref:${refid}[${text}]`
|
|
448
|
-
})
|
|
449
|
-
}
|
|
450
|
-
if (~line.indexOf('link:{attachmentsdir}/')) {
|
|
451
|
-
line = line.replace(/(?<![\\+])link:\{attachmentsdir\}\/([^\s[]+)\[(|.*?[^\\])\]/g, (m, relative, text) => {
|
|
452
|
-
const attachment =
|
|
453
|
-
(siteRoot || linkRefStyle === 'relative') &&
|
|
454
|
-
contentCatalog.getById({
|
|
455
|
-
component: componentVersion.name,
|
|
456
|
-
version: componentVersion.version,
|
|
457
|
-
module: module_,
|
|
458
|
-
family: 'attachment',
|
|
459
|
-
relative,
|
|
460
|
-
})
|
|
461
|
-
// TODO: handle unresolved attachment page
|
|
462
|
-
return attachment?.out ? `${resolveLinkTarget(attachment, siteRoot, pubRoot, linkRefStyle)}[${text}]` : m
|
|
463
|
-
})
|
|
388
|
+
line = rewriteXrefs(
|
|
389
|
+
line,
|
|
390
|
+
contentCatalog,
|
|
391
|
+
assemblyModel,
|
|
392
|
+
page.src,
|
|
393
|
+
true,
|
|
394
|
+
idCoordinateSeparator,
|
|
395
|
+
idScopeSeparator,
|
|
396
|
+
idPrefix,
|
|
397
|
+
idScopeLeader,
|
|
398
|
+
pagesInOutline,
|
|
399
|
+
doc,
|
|
400
|
+
{ file: relative, lineno: idx + 1 }
|
|
401
|
+
)
|
|
464
402
|
}
|
|
465
403
|
if (~line.indexOf('image:') && !line.startsWith('image::')) {
|
|
466
|
-
line = line
|
|
467
|
-
let image
|
|
468
|
-
if (
|
|
469
|
-
isResourceSpec(target) &&
|
|
470
|
-
(image = contentCatalog.resolveResource(target, page.src, 'image', ['image']))?.out
|
|
471
|
-
) {
|
|
472
|
-
if (filetype !== 'html') {
|
|
473
|
-
pagesInOutline.assembled.assets.add(image)
|
|
474
|
-
return `image:${resolveEmbedTarget(image, outDirname, embedRefStyle, true)}[${attrlist}]`
|
|
475
|
-
}
|
|
476
|
-
if (siteRoot || linkRefStyle === 'relative') {
|
|
477
|
-
pagesInOutline.assembled.assets.add(image)
|
|
478
|
-
return `image:${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle)}[${attrlist}]`
|
|
479
|
-
}
|
|
480
|
-
asciidoctorLogger.warn(
|
|
481
|
-
doc.createLogMessage(
|
|
482
|
-
`Cannot create external image reference in assembly because site URL is unknown: ${target}`,
|
|
483
|
-
{ source_location: { file: relative, lineno: idx + 1 } }
|
|
484
|
-
)
|
|
485
|
-
)
|
|
486
|
-
}
|
|
487
|
-
return m
|
|
488
|
-
})
|
|
404
|
+
line = rewriteInlineImages(line, contentCatalog, assemblyModel, page.src, assembled.assets, true)
|
|
489
405
|
}
|
|
490
406
|
lines[idx] = line
|
|
491
407
|
}
|
|
@@ -511,7 +427,7 @@ function mergeAsciiDoc (
|
|
|
511
427
|
return '='.repeat(targetMarkerLength) + ' ' + rest
|
|
512
428
|
})
|
|
513
429
|
// NOTE: ID will be undefined if sectids are turned off
|
|
514
|
-
if (block.getId()) rewriteStyleAttribute(block, lines, idx,
|
|
430
|
+
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idScopeLeader, blockStyle)
|
|
515
431
|
} else {
|
|
516
432
|
if (context === 'image') {
|
|
517
433
|
let line = lines[idx] || ''
|
|
@@ -530,27 +446,17 @@ function mergeAsciiDoc (
|
|
|
530
446
|
imageMacroOffset = -1
|
|
531
447
|
}
|
|
532
448
|
}
|
|
533
|
-
if (imageMacroOffset
|
|
449
|
+
if (~imageMacroOffset) {
|
|
534
450
|
const target = block.getAttribute('target')
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
// FIXME: handle (or report) case when image is not resolved
|
|
538
|
-
if (image?.out && (filetype !== 'html' || siteRoot)) {
|
|
539
|
-
const attrlist = line.slice(line.indexOf('[') + 1, -1)
|
|
540
|
-
pagesInOutline.assembled.assets.add(image)
|
|
541
|
-
lines[idx] =
|
|
542
|
-
filetype === 'html'
|
|
543
|
-
? `${prefix}image::${resolveLinkTarget(image, siteRoot, pubRoot, linkRefStyle, false)}[${attrlist}]`
|
|
544
|
-
: `${prefix}image::${resolveEmbedTarget(image, outDirname, embedRefStyle)}[${attrlist}]`
|
|
545
|
-
}
|
|
546
|
-
}
|
|
451
|
+
const newTarget = rewriteImageRef(target, contentCatalog, assemblyModel, page.src, assembled.assets)
|
|
452
|
+
if (newTarget) lines[idx] = `${prefix}image::${newTarget}${line.slice(line.indexOf('['))}`
|
|
547
453
|
lastImageMacroAt = [idx, imageMacroOffset]
|
|
548
454
|
}
|
|
549
455
|
} else if (context === 'document' && block.hasHeader()) {
|
|
550
456
|
// nested document
|
|
551
457
|
idx = (block.getHeader().getLineNumber() || idx + 1) - 1
|
|
552
458
|
}
|
|
553
|
-
if (block.getId()) rewriteStyleAttribute(block, lines, idx,
|
|
459
|
+
if (block.getId()) rewriteStyleAttribute(block, lines, idx, idScopeLeader)
|
|
554
460
|
}
|
|
555
461
|
})
|
|
556
462
|
safePush(
|
|
@@ -585,20 +491,7 @@ function mergeAsciiDoc (
|
|
|
585
491
|
} else {
|
|
586
492
|
buffer.inBody = true
|
|
587
493
|
buffer.push('')
|
|
588
|
-
|
|
589
|
-
// Q: should we unset docname, page-module, etc?
|
|
590
|
-
let toggleSectids, syntheticId
|
|
591
|
-
if (!('sectids' in asciidocConfig.attributes)) {
|
|
592
|
-
buffer.push(':!sectids:')
|
|
593
|
-
toggleSectids = true
|
|
594
|
-
} else if (typeof asciidocConfig.attributes.sectids === 'string') {
|
|
595
|
-
if ('sectids' in mutableAttributes) {
|
|
596
|
-
buffer.push(':!sectids:')
|
|
597
|
-
toggleSectids = true
|
|
598
|
-
} else {
|
|
599
|
-
syntheticId = `__object-id-${global.Opal.hash(outlineEntry).$object_id()}`
|
|
600
|
-
}
|
|
601
|
-
}
|
|
494
|
+
const syntheticId = pagesInOutline.generateIdFromTitle(navtitleAsciiDoc, idCoordinateSeparator)
|
|
602
495
|
let sectionTitle = navtitleAsciiDoc
|
|
603
496
|
if (urlType === 'external') {
|
|
604
497
|
sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
@@ -606,22 +499,29 @@ function mergeAsciiDoc (
|
|
|
606
499
|
const resource = files.find((it) => it.pub.url === url)
|
|
607
500
|
if (resource) {
|
|
608
501
|
if (resource.src.family === 'page' && pagesInOutline.has(resource.pub.url)) {
|
|
609
|
-
const refid =
|
|
502
|
+
const { id: refid } = generateScopedId(
|
|
503
|
+
resource.src,
|
|
504
|
+
componentVersion,
|
|
505
|
+
idCoordinateSeparator,
|
|
506
|
+
idScopeSeparator,
|
|
507
|
+
idPrefix,
|
|
508
|
+
filetype,
|
|
509
|
+
true
|
|
510
|
+
)
|
|
610
511
|
sectionTitle = `xref:${refid}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
611
512
|
} else if (siteRoot) {
|
|
612
|
-
sectionTitle = `${resolveLinkTarget(resource, siteRoot, pubRoot,
|
|
513
|
+
sectionTitle = `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, true)}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
613
514
|
}
|
|
614
515
|
}
|
|
615
516
|
}
|
|
616
517
|
let hlevel = level + 1
|
|
617
518
|
if (hlevel > 6) {
|
|
618
519
|
hlevel = 6
|
|
619
|
-
buffer.push(
|
|
620
|
-
} else
|
|
520
|
+
buffer.push(`[discrete#${syntheticId}]`)
|
|
521
|
+
} else {
|
|
621
522
|
buffer.push(`[#${syntheticId}]`)
|
|
622
523
|
}
|
|
623
524
|
buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
|
|
624
|
-
if (toggleSectids) buffer.push(':sectids:')
|
|
625
525
|
}
|
|
626
526
|
}
|
|
627
527
|
|
|
@@ -697,6 +597,7 @@ function processDocumentHeader (doc, lines, buffer, ignoreLines) {
|
|
|
697
597
|
.slice(1, -1)
|
|
698
598
|
.trim()
|
|
699
599
|
.replace(/(?:^\w[\w-]*(?!=|[\w-]))?([#.%]\w[\w-]*)*/, '')
|
|
600
|
+
.replace(NamedIdAttrRx, '')
|
|
700
601
|
if (attrlist) buffer.push('[' + attrlist + ']')
|
|
701
602
|
}
|
|
702
603
|
} else if (belowDoctitle) {
|
|
@@ -732,64 +633,6 @@ function fixSectionLevels (sections, expectedLevel) {
|
|
|
732
633
|
})
|
|
733
634
|
}
|
|
734
635
|
|
|
735
|
-
function rewriteStyleAttribute (block, lines, idx, idPrefix, replacementStyle = '') {
|
|
736
|
-
let prevLine = lines[idx - 1]
|
|
737
|
-
const char0 = prevLine?.charAt()
|
|
738
|
-
if (char0) {
|
|
739
|
-
if (
|
|
740
|
-
(char0 === '.' && /^\.\.?[^ \t.]/.test(prevLine)) ||
|
|
741
|
-
(char0 === '[' &&
|
|
742
|
-
prevLine.charAt(1) === '[' &&
|
|
743
|
-
/^\[\[(?:|[\p{Alpha}_:][\p{Alpha}0-9_\-:.]*(?:, *.+)?)\]\]$/u.test(prevLine))
|
|
744
|
-
) {
|
|
745
|
-
return rewriteStyleAttribute(block, lines, idx - 1, idPrefix, replacementStyle)
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
let cellSpec
|
|
749
|
-
if (
|
|
750
|
-
char0 &&
|
|
751
|
-
(char0 === '[' || (block.getDocument().isNested() && (cellSpec = prevLine.match(/^([^[|]*)\| *(\[.+)/)))) &&
|
|
752
|
-
prevLine.charAt(prevLine.length - 1) === ']'
|
|
753
|
-
) {
|
|
754
|
-
if (cellSpec) {
|
|
755
|
-
prevLine = cellSpec[2]
|
|
756
|
-
cellSpec = cellSpec[1]
|
|
757
|
-
}
|
|
758
|
-
let rawStyle
|
|
759
|
-
const commaIdx = prevLine.indexOf(',')
|
|
760
|
-
if (~commaIdx) {
|
|
761
|
-
rawStyle = prevLine.slice(1, commaIdx)
|
|
762
|
-
if (~rawStyle.indexOf('=')) rawStyle = undefined
|
|
763
|
-
} else if (!~prevLine.indexOf('=')) {
|
|
764
|
-
rawStyle = prevLine.slice(1, prevLine.length - 1)
|
|
765
|
-
}
|
|
766
|
-
if (rawStyle) {
|
|
767
|
-
if (~rawStyle.indexOf('#')) {
|
|
768
|
-
prevLine = prevLine.replace(/#[^.%,\]]+/, `#${idPrefix}${block.getId()}`)
|
|
769
|
-
if (replacementStyle) prevLine = prevLine.replace(/^[^#.%,\]]+/, `[${replacementStyle}`)
|
|
770
|
-
} else {
|
|
771
|
-
prevLine = `[${
|
|
772
|
-
replacementStyle ? rawStyle.replace(/^[^.%]*/, replacementStyle) : rawStyle
|
|
773
|
-
}#${idPrefix}${block.getId()}${prevLine.slice(rawStyle.length + 1)}`
|
|
774
|
-
}
|
|
775
|
-
} else {
|
|
776
|
-
prevLine = `[${replacementStyle}#${idPrefix}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.slice(1)}`
|
|
777
|
-
}
|
|
778
|
-
if (cellSpec) prevLine = `${cellSpec}|${prevLine}`
|
|
779
|
-
lines[idx - 1] = prevLine
|
|
780
|
-
} else {
|
|
781
|
-
lines.splice(idx, 0, `[${replacementStyle}#${idPrefix}${block.getId()}]`)
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function isResourceSpec (str) {
|
|
786
|
-
return !(~str.indexOf(':') && (~str.indexOf('://') || (str.startsWith('data:') && ~str.indexOf(','))))
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
function getObjectId (obj) {
|
|
790
|
-
return global.Opal.id(obj)
|
|
791
|
-
}
|
|
792
|
-
|
|
793
636
|
// NOTE: blank lines at top and bottom of document create mismatch when using line numbers to navigate source lines
|
|
794
637
|
// IMPORTANT: this must not leave behind lines the parser will drop!
|
|
795
638
|
// IDEA: another option is to capture initial lineno of reader and use as offset (but preseves those blank lines)
|
|
@@ -812,58 +655,11 @@ function safePush (onto, entries) {
|
|
|
812
655
|
}
|
|
813
656
|
}
|
|
814
657
|
|
|
815
|
-
function
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function resolveLinkTarget (resource, siteRoot, pubRoot, referenceStyle, escapeForInline = true) {
|
|
822
|
-
let target
|
|
823
|
-
if (resource.site?.url) {
|
|
824
|
-
target = ['', resource.pub.url]
|
|
825
|
-
} else {
|
|
826
|
-
switch (referenceStyle) {
|
|
827
|
-
case 'absolute':
|
|
828
|
-
target = ['', siteRoot.url + resource.pub.url]
|
|
829
|
-
break
|
|
830
|
-
case 'root-relative':
|
|
831
|
-
target = ['link:', siteRoot.path + resource.pub.url]
|
|
832
|
-
break
|
|
833
|
-
default:
|
|
834
|
-
target = ['link:', computeRelativeUrl(pubRoot + '/', resource.pub.url)]
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
if (escapeForInline) target[1] = target[1].replace(/_/g, '{underscore}')
|
|
838
|
-
return target.join('')
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
function computeRelativeUrl (from, to) {
|
|
842
|
-
const rel = path.relative(from, to)
|
|
843
|
-
return to.charAt(to.length - 1) === '/' ? rel + '/' : rel
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function createResourceKey ({ component, version, module: mod, family, relative }) {
|
|
847
|
-
return `${version}@${component}:${mod === 'ROOT' ? '' : mod}:${family === 'page' ? '' : family + '$'}${relative}`
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function generateId (componentSrc, componentVersion, coordinateSep, scopeSep, asXrefTarget, fragment) {
|
|
851
|
-
let { component, module: mod, relative } = componentSrc
|
|
852
|
-
let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
|
|
853
|
-
if (component !== componentVersion.name) {
|
|
854
|
-
id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSep)
|
|
855
|
-
} else if (mod !== 'ROOT') {
|
|
856
|
-
if (asXrefTarget && coordinateSep === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
|
|
857
|
-
mod = mod.replace(/(?:pass|stem)$/, '\\$&')
|
|
858
|
-
}
|
|
859
|
-
id = mod + coordinateSep + id
|
|
860
|
-
} else if (ReservedIdNames.includes(id)) {
|
|
861
|
-
id += scopeSep
|
|
862
|
-
scopeSep = ''
|
|
863
|
-
}
|
|
864
|
-
const idPrefix = id + scopeSep
|
|
865
|
-
if (fragment) id = idPrefix + fragment
|
|
866
|
-
return { idPrefix, id }
|
|
658
|
+
function generateIdFromTitle (titleAsciiDoc, sep) {
|
|
659
|
+
const Section = this.$class().$const_get('::Asciidoctor::Section')
|
|
660
|
+
const baseId = Section.$generate_id(titleAsciiDoc, this)
|
|
661
|
+
this.getCatalog().refs['$[]='](baseId, true)
|
|
662
|
+
return `_${sep}${baseId}`
|
|
867
663
|
}
|
|
868
664
|
|
|
869
665
|
module.exports = produceAssemblyFile
|
|
@@ -4,14 +4,15 @@ const computeOut = require('./util/compute-out')
|
|
|
4
4
|
const createAsciiDocFile = require('./util/create-asciidoc-file')
|
|
5
5
|
const filterComponentVersions = require('./filter-component-versions')
|
|
6
6
|
const produceAssemblyFile = require('./produce-assembly-file')
|
|
7
|
+
const { rewriteImageAttr, rewriteInlineImages, rewriteXrefs } = require('./util/rewriter')
|
|
7
8
|
const selectMutableAttributes = require('./select-mutable-attributes')
|
|
8
9
|
|
|
9
10
|
const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
|
|
10
|
-
const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
|
|
11
11
|
|
|
12
|
-
function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig,
|
|
13
|
-
const {
|
|
14
|
-
|
|
12
|
+
function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, selectAssemblyProfile) {
|
|
13
|
+
const { assembly: assemblyConfig } = assemblerConfig
|
|
14
|
+
selectAssemblyProfile ??= (componentVersion) => ({
|
|
15
|
+
attributes: assemblyConfig.attributes,
|
|
15
16
|
doctype: assemblyConfig.doctype,
|
|
16
17
|
insertStartPage: assemblyConfig.insertStartPage,
|
|
17
18
|
rootLevel: assemblyConfig.rootLevel,
|
|
@@ -21,36 +22,42 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
|
|
|
21
22
|
embedReferenceStyle: assemblyConfig.embedReferenceStyle,
|
|
22
23
|
linkReferenceStyle: assemblyConfig.linkReferenceStyle,
|
|
23
24
|
dropExplicitXrefText: assemblyConfig.dropExplicitXrefText,
|
|
25
|
+
revdate: assemblyConfig.revdate,
|
|
24
26
|
logger: assemblyConfig.logger, // for tests
|
|
25
27
|
})
|
|
26
|
-
const
|
|
27
|
-
const { revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
|
|
28
|
-
delete assemblerAsciiDocAttributes.revdate
|
|
29
|
-
delete assemblerAsciiDocAttributes['source-highlighter']
|
|
28
|
+
const baseAssemblyAttributes = assemblyConfig.attributes
|
|
30
29
|
const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
|
|
31
30
|
let siteRoot
|
|
32
31
|
const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
|
|
33
32
|
return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter.names).reduce(
|
|
34
33
|
(accum, componentVersion) => {
|
|
35
|
-
const assemblyModel =
|
|
36
|
-
|
|
34
|
+
const assemblyModel = selectAssemblyProfile(componentVersion)
|
|
35
|
+
const { attributes: assemblyAttributes, logger, navigation, rootLevel } = assemblyModel
|
|
36
|
+
if (!navigation) return accum
|
|
37
|
+
const contextualLogger = logger ? { warn: logger.warn.bind(logger, configMdc) } : undefined
|
|
37
38
|
const { name: componentName, version, title } = componentVersion
|
|
38
39
|
const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
|
|
40
|
+
let sourceHighlighter
|
|
41
|
+
if ('source-highlighter' in assemblyAttributes) {
|
|
42
|
+
sourceHighlighter = assemblyAttributes['source-highlighter']
|
|
43
|
+
delete assemblyAttributes['source-highlighter']
|
|
44
|
+
}
|
|
39
45
|
const mergedAsciiDocAttributes = collateAsciiDocAttributes(
|
|
40
|
-
Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes),
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
Object.assign({ revdate: assemblyModel.revdate }, componentVersionAsciiDocConfig.attributes),
|
|
47
|
+
assemblyAttributes,
|
|
48
|
+
contextualLogger
|
|
43
49
|
)
|
|
44
50
|
const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
|
|
45
51
|
attributes: mergedAsciiDocAttributes,
|
|
46
52
|
})
|
|
47
|
-
assemblyModel.
|
|
53
|
+
assemblyModel.filetype = baseAssemblyAttributes['assembler-filetype']
|
|
54
|
+
const outDirname = (assemblyModel.outDirname = computeOut.call(contentCatalog, {
|
|
48
55
|
component: componentName,
|
|
49
56
|
version,
|
|
50
57
|
family: 'export',
|
|
51
58
|
relative: '.index.adoc',
|
|
52
|
-
}).dirname
|
|
53
|
-
assemblyModel.
|
|
59
|
+
}).dirname)
|
|
60
|
+
assemblyModel.pubRoot = outDirname ? '/' + outDirname : ''
|
|
54
61
|
assemblyModel.siteRoot =
|
|
55
62
|
siteRoot === undefined
|
|
56
63
|
? (siteRoot ??= ((val) => {
|
|
@@ -69,17 +76,22 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
|
|
|
69
76
|
assemblyModel.linkReferenceStyle = 'absolute'
|
|
70
77
|
}
|
|
71
78
|
const auxiliaryImages = new Set()
|
|
79
|
+
const ctx = { component: componentName, version }
|
|
72
80
|
Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
if (!(typeof val === 'string' && ~val.indexOf(':'))) return
|
|
82
|
+
if (name.endsWith('-image')) {
|
|
83
|
+
const newVal = rewriteImageAttr(val, contentCatalog, assemblyModel, ctx, auxiliaryImages)
|
|
84
|
+
if (newVal) {
|
|
85
|
+
mergedAsciiDocAttributes[name] = newVal
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const oldVal = val
|
|
90
|
+
if (~val.indexOf('image:')) val = rewriteInlineImages(val, contentCatalog, assemblyModel, ctx, auxiliaryImages)
|
|
91
|
+
if (~val.indexOf('xref:')) val = rewriteXrefs(val, contentCatalog, assemblyModel, ctx)
|
|
92
|
+
if (val !== oldVal) mergedAsciiDocAttributes[name] = val
|
|
80
93
|
})
|
|
81
94
|
const rootEntry = { content: title }
|
|
82
|
-
const { navigation, rootLevel } = assemblyModel
|
|
83
95
|
let startPage =
|
|
84
96
|
'startPage' in componentVersion
|
|
85
97
|
? componentVersion.startPage
|
|
@@ -123,9 +135,12 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
|
|
|
123
135
|
accum.push(assemblyFile)
|
|
124
136
|
return true
|
|
125
137
|
}, false)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
// NOTE restore source highlighter for conversion if defined in Assembler config
|
|
139
|
+
if (sourceHighlighter !== undefined) {
|
|
140
|
+
mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter
|
|
141
|
+
} else if (assemblyModel.filetype !== 'html') {
|
|
142
|
+
delete mergedAsciiDocAttributes['source-highlighter']
|
|
143
|
+
}
|
|
129
144
|
return accum
|
|
130
145
|
},
|
|
131
146
|
[]
|
|
@@ -151,10 +166,6 @@ function includedInNav (items, url) {
|
|
|
151
166
|
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
152
167
|
}
|
|
153
168
|
|
|
154
|
-
function isResourceRef (target) {
|
|
155
|
-
return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
|
|
156
|
-
}
|
|
157
|
-
|
|
158
169
|
function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
159
170
|
const singleEntry = navigation.length === 1
|
|
160
171
|
const items = navigation.reduce((accum, it) => {
|
|
@@ -178,7 +189,7 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
|
|
|
178
189
|
return items
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
function collateAsciiDocAttributes (collated, additional,
|
|
192
|
+
function collateAsciiDocAttributes (collated, additional, logger) {
|
|
182
193
|
Object.entries(additional).forEach(([name, val]) => {
|
|
183
194
|
if (val && val.constructor === String) {
|
|
184
195
|
let alias
|
|
@@ -189,9 +200,7 @@ function collateAsciiDocAttributes (collated, additional, { logger, mdc }) {
|
|
|
189
200
|
if (refname in collated && ref === val) {
|
|
190
201
|
alias = refval
|
|
191
202
|
} else if (collated['attribute-missing'] === 'warn') {
|
|
192
|
-
|
|
193
|
-
logger.warn(mdc, "Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
|
|
194
|
-
}
|
|
203
|
+
logger?.warn("Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
|
|
195
204
|
}
|
|
196
205
|
return ref
|
|
197
206
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ReservedIdNames = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
|
|
4
|
+
|
|
5
|
+
function generateScopedId (componentSrc, componentVersion, coordinateSep, scopeSep, prefix, filetype, asXrefTarget) {
|
|
6
|
+
let { component, module: mod, relative } = componentSrc
|
|
7
|
+
let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
|
|
8
|
+
if (component !== componentVersion.name) {
|
|
9
|
+
id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSep)
|
|
10
|
+
} else if (mod !== 'ROOT') {
|
|
11
|
+
if (asXrefTarget && coordinateSep === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
|
|
12
|
+
prefix = ''
|
|
13
|
+
mod = mod.replace(/(?:pass|stem)$/, '\\$&')
|
|
14
|
+
}
|
|
15
|
+
id = mod + coordinateSep + id
|
|
16
|
+
} else if (filetype === 'html' && ReservedIdNames.includes(id)) {
|
|
17
|
+
id = prefix + id
|
|
18
|
+
prefix = ''
|
|
19
|
+
}
|
|
20
|
+
if (prefix && !/^[\p{Alpha}_:]/u.test(id)) id = prefix + id
|
|
21
|
+
const idLeader = id + scopeSep
|
|
22
|
+
return { idLeader, id }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = generateScopedId
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('node:path/posix')
|
|
4
|
+
|
|
5
|
+
function resolveEmbedTarget (resource, outDirname, referenceStyle, escapeForInline) {
|
|
6
|
+
const target =
|
|
7
|
+
referenceStyle === 'output-relative' ? resource.out.path : path.relative(outDirname + '/', resource.out.path)
|
|
8
|
+
return escapeForInline ? target.replace(/_/g, '{underscore}') : target
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveLinkTarget (resource, siteRoot, pubRoot, referenceStyle, escapeForInline, ensurePrefix = true) {
|
|
12
|
+
let target
|
|
13
|
+
if (resource.site?.url) {
|
|
14
|
+
target = ['', resource.pub.url]
|
|
15
|
+
} else {
|
|
16
|
+
switch (referenceStyle) {
|
|
17
|
+
case 'absolute':
|
|
18
|
+
target = ['', siteRoot.url + resource.pub.url]
|
|
19
|
+
break
|
|
20
|
+
case 'root-relative':
|
|
21
|
+
target = [ensurePrefix ? 'link:' : '', siteRoot.path + resource.pub.url]
|
|
22
|
+
break
|
|
23
|
+
default:
|
|
24
|
+
target = [ensurePrefix ? 'link:' : '', computeRelativeUrl(pubRoot + '/', resource.pub.url)]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (escapeForInline) target[1] = target[1].replace(/_/g, '{underscore}')
|
|
28
|
+
return target.join('')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function computeRelativeUrl (from, to) {
|
|
32
|
+
const rel = path.relative(from, to)
|
|
33
|
+
return to.charAt(to.length - 1) === '/' ? rel + '/' : rel
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { resolveEmbedTarget, resolveLinkTarget }
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createResourceKey = require('./create-resource-key')
|
|
4
|
+
const generateScopedId = require('./generate-scoped-id')
|
|
5
|
+
const parseResourceRef = require('./parse-resource-ref')
|
|
6
|
+
const { resolveEmbedTarget, resolveLinkTarget } = require('./resolver')
|
|
7
|
+
const { NamedIdAttrRx } = require('./rx')
|
|
8
|
+
|
|
9
|
+
function rewriteXrefs (
|
|
10
|
+
line,
|
|
11
|
+
contentCatalog,
|
|
12
|
+
assemblyModel,
|
|
13
|
+
ctx,
|
|
14
|
+
escapeForInline,
|
|
15
|
+
idCoordinateSep,
|
|
16
|
+
idScopeSep,
|
|
17
|
+
idPrefix,
|
|
18
|
+
idLeader,
|
|
19
|
+
pagesInOutline,
|
|
20
|
+
doc,
|
|
21
|
+
sourceLocation
|
|
22
|
+
) {
|
|
23
|
+
return line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|.*?[^\\])\]/gu, (_, target, text) =>
|
|
24
|
+
rewriteXref(
|
|
25
|
+
target,
|
|
26
|
+
text,
|
|
27
|
+
contentCatalog,
|
|
28
|
+
assemblyModel,
|
|
29
|
+
ctx,
|
|
30
|
+
escapeForInline,
|
|
31
|
+
idCoordinateSep,
|
|
32
|
+
idScopeSep,
|
|
33
|
+
idPrefix,
|
|
34
|
+
idLeader,
|
|
35
|
+
pagesInOutline,
|
|
36
|
+
doc,
|
|
37
|
+
sourceLocation
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function rewriteXref (
|
|
43
|
+
target,
|
|
44
|
+
text,
|
|
45
|
+
contentCatalog,
|
|
46
|
+
assemblyModel,
|
|
47
|
+
ctx,
|
|
48
|
+
escapeForInline,
|
|
49
|
+
idCoordinateSep,
|
|
50
|
+
idScopeSep,
|
|
51
|
+
idPrefix,
|
|
52
|
+
idLeader,
|
|
53
|
+
pagesInOutline,
|
|
54
|
+
doc,
|
|
55
|
+
sourceLocation
|
|
56
|
+
) {
|
|
57
|
+
const rawTarget = target
|
|
58
|
+
if (doc && ~target.indexOf('{')) target = doc.$sub_attributes(target, global.Opal.hash({ attribute_missing: 'skip' }))
|
|
59
|
+
let fragment, resourceRef
|
|
60
|
+
const hashIdx = target.indexOf('#')
|
|
61
|
+
if (~hashIdx) {
|
|
62
|
+
resourceRef = target.slice(0, hashIdx)
|
|
63
|
+
fragment = target.slice(hashIdx + 1)
|
|
64
|
+
} else if (target.endsWith('.adoc') || ~target.indexOf('$')) {
|
|
65
|
+
resourceRef = target
|
|
66
|
+
fragment = ''
|
|
67
|
+
} else {
|
|
68
|
+
fragment = target
|
|
69
|
+
}
|
|
70
|
+
if (!resourceRef) return `xref:${idLeader || ''}${fragment}[${text}]`
|
|
71
|
+
const resourceId = parseResourceRef(resourceRef, ctx, 'page', contentCatalog)
|
|
72
|
+
const family = resourceId.family
|
|
73
|
+
let resource
|
|
74
|
+
if (family !== 'page' || !(resource = pagesInOutline?.get(createResourceKey(resourceId)))) {
|
|
75
|
+
if ((resource = contentCatalog.getById(resourceId))?.pub) {
|
|
76
|
+
const { linkReferenceStyle, pubRoot, siteRoot } = assemblyModel
|
|
77
|
+
text ||= resource.asciidoc?.xreftext || rawTarget
|
|
78
|
+
if (siteRoot || linkReferenceStyle === 'relative') {
|
|
79
|
+
return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, escapeForInline)}${fragment && '#' + fragment}[${text}]`
|
|
80
|
+
}
|
|
81
|
+
if (doc) {
|
|
82
|
+
const msg = `Cannot create external ${family} reference in assembly because site URL is unknown: ${rawTarget}`
|
|
83
|
+
doc.logger.warn(doc.createLogMessage(msg, { source_location: sourceLocation }))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const linkAttrlist = text
|
|
87
|
+
? (~text.indexOf(',') ? `"${text}"` : text) + ',role=unresolved'
|
|
88
|
+
: (~target.indexOf(':') ? `${rawTarget},` : '') + 'role=unresolved'
|
|
89
|
+
return `link:${rawTarget}[${linkAttrlist}]`
|
|
90
|
+
}
|
|
91
|
+
if (fragment === resource.asciidoc.id) fragment = ''
|
|
92
|
+
if (
|
|
93
|
+
text &&
|
|
94
|
+
(assemblyModel.dropExplicitXrefText === 'always' ||
|
|
95
|
+
(assemblyModel.dropExplicitXrefText === 'if-redundant' && text === resource.title))
|
|
96
|
+
) {
|
|
97
|
+
text = ''
|
|
98
|
+
}
|
|
99
|
+
const componentVersionCtx = { name: ctx.component, version: ctx.version }
|
|
100
|
+
const { idLeader: refidLeader, id: refid } = generateScopedId(
|
|
101
|
+
resource.src,
|
|
102
|
+
componentVersionCtx,
|
|
103
|
+
idCoordinateSep,
|
|
104
|
+
idScopeSep,
|
|
105
|
+
idPrefix,
|
|
106
|
+
assemblyModel.filetype,
|
|
107
|
+
text.length > 0
|
|
108
|
+
)
|
|
109
|
+
return `xref:${fragment ? refidLeader + fragment : refid}[${text}]`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rewriteImageAttr (val, contentCatalog, assemblyModel, ctx, assets) {
|
|
113
|
+
const match = val.startsWith('image:') && /^image::?(.+?)\[(.*?)\]$/.exec(val)
|
|
114
|
+
if (!match) return
|
|
115
|
+
const newTarget = rewriteImageRef(match[1], contentCatalog, assemblyModel, ctx, assets)
|
|
116
|
+
return newTarget ? `image:${newTarget}[${match[2]}]` : val
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function rewriteInlineImages (line, contentCatalog, assemblyModel, ctx, assets, escapeForInline) {
|
|
120
|
+
return line.replace(/(?<![\\+])image:([^:\s[](?:[^[]*[^\s[])?)\[([^\]]*)\]/g, (m, target, attrlist) => {
|
|
121
|
+
const newTarget = rewriteImageRef(target, contentCatalog, assemblyModel, ctx, assets, escapeForInline)
|
|
122
|
+
return newTarget ? `image:${newTarget}[${attrlist}]` : m
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function rewriteImageRef (target, contentCatalog, assemblyModel, ctx, assets, escapeForInline = false) {
|
|
127
|
+
const image = isResourceRef(target) && contentCatalog.resolveResource(target, ctx, 'image', ['image'])
|
|
128
|
+
if (!image?.out) return
|
|
129
|
+
let newTarget
|
|
130
|
+
const { filetype, embedReferenceStyle, linkReferenceStyle, outDirname, pubRoot, siteRoot } = assemblyModel
|
|
131
|
+
if (filetype !== 'html') {
|
|
132
|
+
newTarget = resolveEmbedTarget(image, outDirname, embedReferenceStyle, escapeForInline)
|
|
133
|
+
assets.add(image)
|
|
134
|
+
} else if (siteRoot || linkReferenceStyle === 'relative') {
|
|
135
|
+
newTarget = resolveLinkTarget(image, siteRoot, pubRoot, linkReferenceStyle, escapeForInline, false)
|
|
136
|
+
if (linkReferenceStyle === 'relative') assets.add(image)
|
|
137
|
+
}
|
|
138
|
+
if (!newTarget) return
|
|
139
|
+
return newTarget
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function rewriteStyleAttribute (block, lines, idx, idLeader, replacementStyle = '') {
|
|
143
|
+
let prevLine = lines[idx - 1]
|
|
144
|
+
const char0 = prevLine?.charAt()
|
|
145
|
+
if (char0) {
|
|
146
|
+
if (
|
|
147
|
+
(char0 === '.' && /^\.\.?[^ \t.]/.test(prevLine)) ||
|
|
148
|
+
(char0 === '[' &&
|
|
149
|
+
prevLine.charAt(1) === '[' &&
|
|
150
|
+
/^\[\[(?:|[\p{Alpha}_:][\p{Alpha}0-9_\-:.]*(?:, *.+)?)\]\]$/u.test(prevLine))
|
|
151
|
+
) {
|
|
152
|
+
return rewriteStyleAttribute(block, lines, idx - 1, idLeader, replacementStyle)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let cellSpec
|
|
156
|
+
if (
|
|
157
|
+
char0 &&
|
|
158
|
+
(char0 === '[' || (block.getDocument().isNested() && (cellSpec = prevLine.match(/^([^[|]*)\| *(\[.+)/)))) &&
|
|
159
|
+
prevLine.charAt(prevLine.length - 1) === ']'
|
|
160
|
+
) {
|
|
161
|
+
if (cellSpec) {
|
|
162
|
+
prevLine = cellSpec[2]
|
|
163
|
+
cellSpec = cellSpec[1]
|
|
164
|
+
} else if (~prevLine.indexOf('id=')) {
|
|
165
|
+
prevLine = `[${prevLine.slice(1, -1).replace(NamedIdAttrRx, '')}]`
|
|
166
|
+
}
|
|
167
|
+
let rawStyle
|
|
168
|
+
const commaIdx = prevLine.indexOf(',')
|
|
169
|
+
if (~commaIdx) {
|
|
170
|
+
rawStyle = prevLine.slice(1, commaIdx)
|
|
171
|
+
if (~rawStyle.indexOf('=')) rawStyle = undefined
|
|
172
|
+
} else if (!~prevLine.indexOf('=')) {
|
|
173
|
+
rawStyle = prevLine.slice(1, prevLine.length - 1)
|
|
174
|
+
}
|
|
175
|
+
if (rawStyle) {
|
|
176
|
+
if (~rawStyle.indexOf('#')) {
|
|
177
|
+
prevLine = prevLine.replace(/#[^.%,\]]+/, `#${idLeader}${block.getId()}`)
|
|
178
|
+
if (replacementStyle) prevLine = prevLine.replace(/^[^#.%,\]]+/, `[${replacementStyle}`)
|
|
179
|
+
} else {
|
|
180
|
+
prevLine = `[${
|
|
181
|
+
replacementStyle ? rawStyle.replace(/^[^.%]*/, replacementStyle) : rawStyle
|
|
182
|
+
}#${idLeader}${block.getId()}${prevLine.slice(rawStyle.length + 1)}`
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
prevLine = `[${replacementStyle}#${idLeader}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.slice(1)}`
|
|
186
|
+
}
|
|
187
|
+
if (cellSpec) prevLine = `${cellSpec}|${prevLine}`
|
|
188
|
+
lines[idx - 1] = prevLine
|
|
189
|
+
} else {
|
|
190
|
+
lines.splice(idx, 0, `[${replacementStyle}#${idLeader}${block.getId()}]`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isResourceRef (str) {
|
|
195
|
+
return !(~str.indexOf(':') && (~str.indexOf('://') || (str.startsWith('data:') && ~str.indexOf(','))))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
rewriteXref,
|
|
200
|
+
rewriteXrefs,
|
|
201
|
+
rewriteImageAttr,
|
|
202
|
+
rewriteImageRef,
|
|
203
|
+
rewriteInlineImages,
|
|
204
|
+
rewriteStyleAttribute,
|
|
205
|
+
}
|
package/lib/util/rx.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/assembler",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
4
4
|
"description": "A JavaScript library that merges AsciiDoc content from multiple pages in an Antora site into assembly files and delegates to an exporter to convert those files to another format, such as PDF.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"./parse-resource-ref": "./lib/util/parse-resource-ref.js",
|
|
31
31
|
"./produce-assembly-file": "./lib/produce-assembly-file.js",
|
|
32
32
|
"./produce-assembly-files": "./lib/produce-assembly-files.js",
|
|
33
|
-
"./select-mutable-attributes": "./lib/select-mutable-attributes.js"
|
|
33
|
+
"./select-mutable-attributes": "./lib/select-mutable-attributes.js",
|
|
34
|
+
"./package.json": "./package.json"
|
|
34
35
|
},
|
|
35
36
|
"imports": {
|
|
36
37
|
"#asciidoctor-log-adapter": "./adapters/asciidoctor/jsonl-logger.rb",
|