@antora/assembler 1.0.0-beta.18 → 1.0.0-beta.19

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.
@@ -5,10 +5,12 @@ const fs = require('node:fs')
5
5
  const { promises: fsp } = fs
6
6
  const LazyReadable = require('./util/lazy-readable')
7
7
  const loadConfig = require('./load-config')
8
+ const logCommand = require('./log-command')
8
9
  const ospath = require('node:path')
9
10
  const { posix: path } = ospath
10
11
  const produceAssemblyFiles = require('./produce-assembly-files')
11
12
  const PromiseQueue = require('./util/promise-queue')
13
+ const runCommand = require('@antora/run-command-helper')
12
14
  const { stringify: toJSON } = JSON
13
15
 
14
16
  const invariably = { new: () => ({}), void: () => undefined }
@@ -16,17 +18,6 @@ const PACKAGE_NAME = require('../package.json').name
16
18
  const NEWLINE_RX = /(?:\r?\n)+/
17
19
 
18
20
  async function assembleContent (playbook, contentCatalog, converter, { configSource, navigationCatalog }) {
19
- const context = isBound(this)
20
- ? this
21
- : {
22
- getFunctions: invariably.new,
23
- getLogger: invariably.void,
24
- getVariables: invariably.new,
25
- }
26
- const assemblerConfig = await loadConfig.call(context, playbook, configSource)
27
- if (assemblerConfig.enabled === false) return []
28
- const generatorFunctions = context.getFunctions()
29
- const { loadAsciiDoc = require('@antora/asciidoc-loader') } = generatorFunctions
30
21
  const {
31
22
  convert = converter,
32
23
  getDefaultCommand,
@@ -36,13 +27,24 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
36
27
  mediaType: targetMediaType,
37
28
  loggerName = PACKAGE_NAME,
38
29
  } = converter ?? {}
30
+ const assemblerConfig = await loadConfig.call(this, playbook, configSource, '-' + targetBackend)
31
+ if (assemblerConfig.enabled === false) return []
32
+ const context = isBound(this)
33
+ ? this
34
+ : {
35
+ getFunctions: invariably.new,
36
+ getLogger: invariably.void,
37
+ getVariables: invariably.new,
38
+ }
39
+ const generatorFunctions = context.getFunctions()
40
+ const { loadAsciiDoc = require('@antora/asciidoc-loader') } = generatorFunctions
39
41
  const { assembly: assemblyConfig, build: buildConfig } = assemblerConfig
40
42
  assemblyConfig.embedReferenceStyle = embedReferenceStyle
41
43
  const { profile = targetBackend } = assemblyConfig
42
44
  const intrinsicAttributes = { 'loader-assembler': '' }
43
45
  buildConfig.cwd ??= process.cwd()
44
46
  if (profile) {
45
- buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler-${profile}`)
47
+ buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), `build/assembler/${profile}`)
46
48
  intrinsicAttributes[`assembler-profile-${profile}`] = ''
47
49
  intrinsicAttributes['assembler-profile'] = profile
48
50
  if (targetBackend) {
@@ -50,14 +52,14 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
50
52
  intrinsicAttributes['assembler-backend'] = targetBackend
51
53
  }
52
54
  } else {
53
- buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), 'build/assembler')
55
+ buildConfig.dir ??= ospath.join(playbook.dir ?? process.cwd(), 'build/assembler/_')
54
56
  }
55
57
  if (targetExtname) {
56
58
  const targetFiletype = targetExtname.slice(1)
57
59
  intrinsicAttributes[`assembler-filetype-${targetFiletype}`] = ''
58
60
  intrinsicAttributes['assembler-filetype'] = targetFiletype
59
61
  }
60
- Object.assign(assemblerConfig.assembly.attributes, intrinsicAttributes)
62
+ Object.assign(assemblyConfig.attributes, intrinsicAttributes)
61
63
  const assemblyFiles = produceAssemblyFiles(
62
64
  loadAsciiDoc,
63
65
  contentCatalog,
@@ -70,14 +72,15 @@ async function assembleContent (playbook, contentCatalog, converter, { configSou
70
72
  }
71
73
  const { publishSite: publishFiles = require('@antora/site-publisher') } = generatorFunctions
72
74
  await prepareWorkspace(publishFiles, assemblyFiles, buildConfig)
75
+ const helpers = { logCommand: logCommand.bind(context, loggerName), runCommand }
73
76
  const boundConvert = convert.bind(context)
74
77
  return new PromiseQueue({ concurrency: buildConfig.processLimit })
75
78
  .add(
76
79
  assemblyFiles.map((doc) => async () => {
77
80
  const relativeToOutput = embedReferenceStyle === 'output-relative'
78
- const convertAttributes = prepareConvertAttributes(doc, targetExtname, relativeToOutput, buildConfig)
81
+ const convertAttributes = prepareConvertAttributes(doc, targetExtname, relativeToOutput, assemblerConfig)
79
82
  if (buildConfig.mkdirs) await fsp.mkdir(convertAttributes.outdir, { recursive: true, force: true })
80
- return boundConvert(doc, convertAttributes, buildConfig).then((result) => {
83
+ return boundConvert(doc, convertAttributes, buildConfig, helpers).then((result) => {
81
84
  const fileOrContents = resolveFileOrContents.call(context, result, convertAttributes, buildConfig, loggerName)
82
85
  return coerceToExportFormat(doc, targetBackend, targetExtname, targetMediaType, fileOrContents)
83
86
  })
@@ -178,21 +181,21 @@ function generateSelectAssemblyProfile (context, contentCatalog, base, intrinsic
178
181
  }
179
182
  }
180
183
 
181
- function prepareConvertAttributes (doc, targetExtname, relativeToOutput, buildConfig) {
184
+ function prepareConvertAttributes (doc, targetExtname, relativeToOutput, assemblerConfig) {
182
185
  const {
183
186
  asciidoc: { attributes: docAttributes } = { attributes: {} },
184
187
  extname: docfilesuffix,
185
188
  path: reldocfile,
186
189
  src: { family, relative },
187
190
  } = doc
188
- const { cwd = process.cwd(), dir = cwd } = buildConfig
191
+ const { cwd = process.cwd(), dir = cwd } = assemblerConfig.build
189
192
  const docname = family + '$' + relative.slice(0, relative.length - docfilesuffix.length)
190
193
  const docfile = ospath.join(dir, reldocfile)
191
194
  const outdir = ospath.dirname(docfile)
192
195
  const docdir = relativeToOutput ? dir : outdir
193
196
  const imagesdir = ''
194
197
  const outfile = docfile.slice(0, docfile.length - docfilesuffix.length) + targetExtname
195
- const attributes = Object.assign({}, docAttributes, {
198
+ const attributes = Object.assign({ revdate: `${assemblerConfig.assembly.revdate}@` }, docAttributes, {
196
199
  docdir,
197
200
  docfile,
198
201
  docfilesuffix,
@@ -256,7 +259,13 @@ function prepareWorkspace (publishFiles, assemblyFiles, buildConfig) {
256
259
  outPaths.add(asset.out.path)
257
260
  }
258
261
  }
259
- if (keepSource) files.push(...assemblyFiles.map((file) => Object.assign(file, { out: { path: file.path } })))
262
+ if (keepSource) {
263
+ for (const file of assemblyFiles) {
264
+ file.src.contents = file.contents
265
+ file.out = { path: file.path }
266
+ files.push(file)
267
+ }
268
+ }
260
269
  return publishFiles({ output: { clean, dir } }, { getFiles: () => files })
261
270
  }
262
271
 
package/lib/configure.js CHANGED
@@ -10,9 +10,7 @@ function internalConfigure (converter, config = {}, providers = {}) {
10
10
  this.updateVariables({ assemblerProfiles: getAssemblerProfiles(contentCatalog) })
11
11
  })
12
12
 
13
- this.once('beforeProcess', ({ siteAsciiDocConfig }) => {
14
- siteAsciiDocConfig.keepSource = true
15
- })
13
+ this.once('beforeProcess', enableKeepSource)
16
14
 
17
15
  this.once('navigationBuilt', async ({ playbook, contentCatalog }) => {
18
16
  const { assembleContent = require('./assemble-content'), ...assembleContentConfig } = providers
@@ -30,10 +28,23 @@ function internalConfigure (converter, config = {}, providers = {}) {
30
28
  })
31
29
  }
32
30
 
31
+ function enableKeepSource ({ siteAsciiDocConfig }) {
32
+ if (siteAsciiDocConfig.keepSource instanceof Boolean) return
33
+ siteAsciiDocConfig.keepSource = Object.assign(new Boolean(true), { oldValue: siteAsciiDocConfig.keepSource })
34
+ this.once('navigationBuilt', restoreKeepSource)
35
+ }
36
+
37
+ function restoreKeepSource ({ siteAsciiDocConfig, contentCatalog }) {
38
+ if (!(siteAsciiDocConfig.keepSource instanceof Boolean)) return
39
+ if ((siteAsciiDocConfig.keepSource = siteAsciiDocConfig.keepSource.oldValue)) return
40
+ contentCatalog.getPages((page) => delete page.src.contents)
41
+ }
42
+
33
43
  function getAssemblerProfiles (contentCatalog, assemblerProfiles = new Map()) {
34
44
  contentCatalog.getComponents().forEach((component) => {
35
45
  component.versions.forEach((componentVersion) => {
36
- const source = componentVersion.nav?.origin
46
+ const source =
47
+ componentVersion.nav?.origin ?? componentVersion.origins?.find((it) => it.descriptor?.ext?.assembler)
37
48
  const assemblerConfig = getAssemblerConfigFromDescriptor(source?.descriptor)
38
49
  if (!assemblerConfig) return
39
50
  const componentVersionRef = `${componentVersion.version}@${componentVersion.name}`
@@ -5,8 +5,6 @@ const fsp = require('node:fs/promises')
5
5
  const os = require('node:os')
6
6
  const yaml = require('js-yaml')
7
7
 
8
- const PACKAGE_NAME = require('../package.json').name
9
-
10
8
  const ASSEMBLY_KEYS = [
11
9
  'rootLevel',
12
10
  'insertStartPage',
@@ -15,18 +13,24 @@ const ASSEMBLY_KEYS = [
15
13
  'dropExplicitXrefText',
16
14
  ]
17
15
  const CAMEL_CASE_STOP_PATHS = ['asciidoc.attributes', 'assembly.attributes']
16
+ const PACKAGE_NAME = require('../package.json').name
18
17
 
19
- function loadConfig (playbook, configSource) {
18
+ function loadConfig (playbook, configSource, preferredQualifier = '') {
20
19
  let resolvedConfigSource
21
20
  return (
22
21
  configSource?.constructor === Object
23
22
  ? Promise.resolve(configSource)
24
- : fsp
25
- .access((resolvedConfigSource = expandPath(configSource ?? './antora-assembler.yml', { dot: playbook.dir })))
26
- .then(
27
- () => true,
28
- () => false
29
- )
23
+ : fileExists(
24
+ (resolvedConfigSource = expandPath(configSource ?? `./antora-assembler${preferredQualifier}.yml`, {
25
+ dot: playbook.dir,
26
+ }))
27
+ )
28
+ .then((exists) => {
29
+ if (exists || configSource || !preferredQualifier) return exists
30
+ return fileExists(
31
+ (resolvedConfigSource = resolvedConfigSource.slice(0, -(preferredQualifier.length + 4)) + '.yml')
32
+ )
33
+ })
30
34
  .then((exists) => {
31
35
  if (!exists) {
32
36
  let logger
@@ -134,6 +138,13 @@ function camelCaseKeys (o, stopPaths = [], p = undefined) {
134
138
  return accum
135
139
  }
136
140
 
141
+ function fileExists (path) {
142
+ return fsp.access(path).then(
143
+ () => true,
144
+ () => false
145
+ )
146
+ }
147
+
137
148
  function getLocalDate (now = new Date()) {
138
149
  return new Date(now - now.getTimezoneOffset() * 60000).toISOString().split('T')[0]
139
150
  }
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ function logCommand (loggerName, command, file, convertAttributes, attributeOptionFlagOrArgs) {
4
+ const logger = this?.getLogger(loggerName)
5
+ if (!logger?.isLevelEnabled('debug')) return
6
+ const { docfile, 'assembler-filetype': filetype } = convertAttributes
7
+ const args = Array.isArray(attributeOptionFlagOrArgs)
8
+ ? attributeOptionFlagOrArgs
9
+ : attributeOptionFlagOrArgs
10
+ ? convertAttributes.toArgs(attributeOptionFlagOrArgs, command)
11
+ : []
12
+ const ctx = { command: [command].concat(args).join(' '), file: { path: docfile } }
13
+ const msg = `Running external command to export assembly in %s to %s: %s`
14
+ const componentVersionStr = file.src.version ? `${file.src.version}@${file.src.component}` : file.src.component
15
+ logger.debug(ctx, msg, componentVersionStr, filetype, file.src.relative)
16
+ }
17
+
18
+ module.exports = logCommand
@@ -4,15 +4,28 @@ const createAsciiDocFile = require('./util/create-asciidoc-file')
4
4
  const createResourceKey = require('./util/create-resource-key')
5
5
  const generateScopedId = require('./util/generate-scoped-id')
6
6
  const { resolveLinkTarget } = require('./util/resolver')
7
- const { rewriteXrefs, rewriteImageRef, rewriteInlineImages, rewriteStyleAttribute } = require('./util/rewriter')
8
- const { NamedIdAttrRx } = require('./util/rx')
7
+ const {
8
+ rewriteXrefs,
9
+ rewriteImageAttr,
10
+ rewriteImageRef,
11
+ rewriteInlineImages,
12
+ rewriteStyleAttribute,
13
+ } = require('./util/rewriter')
9
14
  const sanitize = require('./util/sanitize')
10
15
  const unconvertInlineAsciiDoc = require('./util/unconvert-inline-asciidoc')
11
16
 
12
- const AttributeEntryRx = /^:([^:-][^:]*):(?: .*)?$/
13
- const BuiltInNamedEntities = { amp: '&', apos: "'", gt: '>', lt: '<', nbsp: ' ', quot: '"' }
14
- const CharRefRx = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d{2,6})|x([a-z\d]{2,5})));/g
15
- const DiscardAttributes = 'doctype leveloffset assembly-navtitle assembly-style underscore'.split(' ')
17
+ const ATTR_ENTRY_RX = /^:(!?[\p{Alpha}0-9_][^:]*):(?: |$)/u
18
+ const BUILT_IN_NAMED_ENTITIES = { amp: '&', apos: "'", gt: '>', lt: '<', nbsp: ' ', quot: '"' }
19
+ const CHAR_REF_RX = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d{2,6})|x([a-z\d]{2,5})));/g
20
+ const DISCARD_ATTRIBUTE_NAMES = [
21
+ 'doctype',
22
+ 'leveloffset',
23
+ 'assembly-header-attributes',
24
+ 'assembly-navtitle',
25
+ 'assembly-style',
26
+ 'underscore',
27
+ ]
28
+ const { NAMED_ID_ATTR_RX } = require('./util/rx')
16
29
 
17
30
  function produceAssemblyFile (
18
31
  loadAsciiDoc,
@@ -27,12 +40,28 @@ function produceAssemblyFile (
27
40
  const pagesByUrl = files.reduce((map, it) => (it.src.family === 'page' ? map.set(it.pub.url, it) : map), new Map())
28
41
  if (outline.urlType === 'internal' && !pagesByUrl.get(outline.url) && !(outline.items || []).length) return
29
42
  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
- )
43
+ const { rootLevel, xmlIds } = assemblyModel
44
+ const idSeparators = {
45
+ prefix:
46
+ 'assembler-idprefix' in asciidocConfig.attributes ? (asciidocConfig.attributes['assembler-idprefix'] ?? '') : '_',
47
+ scope: xmlIds ? '---' : ':::',
48
+ coordinate: xmlIds ? '----' : ':',
49
+ generateIdFromTitle: generateIdFromTitle.bind(
50
+ loadAsciiDoc(
51
+ { contents: Buffer.alloc(0), src: { family: 'page', relative: 'generate-id-from-title.adoc' } },
52
+ undefined,
53
+ asciidocConfig
54
+ )
55
+ ),
56
+ }
57
+ const { name: component, version } = componentVersion
58
+ asciidocConfig = prepareAsciiDocConfig(
59
+ contentCatalog,
60
+ { component, version },
61
+ pagesInOutline,
62
+ asciidocConfig,
63
+ assemblyModel,
64
+ idSeparators
36
65
  )
37
66
  const buffer = mergeAsciiDoc(
38
67
  loadAsciiDoc,
@@ -42,29 +71,51 @@ function produceAssemblyFile (
42
71
  outline,
43
72
  files,
44
73
  pagesInOutline,
74
+ idSeparators,
45
75
  asciidocConfig,
46
76
  mutableAttributes,
47
77
  assemblyModel
48
78
  )
49
- const rootLevel = assemblyModel.rootLevel
50
79
  const stem = rootLevel === 0 ? 'index' : generateSlug(buffer.navtitle)
51
- const downloadStem = [componentVersion.name, componentVersion.version, rootLevel === 0 ? '' : stem]
52
- .filter((it) => it)
53
- .join('-')
80
+ const downloadStem = [component, version, rootLevel === 0 ? '' : stem].filter((it) => it).join('-')
54
81
  return createAsciiDocFile(contentCatalog, {
55
82
  asciidoc: asciidocConfig,
56
83
  assembler: { assembled: pagesInOutline.assembled, downloadStem, rootLevel },
57
84
  contents: Buffer.from(buffer.join('\n') + '\n'),
58
- src: {
59
- component: componentVersion.name,
60
- version: componentVersion.version,
61
- module: 'ROOT',
62
- family: 'export',
63
- relative: stem + '.adoc',
64
- },
85
+ src: { component, version, module: 'ROOT', family: 'export', relative: stem + '.adoc' },
65
86
  })
66
87
  }
67
88
 
89
+ function prepareAsciiDocConfig (contentCatalog, ctx, pagesInOutline, asciidocConfig, assemblyModel, idSeparators) {
90
+ let attributesModified
91
+ const configShared = asciidocConfig.$shared
92
+ const sharedAttributes = asciidocConfig.attributes
93
+ if (!configShared) {
94
+ if (configShared == null) {
95
+ const assets = pagesInOutline.assembled.assets
96
+ for (const [name, val] of Object.entries(sharedAttributes)) {
97
+ if (!(typeof val === 'string' && ~val.indexOf(':'))) continue
98
+ let newVal
99
+ if (!name.endsWith('-image') || !(newVal = rewriteImageAttr(val, contentCatalog, assemblyModel, ctx, assets))) {
100
+ if (~(newVal = val).indexOf('image:')) {
101
+ newVal = rewriteInlineImages(newVal, contentCatalog, assemblyModel, ctx, assets)
102
+ }
103
+ }
104
+ if (newVal !== val) sharedAttributes[name] = newVal
105
+ }
106
+ }
107
+ for (const [name, val] of Object.entries(sharedAttributes)) {
108
+ if (!(typeof val === 'string' && ~val.indexOf(':'))) continue
109
+ if (~val.indexOf('xref:')) {
110
+ const newVal = rewriteXrefs(val, contentCatalog, assemblyModel, ctx, false, pagesInOutline, idSeparators)
111
+ if (newVal !== val) (attributesModified ??= {})[name] = newVal
112
+ }
113
+ }
114
+ if (configShared == null) Object.defineProperty(asciidocConfig, '$shared', { value: attributesModified == null })
115
+ }
116
+ return Object.assign({}, asciidocConfig, { attributes: Object.assign({}, sharedAttributes, attributesModified) })
117
+ }
118
+
68
119
  function buildAsciiDocHeader (componentVersion, navtitle, assemblyModel) {
69
120
  const doctype = assemblyModel.doctype ?? 'book'
70
121
  const navtitlePlain = sanitize(navtitle)
@@ -107,6 +158,7 @@ function mergeAsciiDoc (
107
158
  outlineEntry,
108
159
  files,
109
160
  pagesInOutline,
161
+ idSeparators,
110
162
  asciidocConfig,
111
163
  mutableAttributes,
112
164
  assemblyModel,
@@ -123,18 +175,14 @@ function mergeAsciiDoc (
123
175
  let navtitlePlain = sanitize(navtitle)
124
176
  let navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
125
177
  const { items = [], unresolved, urlType, url } = outlineEntry
126
- const { doctype, filetype, linkReferenceStyle, pubRoot, siteRoot, xmlIds, logger } = assemblyModel
178
+ const { doctype, filetype, linkReferenceStyle, pubRoot, siteRoot, logger } = assemblyModel
127
179
  const assembled = pagesInOutline.assembled
128
180
  // FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
129
181
  const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
130
182
  const atDocumentRoot = !buffer.inBody
183
+ const isRootPage = page && atDocumentRoot && !level
131
184
  const atBookRoot = atDocumentRoot && !level && doctype === 'book' && (supportsParts = true)
132
185
  const hasItems = items.length > 0
133
- const idPrefix =
134
- asciidocConfig.attributes['assembler-idprefix'] ?? ('assembler-idprefix' in asciidocConfig.attributes ? '' : '_')
135
- const idSeparator = xmlIds ? '-' : ':'
136
- const idScopeSeparator = idSeparator.repeat(3)
137
- const idCoordinateSeparator = idSeparator === '-' ? '----' : idSeparator
138
186
  if (page && !assembled.pages.has(page)) {
139
187
  const contents = page.src.contents
140
188
  if (contents == null) {
@@ -155,6 +203,7 @@ function mergeAsciiDoc (
155
203
  },
156
204
  })
157
205
  : doc.getLogger()
206
+ doc.source_header_attributes ??= doc.parent.$to_h()
158
207
  if (doc.hasAttribute('assembly-navtitle')) {
159
208
  navtitleAsciiDoc = doc.getAttribute('assembly-navtitle')
160
209
  navtitlePlain = sanitize((navtitle = doc.$apply_reftext_subs(navtitleAsciiDoc)))
@@ -168,31 +217,21 @@ function mergeAsciiDoc (
168
217
  }
169
218
  }
170
219
  }
171
- if (atDocumentRoot) {
172
- const authors = doc.getAuthors()
173
- if (authors.length) {
174
- const authorLine = authors
175
- .map((author) => {
176
- const email = author.getEmail()
177
- return email ? `${author.getName()} <${author.getEmail()}>` : author.getName()
178
- })
179
- .join('; ')
180
- buffer.splice(1, 0, authorLine)
181
- }
182
- }
183
220
  // NOTE: in Antora, docname is relative src path from module without file extension
184
221
  const docname = doc.getAttribute('docname')
185
- const { idLeader: idScopeLeader, id: idScope } = generateScopedId(
186
- page.src,
187
- componentVersion,
188
- idCoordinateSeparator,
189
- idScopeSeparator,
190
- idPrefix,
191
- filetype
192
- )
222
+ const pageId = generateScopedId(page.src, componentVersion, idSeparators, filetype)
223
+ const pageIdLeader = pageId + idSeparators.scope
193
224
  let pageFragment = ''
194
225
  let pageRoles = ''
195
226
  let pageStyle = doc.getAttribute('assembly-style', '')
227
+ if (
228
+ !pageStyle &&
229
+ atBookRoot &&
230
+ doc.isAttribute('preface-title') &&
231
+ !('assembly-style' in doc.source_header_attributes.$$smap)
232
+ ) {
233
+ pageStyle = 'preface'
234
+ }
196
235
  let part
197
236
  if ((part = pageStyle === 'part')) {
198
237
  pageStyle = ''
@@ -204,7 +243,10 @@ function mergeAsciiDoc (
204
243
  let nextSectionLevel = 1
205
244
  const lines = doc.getSourceLines()
206
245
  const ignoreLines = []
207
- buffer.inBody = true
246
+ if (!buffer.inBody) {
247
+ buffer.inBody = true
248
+ buffer.endHeaderIdx = buffer.length - 1
249
+ }
208
250
  buffer.push('')
209
251
  buffer.push(`:docname: ${docname}`)
210
252
  if (component !== lastComponentVersion.name) {
@@ -228,7 +270,63 @@ function mergeAsciiDoc (
228
270
  buffer.push(`:page-origin-refname: ${origin.branch || origin.tag}`)
229
271
  buffer.push(`:page-origin-reftype: ${origin.branch ? 'branch' : 'tag'}`)
230
272
  buffer.push(`:page-origin-refhash: ${origin.worktree ? '(worktree)' : origin.refhash}`)
231
- if (doc.hasHeader()) pageRoles = processDocumentHeader(doc, lines, buffer, ignoreLines)
273
+ if (doc.hasHeader()) {
274
+ for (const entry of processDocumentHeader(doc, lines, ignoreLines, isRootPage)) {
275
+ if (entry.type === 'author_line') {
276
+ buffer.splice(1, 0, entry.lines[0])
277
+ buffer.endHeaderIdx += 1
278
+ } else if (entry.type === 'attribute_entry') {
279
+ const name = entry.name
280
+ if (!entry.negated && !doc.isAttributeLocked(name)) {
281
+ let val, newVal
282
+ if (
283
+ name.endsWith('-image') &&
284
+ (val = doc.getAttribute(name)) &&
285
+ (newVal = rewriteImageAttr(val, contentCatalog, assemblyModel, page.src, assembled.assets))
286
+ ) {
287
+ if (newVal !== val) entry.lines = [`:${name}: ${newVal}`]
288
+ } else if (~(val = entry.lines.join('\n').slice(name.length + 3)).indexOf(':')) {
289
+ newVal = val
290
+ if (~newVal.indexOf('image:')) {
291
+ newVal = rewriteInlineImages(
292
+ newVal,
293
+ contentCatalog,
294
+ assemblyModel,
295
+ page.src,
296
+ assembled.assets,
297
+ false,
298
+ doc
299
+ )
300
+ }
301
+ if (~newVal.indexOf('xref:')) {
302
+ newVal = rewriteXrefs(
303
+ newVal,
304
+ contentCatalog,
305
+ assemblyModel,
306
+ page.src,
307
+ false,
308
+ pagesInOutline,
309
+ idSeparators,
310
+ pageIdLeader,
311
+ doc,
312
+ { file: relative, lineno: entry.lineno }
313
+ )
314
+ }
315
+ if (newVal !== val) entry.lines = `:${entry.name}: ${newVal}`.split('\n')
316
+ }
317
+ }
318
+ if (entry.promote) {
319
+ buffer.splice(buffer.endHeaderIdx + 1, 0, ...entry.lines)
320
+ buffer.endHeaderIdx += entry.lines.length
321
+ } else {
322
+ buffer.push(...entry.lines)
323
+ }
324
+ } else {
325
+ buffer.push(...entry.lines)
326
+ }
327
+ }
328
+ pageRoles = doc.getRoles().reduce((accum, role) => `${accum}.${role}`, '')
329
+ }
232
330
  let heading
233
331
  if (pageStyle && (part && level === 1 ? (level = 0) : level) === 0) {
234
332
  let htitleAsciiDoc = navtitleAsciiDoc
@@ -245,10 +343,10 @@ function mergeAsciiDoc (
245
343
  assemblyModel = Object.assign({}, assemblyModel, { sectionMergeStrategy: 'discrete' })
246
344
  } else {
247
345
  if (atDocumentRoot) {
248
- pageFragment = `#${idScopeLeader}${doc.getId() ?? pageStyle}`
249
- buffer.unshift(`[#${idScope}]`)
346
+ pageFragment = `#${pageIdLeader}${doc.getId() ?? pageStyle}`
347
+ buffer.unshift(`[#${pageId}]`)
250
348
  } else {
251
- pageFragment = `#${idScope}`
349
+ pageFragment = `#${pageId}`
252
350
  }
253
351
  heading = { title: htitleAsciiDoc, level: part ? 1 : 2 }
254
352
  nextSectionLevel++
@@ -257,7 +355,7 @@ function mergeAsciiDoc (
257
355
  if (atDocumentRoot && navtitlePlain === componentVersion.title) {
258
356
  level--
259
357
  } else {
260
- pageFragment = `#${idScope}`
358
+ pageFragment = `#${pageId}`
261
359
  if (part && level === 1) level--
262
360
  if ((heading = { title: navtitleAsciiDoc, level: level + 1 }).level > 6) {
263
361
  Object.assign(heading, { level: 6, style: `discrete.h${heading.level}` })
@@ -268,7 +366,7 @@ function mergeAsciiDoc (
268
366
  buffer.push(`[${heading.style ?? pageStyle}${pageFragment}${pageRoles}]`)
269
367
  buffer.push(`${'='.repeat(heading.level)} ${heading.title}`)
270
368
  } else if (atDocumentRoot) {
271
- buffer.unshift(`[#${idScope}]`)
369
+ buffer.unshift(`[#${pageId}]`)
272
370
  }
273
371
  let enclosed
274
372
  if (assemblyModel.sectionMergeStrategy === 'enclose' && hasItems && doc.hasSections()) {
@@ -278,7 +376,7 @@ function mergeAsciiDoc (
278
376
  //if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
279
377
  const overviewTitle = doc.getAttribute('overview-title', 'Overview')
280
378
  buffer.push('')
281
- const syntheticId = pagesInOutline.generateIdFromTitle(navtitleAsciiDoc, idCoordinateSeparator)
379
+ const syntheticId = idSeparators.generateIdFromTitle(navtitleAsciiDoc, idSeparators)
282
380
  let hlevel = level + 2
283
381
  if (hlevel > 6) {
284
382
  const blockStyle = `discrete.h${hlevel}`
@@ -363,7 +461,7 @@ function mergeAsciiDoc (
363
461
  if (
364
462
  line.charAt() === ':' &&
365
463
  ~line.indexOf(':', 2) &&
366
- (line.match(AttributeEntryRx) || ['', ''])[1].replace('!', '') === 'leveloffset'
464
+ (line.match(ATTR_ENTRY_RX) || ['', ''])[1].replace('!', '') === 'leveloffset'
367
465
  ) {
368
466
  if (lines[idx - 1] === '') lines[idx - 1] = undefined
369
467
  lines[idx] = undefined
@@ -375,13 +473,13 @@ function mergeAsciiDoc (
375
473
  if (!refs['$key?'](refid) && (~refid.indexOf(' ') || refid.toLowerCase() !== refid)) {
376
474
  if ((refid = doc.$resolve_id(refid))['$nil?']()) return m
377
475
  }
378
- return `<<${idScopeLeader}${refid}${text ? ',' + text : ''}>>`
476
+ return `<<${pageIdLeader}${refid}${text ? ',' + text : ''}>>`
379
477
  })
380
478
  }
381
479
  // NOTE: the next check takes care of inline and block anchors
382
480
  if (~line.indexOf('[[')) {
383
481
  line = line.replace(/\[\[([\p{Alpha}_:][\p{Alpha}0-9_\-:.]*)(|, *.+?)\]\]/gu, (_, refid, text) => {
384
- return `[[${idScopeLeader}${refid}${text}]]`
482
+ return `[[${pageIdLeader}${refid}${text}]]`
385
483
  })
386
484
  }
387
485
  if (~line.indexOf('xref:')) {
@@ -391,17 +489,15 @@ function mergeAsciiDoc (
391
489
  assemblyModel,
392
490
  page.src,
393
491
  true,
394
- idCoordinateSeparator,
395
- idScopeSeparator,
396
- idPrefix,
397
- idScopeLeader,
398
492
  pagesInOutline,
493
+ idSeparators,
494
+ pageIdLeader,
399
495
  doc,
400
496
  { file: relative, lineno: idx + 1 }
401
497
  )
402
498
  }
403
499
  if (~line.indexOf('image:') && !line.startsWith('image::')) {
404
- line = rewriteInlineImages(line, contentCatalog, assemblyModel, page.src, assembled.assets, true)
500
+ line = rewriteInlineImages(line, contentCatalog, assemblyModel, page.src, assembled.assets, true, doc)
405
501
  }
406
502
  lines[idx] = line
407
503
  }
@@ -427,7 +523,7 @@ function mergeAsciiDoc (
427
523
  return '='.repeat(targetMarkerLength) + ' ' + rest
428
524
  })
429
525
  // NOTE: ID will be undefined if sectids are turned off
430
- if (block.getId()) rewriteStyleAttribute(block, lines, idx, idScopeLeader, blockStyle)
526
+ if (block.getId()) rewriteStyleAttribute(block, lines, idx, pageIdLeader, blockStyle)
431
527
  } else {
432
528
  if (context === 'image') {
433
529
  let line = lines[idx] || ''
@@ -456,14 +552,14 @@ function mergeAsciiDoc (
456
552
  // nested document
457
553
  idx = (block.getHeader().getLineNumber() || idx + 1) - 1
458
554
  }
459
- if (block.getId()) rewriteStyleAttribute(block, lines, idx, idScopeLeader)
555
+ if (block.getId()) rewriteStyleAttribute(block, lines, idx, pageIdLeader)
460
556
  }
461
557
  })
462
558
  safePush(
463
559
  buffer,
464
560
  lines.filter((it) => it !== undefined)
465
561
  )
466
- const attributeEntries = Object.entries(doc.source_header_attributes?.$$smap || {})
562
+ const attributeEntries = Object.entries(doc.source_header_attributes.$$smap)
467
563
  if (attributeEntries.length) {
468
564
  const resolvedAttributeEntries = attributeEntries.reduce(
469
565
  (accum, [name, val]) => {
@@ -475,7 +571,7 @@ function mergeAsciiDoc (
475
571
  } else if (val !== initialVal) {
476
572
  accum.push(`:${name}:${initialVal ? ' ' + initialVal : ''}`)
477
573
  }
478
- } else if (!(val == null || doc.isAttributeLocked(name) || DiscardAttributes.includes(name))) {
574
+ } else if (!(val == null || doc.isAttributeLocked(name) || DISCARD_ATTRIBUTE_NAMES.includes(name))) {
479
575
  accum.push(`:!${name}:`)
480
576
  }
481
577
  return accum
@@ -491,7 +587,7 @@ function mergeAsciiDoc (
491
587
  } else {
492
588
  buffer.inBody = true
493
589
  buffer.push('')
494
- const syntheticId = pagesInOutline.generateIdFromTitle(navtitleAsciiDoc, idCoordinateSeparator)
590
+ const syntheticId = idSeparators.generateIdFromTitle(navtitleAsciiDoc, idSeparators)
495
591
  let sectionTitle = navtitleAsciiDoc
496
592
  if (urlType === 'external') {
497
593
  sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
@@ -499,15 +595,7 @@ function mergeAsciiDoc (
499
595
  const resource = files.find((it) => it.pub.url === url)
500
596
  if (resource) {
501
597
  if (resource.src.family === 'page' && pagesInOutline.has(resource.pub.url)) {
502
- const { id: refid } = generateScopedId(
503
- resource.src,
504
- componentVersion,
505
- idCoordinateSeparator,
506
- idScopeSeparator,
507
- idPrefix,
508
- filetype,
509
- true
510
- )
598
+ const refid = generateScopedId(resource.src, componentVersion, idSeparators, filetype, true)
511
599
  sectionTitle = `xref:${refid}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
512
600
  } else if (siteRoot) {
513
601
  sectionTitle = `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, true)}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
@@ -524,7 +612,6 @@ function mergeAsciiDoc (
524
612
  buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
525
613
  }
526
614
  }
527
-
528
615
  if (hasItems) {
529
616
  const nextLevel = level + 1
530
617
  // NOTE: drop first child if same as parent; should we keep if content is different?
@@ -540,6 +627,7 @@ function mergeAsciiDoc (
540
627
  item,
541
628
  files,
542
629
  pagesInOutline,
630
+ idSeparators,
543
631
  asciidocConfig,
544
632
  mutableAttributes,
545
633
  assemblyModel,
@@ -552,11 +640,32 @@ function mergeAsciiDoc (
552
640
  return buffer
553
641
  }
554
642
 
555
- function processDocumentHeader (doc, lines, buffer, ignoreLines) {
643
+ function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
644
+ const entries = []
645
+ let assemblyHeaderAttributes = isRootPage && doc.getAttribute('assembly-header-attributes', '%authors')
646
+ assemblyHeaderAttributes = new Set(assemblyHeaderAttributes ? assemblyHeaderAttributes.split(/, */) : undefined)
647
+ if (assemblyHeaderAttributes.size) {
648
+ if (assemblyHeaderAttributes.has('%authors')) {
649
+ const authors = doc.getAuthors()
650
+ if (authors.length) {
651
+ entries.push({
652
+ type: 'author_line',
653
+ promote: true,
654
+ lines: [
655
+ authors
656
+ .map((author) => (author.getEmail() ? `${author.getName()} <${author.getEmail()}>` : author.getName()))
657
+ .join('; '),
658
+ ],
659
+ })
660
+ }
661
+ assemblyHeaderAttributes.delete('%authors')
662
+ }
663
+ const headerAttributes = doc.source_header_attributes
664
+ for (const name of assemblyHeaderAttributes) headerAttributes.$delete(name)
665
+ }
556
666
  const doctitleIdx = doc.getHeader().getLineNumber() - 1
557
667
  const end = doc.getBlocks()[0]?.getLineNumber() ?? lines.length
558
- let belowDoctitle
559
- let open
668
+ let belowDoctitle, current, open
560
669
  const implicitLines = []
561
670
  for (let idx = 0; idx < end; idx++) {
562
671
  if (idx === doctitleIdx) {
@@ -567,38 +676,51 @@ function processDocumentHeader (doc, lines, buffer, ignoreLines) {
567
676
  }
568
677
  const line = lines[idx]
569
678
  if (open === ':' || open === '-:') {
570
- if (open === ':') buffer.push(line)
571
- if (!line || !line.endsWith(' \\')) open = undefined
679
+ if (open === ':') current.lines.push(line)
680
+ if (!line || !line.endsWith(' \\')) current = open = undefined
572
681
  } else if (line) {
573
- const chr0 = line.charAt()
574
682
  let attributeEntryMatch
683
+ const chr0 = line.charAt()
575
684
  if (chr0 === '/' && line.charAt(1) === '/') {
576
- if (line.startsWith('////')) {
577
- open = open ? (open === line ? undefined : open) : line
578
- } else if (belowDoctitle && !open && line.charAt(2) === '/') {
685
+ if (line.startsWith('////') && line === '/'.repeat(line.length)) {
686
+ if (open) {
687
+ current.lines.push(line)
688
+ if (open === line) current = open = undefined
689
+ } else {
690
+ entries.push((current = { type: 'block_comment', lines: [(open = line)] }))
691
+ }
692
+ } else if (open) {
693
+ current.lines.push(line)
694
+ } else if (belowDoctitle && line.charAt(2) === '/') {
579
695
  break
696
+ } else {
697
+ entries.push({ type: 'line_comment', lines: [line] })
698
+ current = undefined
580
699
  }
581
- buffer.push(line)
582
700
  } else if (open) {
583
- buffer.push(line)
584
- } else if (chr0 === ':' && ~line.indexOf(':', 2) && (attributeEntryMatch = line.match(AttributeEntryRx))) {
585
- const attributeName = attributeEntryMatch[1].replace('!', '')
586
- if (DiscardAttributes.includes(attributeName)) {
701
+ current.lines.push(line)
702
+ } else if (chr0 === ':' && ~line.indexOf(':', 2) && (attributeEntryMatch = line.match(ATTR_ENTRY_RX))) {
703
+ let name = attributeEntryMatch[1]
704
+ const negated = name.charAt() === '!' || name.charAt(name.length - 1) === '!'
705
+ if (negated) name = name.replace('!', '')
706
+ if (DISCARD_ATTRIBUTE_NAMES.includes(name)) {
587
707
  if (line.endsWith(' \\')) open = '-:' // disallow value continuation
588
708
  } else {
589
709
  if (line.endsWith(' \\')) open = ':'
590
- buffer.push(line)
710
+ entries.push((current = { type: 'attribute_entry', name, negated, lines: [line], lineno: idx + 1 }))
711
+ if (assemblyHeaderAttributes.has(name)) current.promote = true
591
712
  }
592
713
  } else if (belowDoctitle) {
593
714
  if (implicitLines.length === 2 || !/[\p{Alpha}0-9]/u.test(chr0)) break
594
715
  implicitLines.push(line)
595
716
  } else if (chr0 === '[' && line.charAt(line.length - 1) === ']') {
717
+ current = undefined
596
718
  const attrlist = line
597
719
  .slice(1, -1)
598
720
  .trim()
599
- .replace(/(?:^\w[\w-]*(?!=|[\w-]))?([#.%]\w[\w-]*)*/, '')
600
- .replace(NamedIdAttrRx, '')
601
- if (attrlist) buffer.push('[' + attrlist + ']')
721
+ .replace(/(?:^\w[\w-]*(?!=|[\w-]))?([#.]\w[\w-]*)*/, '')
722
+ .replace(NAMED_ID_ATTR_RX, '')
723
+ if (attrlist) entries.push({ type: 'attrlist', lines: ['[' + attrlist + ']'] })
602
724
  }
603
725
  } else if (belowDoctitle) {
604
726
  break
@@ -606,18 +728,15 @@ function processDocumentHeader (doc, lines, buffer, ignoreLines) {
606
728
  lines[idx] = undefined
607
729
  ignoreLines.push(idx)
608
730
  }
609
- return doc
610
- .getRoles()
611
- .map((role) => '.' + role)
612
- .join('')
731
+ return entries
613
732
  }
614
733
 
615
734
  function generateSlug (title) {
616
735
  return title
617
736
  .toLowerCase()
618
737
  .replace(/<[^>]+>/g, '')
619
- .replace(CharRefRx, (_, name, dec, hex) => {
620
- if (name) return BuiltInNamedEntities[name] ?? '?'
738
+ .replace(CHAR_REF_RX, (_, name, dec, hex) => {
739
+ if (name) return BUILT_IN_NAMED_ENTITIES[name] ?? '?'
621
740
  return String.fromCharCode(dec ? parseInt(dec, 10) : parseInt(hex, 16))
622
741
  })
623
742
  .replace(/[\x27\u2019]/g, '')
@@ -655,11 +774,11 @@ function safePush (onto, entries) {
655
774
  }
656
775
  }
657
776
 
658
- function generateIdFromTitle (titleAsciiDoc, sep) {
777
+ function generateIdFromTitle (titleAsciiDoc, idSeparators) {
659
778
  const Section = this.$class().$const_get('::Asciidoctor::Section')
660
779
  const baseId = Section.$generate_id(titleAsciiDoc, this)
661
780
  this.getCatalog().refs['$[]='](baseId, true)
662
- return `_${sep}${baseId}`
781
+ return `_${idSeparators.coordinate}${baseId}`
663
782
  }
664
783
 
665
784
  module.exports = produceAssemblyFile
@@ -4,7 +4,6 @@ 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')
8
7
  const selectMutableAttributes = require('./select-mutable-attributes')
9
8
 
10
9
  const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
@@ -43,7 +42,7 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
43
42
  delete assemblyAttributes['source-highlighter']
44
43
  }
45
44
  const mergedAsciiDocAttributes = collateAsciiDocAttributes(
46
- Object.assign({ revdate: assemblyModel.revdate }, componentVersionAsciiDocConfig.attributes),
45
+ Object.assign({}, componentVersionAsciiDocConfig.attributes),
47
46
  assemblyAttributes,
48
47
  contextualLogger
49
48
  )
@@ -75,22 +74,6 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
75
74
  } else if (!(assemblyModel.filetype === 'pdf' && assemblyModel.linkReferenceStyle === 'relative')) {
76
75
  assemblyModel.linkReferenceStyle = 'absolute'
77
76
  }
78
- const auxiliaryImages = new Set()
79
- const ctx = { component: componentName, version }
80
- Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
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
93
- })
94
77
  const rootEntry = { content: title }
95
78
  let startPage =
96
79
  'startPage' in componentVersion
@@ -115,7 +98,6 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
115
98
  })
116
99
  }
117
100
  const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
118
- delete mutableAttributes.doctype // Q: should this be in selectMutableAttributes?
119
101
  prepareOutlines(navigation, rootEntry, rootLevel).reduce((any, outline) => {
120
102
  const assemblyFile = produceAssemblyFile(
121
103
  loadAsciiDoc,
@@ -128,19 +110,14 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, se
128
110
  assemblyModel
129
111
  )
130
112
  if (!assemblyFile) return any
131
- if (!any && auxiliaryImages.size) {
132
- const assembledAssets = assemblyFile.assembler.assembled.assets
133
- auxiliaryImages.forEach((asset) => assembledAssets.add(asset))
113
+ // NOTE restore source highlighter for conversion if defined in Assembler config
114
+ if (sourceHighlighter !== undefined) {
115
+ assemblyFile.asciidoc.attributes['source-highlighter'] = sourceHighlighter
116
+ } else if (assemblyModel.filetype !== 'html') {
117
+ delete assemblyFile.asciidoc.attributes['source-highlighter']
134
118
  }
135
- accum.push(assemblyFile)
136
- return true
119
+ return !!accum.push(assemblyFile)
137
120
  }, false)
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
- }
144
121
  return accum
145
122
  },
146
123
  []
@@ -17,6 +17,7 @@ function selectMutableAttributes (loadAsciiDoc, contentCatalog, referencePage, a
17
17
  ]
18
18
  // we could consider using an Asciidoctor extension here to grab attributes passed via the API instead
19
19
  const immutableAttributeNames = doc.attribute_overrides.$keys()['$-'](additionalMutableNames)
20
+ immutableAttributeNames.push('doctype')
20
21
  return Object.entries(doc.getAttributes()).reduce((accum, [name, val]) => {
21
22
  if (!immutableAttributeNames.includes(name)) accum[name] = val
22
23
  return accum
@@ -1,25 +1,25 @@
1
1
  'use strict'
2
2
 
3
- const ReservedIdNames = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
3
+ const HTML_RESERVED_ID_NAMES = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
4
4
 
5
- function generateScopedId (componentSrc, componentVersion, coordinateSep, scopeSep, prefix, filetype, asXrefTarget) {
5
+ function generateScopedId (componentSrc, componentVersion, separators, filetype, asXrefTarget) {
6
6
  let { component, module: mod, relative } = componentSrc
7
7
  let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
8
+ let { coordinate: coordinateSeparator, prefix: prefixSeparator } = separators
8
9
  if (component !== componentVersion.name) {
9
- id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSep)
10
+ id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSeparator)
10
11
  } else if (mod !== 'ROOT') {
11
- if (asXrefTarget && coordinateSep === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
12
- prefix = ''
12
+ if (asXrefTarget && coordinateSeparator === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
13
+ prefixSeparator = ''
13
14
  mod = mod.replace(/(?:pass|stem)$/, '\\$&')
14
15
  }
15
- id = mod + coordinateSep + id
16
- } else if (filetype === 'html' && ReservedIdNames.includes(id)) {
17
- id = prefix + id
18
- prefix = ''
16
+ id = mod + coordinateSeparator + id
17
+ } else if (filetype === 'html' && HTML_RESERVED_ID_NAMES.includes(id)) {
18
+ id = prefixSeparator + id
19
+ prefixSeparator = ''
19
20
  }
20
- if (prefix && !/^[\p{Alpha}_:]/u.test(id)) id = prefix + id
21
- const idLeader = id + scopeSep
22
- return { idLeader, id }
21
+ if (prefixSeparator && !/^[\p{Alpha}_:]/u.test(id)) id = prefixSeparator + id
22
+ return id
23
23
  }
24
24
 
25
25
  module.exports = generateScopedId
@@ -4,7 +4,8 @@ const createResourceKey = require('./create-resource-key')
4
4
  const generateScopedId = require('./generate-scoped-id')
5
5
  const parseResourceRef = require('./parse-resource-ref')
6
6
  const { resolveEmbedTarget, resolveLinkTarget } = require('./resolver')
7
- const { NamedIdAttrRx } = require('./rx')
7
+
8
+ const { NAMED_ID_ATTR_RX } = require('./rx')
8
9
 
9
10
  function rewriteXrefs (
10
11
  line,
@@ -12,15 +13,13 @@ function rewriteXrefs (
12
13
  assemblyModel,
13
14
  ctx,
14
15
  escapeForInline,
15
- idCoordinateSep,
16
- idScopeSep,
17
- idPrefix,
18
- idLeader,
19
16
  pagesInOutline,
17
+ idSeparators,
18
+ idLeader,
20
19
  doc,
21
20
  sourceLocation
22
21
  ) {
23
- return line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|.*?[^\\])\]/gu, (_, target, text) =>
22
+ return line.replace(/(?<![\\+])xref:((?:\.\/)?[\p{Alpha}0-9_/.{#].*?)\[(|[\s\S]*?[^\\])\]/gu, (_, target, text) =>
24
23
  rewriteXref(
25
24
  target,
26
25
  text,
@@ -28,11 +27,9 @@ function rewriteXrefs (
28
27
  assemblyModel,
29
28
  ctx,
30
29
  escapeForInline,
31
- idCoordinateSep,
32
- idScopeSep,
33
- idPrefix,
34
- idLeader,
35
30
  pagesInOutline,
31
+ idSeparators,
32
+ idLeader,
36
33
  doc,
37
34
  sourceLocation
38
35
  )
@@ -46,11 +43,9 @@ function rewriteXref (
46
43
  assemblyModel,
47
44
  ctx,
48
45
  escapeForInline,
49
- idCoordinateSep,
50
- idScopeSep,
51
- idPrefix,
52
- idLeader,
53
46
  pagesInOutline,
47
+ idSeparators,
48
+ idLeader,
54
49
  doc,
55
50
  sourceLocation
56
51
  ) {
@@ -67,7 +62,7 @@ function rewriteXref (
67
62
  } else {
68
63
  fragment = target
69
64
  }
70
- if (!resourceRef) return `xref:${idLeader || ''}${fragment}[${text}]`
65
+ if (!resourceRef) return `xref:${idLeader == null ? rawTarget : idLeader + fragment}[${text}]`
71
66
  const resourceId = parseResourceRef(resourceRef, ctx, 'page', contentCatalog)
72
67
  const family = resourceId.family
73
68
  let resource
@@ -76,7 +71,8 @@ function rewriteXref (
76
71
  const { linkReferenceStyle, pubRoot, siteRoot } = assemblyModel
77
72
  text ||= resource.asciidoc?.xreftext || rawTarget
78
73
  if (siteRoot || linkReferenceStyle === 'relative') {
79
- return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, escapeForInline)}${fragment && '#' + fragment}[${text}]`
74
+ const hash = fragment && !(family === 'page' && fragment === resource.asciidoc.id) ? '#' + fragment : ''
75
+ return `${resolveLinkTarget(resource, siteRoot, pubRoot, linkReferenceStyle, escapeForInline)}${hash}[${text}]`
80
76
  }
81
77
  if (doc) {
82
78
  const msg = `Cannot create external ${family} reference in assembly because site URL is unknown: ${rawTarget}`
@@ -97,42 +93,41 @@ function rewriteXref (
97
93
  text = ''
98
94
  }
99
95
  const componentVersionCtx = { name: ctx.component, version: ctx.version }
100
- const { idLeader: refidLeader, id: refid } = generateScopedId(
96
+ const refid = generateScopedId(
101
97
  resource.src,
102
98
  componentVersionCtx,
103
- idCoordinateSep,
104
- idScopeSep,
105
- idPrefix,
99
+ idSeparators,
106
100
  assemblyModel.filetype,
107
101
  text.length > 0
108
102
  )
109
- return `xref:${fragment ? refidLeader + fragment : refid}[${text}]`
103
+ return `xref:${fragment ? refid + idSeparators.scope + fragment : refid}[${text}]`
110
104
  }
111
105
 
112
106
  function rewriteImageAttr (val, contentCatalog, assemblyModel, ctx, assets) {
113
- const match = val.startsWith('image:') && /^image::?(.+?)\[(.*?)\]$/.exec(val)
107
+ const match = val.startsWith('image:') && /^image::?(.+?)\[(.*?)\](@|)$/.exec(val)
114
108
  if (!match) return
115
109
  const newTarget = rewriteImageRef(match[1], contentCatalog, assemblyModel, ctx, assets)
116
- return newTarget ? `image:${newTarget}[${match[2]}]` : val
110
+ return newTarget ? `image:${newTarget}[${match[2]}]${match[3]}` : val
117
111
  }
118
112
 
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)
113
+ function rewriteInlineImages (line, contentCatalog, assemblyModel, ctx, assets, escapeForInline, doc) {
114
+ return line.replace(/(?<![\\+])image:([^:\s[](?:[^[]*[^\s[])?)\[(|[\s\S]*?[^\\])\]/g, (m, target, attrlist) => {
115
+ const newTarget = rewriteImageRef(target, contentCatalog, assemblyModel, ctx, assets, escapeForInline, doc)
122
116
  return newTarget ? `image:${newTarget}[${attrlist}]` : m
123
117
  })
124
118
  }
125
119
 
126
- function rewriteImageRef (target, contentCatalog, assemblyModel, ctx, assets, escapeForInline = false) {
120
+ function rewriteImageRef (target, contentCatalog, assemblyModel, ctx, assets, escapeForInline, doc) {
121
+ if (doc && ~target.indexOf('{')) target = doc.$sub_attributes(target, global.Opal.hash({ attribute_missing: 'skip' }))
127
122
  const image = isResourceRef(target) && contentCatalog.resolveResource(target, ctx, 'image', ['image'])
128
123
  if (!image?.out) return
129
124
  let newTarget
130
125
  const { filetype, embedReferenceStyle, linkReferenceStyle, outDirname, pubRoot, siteRoot } = assemblyModel
131
126
  if (filetype !== 'html') {
132
- newTarget = resolveEmbedTarget(image, outDirname, embedReferenceStyle, escapeForInline)
127
+ newTarget = resolveEmbedTarget(image, outDirname, embedReferenceStyle, escapeForInline ?? false)
133
128
  assets.add(image)
134
129
  } else if (siteRoot || linkReferenceStyle === 'relative') {
135
- newTarget = resolveLinkTarget(image, siteRoot, pubRoot, linkReferenceStyle, escapeForInline, false)
130
+ newTarget = resolveLinkTarget(image, siteRoot, pubRoot, linkReferenceStyle, escapeForInline ?? false, false)
136
131
  if (linkReferenceStyle === 'relative') assets.add(image)
137
132
  }
138
133
  if (!newTarget) return
@@ -162,7 +157,7 @@ function rewriteStyleAttribute (block, lines, idx, idLeader, replacementStyle =
162
157
  prevLine = cellSpec[2]
163
158
  cellSpec = cellSpec[1]
164
159
  } else if (~prevLine.indexOf('id=')) {
165
- prevLine = `[${prevLine.slice(1, -1).replace(NamedIdAttrRx, '')}]`
160
+ prevLine = `[${prevLine.slice(1, -1).replace(NAMED_ID_ATTR_RX, '')}]`
166
161
  }
167
162
  let rawStyle
168
163
  const commaIdx = prevLine.indexOf(',')
package/lib/util/rx.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const NamedIdAttrRx = /(?:^|, *)id=(?:("[^"]*"|'[^']*'|[^,]*))/
3
+ const NAMED_ID_ATTR_RX = /(?:^|, *)id=(?:("[^"]*"|'[^']*'|[^,]*))/
4
4
 
5
- module.exports = { NamedIdAttrRx }
5
+ module.exports = { NAMED_ID_ATTR_RX }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antora/assembler",
3
- "version": "1.0.0-beta.18",
3
+ "version": "1.0.0-beta.19",
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)",
@@ -27,6 +27,7 @@
27
27
  ".": "./lib/index.js",
28
28
  "./filter-component-versions": "./lib/filter-component-versions.js",
29
29
  "./load-config": "./lib/load-config.js",
30
+ "./log-command": "./lib/log-command.js",
30
31
  "./parse-resource-ref": "./lib/util/parse-resource-ref.js",
31
32
  "./produce-assembly-file": "./lib/produce-assembly-file.js",
32
33
  "./produce-assembly-files": "./lib/produce-assembly-files.js",
@@ -35,12 +36,12 @@
35
36
  },
36
37
  "imports": {
37
38
  "#asciidoctor-log-adapter": "./adapters/asciidoctor/jsonl-logger.rb",
38
- "#run-command": "@antora/run-command-helper",
39
39
  "#unconvert-inline-asciidoc": "./lib/util/unconvert-inline-asciidoc.js"
40
40
  },
41
41
  "dependencies": {
42
42
  "@asciidoctor/reducer": "~1.1",
43
43
  "@antora/expand-path-helper": "~3.0",
44
+ "@antora/run-command-helper": "~1.0",
44
45
  "braces": "~3.0",
45
46
  "picomatch": "~3.0",
46
47
  "js-yaml": "~4.1"