@antora/assembler 1.0.0-rc.5 → 1.0.0-rc.7

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.
@@ -11,19 +11,22 @@ const {
11
11
  rewriteStyleAttribute,
12
12
  } = require('./util/rewriter')
13
13
  const sanitize = require('./util/sanitize')
14
+ const toHash = require('./util/to-hash')
14
15
  const unconvertInlineAsciiDoc = require('./util/unconvert-inline-asciidoc')
15
16
 
16
17
  const ATTR_ENTRY_RX = /^:(!?[\p{Alpha}0-9_][^:]*):(?: |$)/u
17
18
  const BUILT_IN_NAMED_ENTITIES = { amp: '&', apos: "'", gt: '>', lt: '<', nbsp: ' ', quot: '"' }
18
19
  const CHAR_REF_RX = /&(?:([a-z][a-z]+\d{0,2})|#(?:(\d{2,6})|x([a-z\d]{2,5})));/g
19
20
  const DISCARD_ATTRIBUTE_NAMES = [
20
- 'doctype',
21
- 'leveloffset',
22
- 'preface-title',
21
+ 'assembly-doctype',
23
22
  'assembly-header-attributes',
24
23
  'assembly-navtitle',
25
24
  'assembly-slug',
26
25
  'assembly-style',
26
+ 'doctype',
27
+ 'leveloffset',
28
+ 'page-aliases',
29
+ 'preface-title',
27
30
  'underscore',
28
31
  ]
29
32
  const { NAMED_ID_ATTR_RX } = require('./util/rx')
@@ -39,21 +42,20 @@ function produceAssemblyFile (
39
42
  assemblyModel
40
43
  ) {
41
44
  const pagesByUrl = files.reduce((map, it) => (it.src.family === 'page' ? map.set(it.pub.url, it) : map), new Map())
42
- if (outline.urlType === 'internal' && !pagesByUrl.get(outline.url) && !(outline.items || []).length) return
43
45
  const pagesInOutline = selectPagesInOutline(outline, pagesByUrl)
46
+ if (outline.urlType === 'internal' && !pagesByUrl.has(outline.pathname) && !outline.items?.length) return
44
47
  const { rootLevel, xmlIds } = assemblyModel
48
+ const scratchDoc = loadAsciiDoc(
49
+ { contents: Buffer.alloc(0), src: { family: 'page', relative: '_scratch.adoc' } },
50
+ undefined,
51
+ asciidocConfig
52
+ )
45
53
  const idSeparators = {
46
54
  prefix:
47
55
  'assembler-idprefix' in asciidocConfig.attributes ? (asciidocConfig.attributes['assembler-idprefix'] ?? '') : '_',
48
56
  scope: xmlIds ? '---' : ':::',
49
57
  coordinate: xmlIds ? '----' : ':',
50
- generateIdFromTitle: generateIdFromTitle.bind(
51
- loadAsciiDoc(
52
- { contents: Buffer.alloc(0), src: { family: 'page', relative: 'generate-id-from-title.adoc' } },
53
- undefined,
54
- asciidocConfig
55
- )
56
- ),
58
+ generateIdFromTitle: generateIdFromTitle.bind(scratchDoc),
57
59
  }
58
60
  const { name: component, version } = componentVersion
59
61
  asciidocConfig = prepareAsciiDocConfig(
@@ -62,12 +64,14 @@ function produceAssemblyFile (
62
64
  pagesInOutline,
63
65
  asciidocConfig,
64
66
  assemblyModel,
65
- idSeparators
67
+ idSeparators,
68
+ scratchDoc
66
69
  )
67
- const buffer = mergeAsciiDoc(
70
+ const builder = buildAsciiDocHeader(componentVersion, outline.content, assemblyModel)
71
+ mergeAsciiDoc(
68
72
  loadAsciiDoc,
69
73
  contentCatalog,
70
- buildAsciiDocHeader(componentVersion, outline.content, assemblyModel),
74
+ builder,
71
75
  componentVersion,
72
76
  outline,
73
77
  files,
@@ -78,22 +82,22 @@ function produceAssemblyFile (
78
82
  assemblyModel
79
83
  )
80
84
  const stem =
81
- (buffer.slug ? sanitizeSlug(buffer.slug) : generateSlug(rootLevel === 0 ? 'index' : buffer.navtitle)) ||
85
+ (builder.slug ? sanitizeSlug(builder.slug) : generateSlug(rootLevel === 0 ? 'index' : builder.navtitle)) ||
82
86
  'export-' + (assemblyModel.stemSeq = (assemblyModel.exportSeq ?? 0) + 1)
83
87
  const downloadStem = [component, version, stem === 'index' ? '' : stem].filter((it) => it).join('-')
84
88
  const file = contentCatalog.createFile({
85
89
  asciidoc: asciidocConfig,
86
90
  assembler: { assembled: pagesInOutline.assembled, downloadStem, rootLevel },
87
- contents: Buffer.from(buffer.join('\n') + '\n'),
91
+ contents: builder.serialize(),
88
92
  src: { component, version, componentVersion, module: 'ROOT', family: 'export', relative: stem + '.adoc' },
89
93
  pub: false,
90
94
  })
91
- file.path = file.out.path // use out path as file path so assets can easily be published to same hierarchy
95
+ file.path = file.src.path = file.out.path // use out path as path so assets can be published to same hierarchy
92
96
  delete file.out
93
97
  return file
94
98
  }
95
99
 
96
- function prepareAsciiDocConfig (contentCatalog, ctx, pagesInOutline, asciidocConfig, assemblyModel, idSeparators) {
100
+ function prepareAsciiDocConfig (contentCatalog, ctx, pagesInOutline, asciidocConfig, assemblyModel, idSeparators, doc) {
97
101
  let attributesModified
98
102
  const configShared = asciidocConfig.$shared
99
103
  const sharedAttributes = asciidocConfig.attributes
@@ -114,7 +118,17 @@ function prepareAsciiDocConfig (contentCatalog, ctx, pagesInOutline, asciidocCon
114
118
  for (const [name, val] of Object.entries(sharedAttributes)) {
115
119
  if (!(val?.constructor === String && ~val.indexOf(':'))) continue
116
120
  if (~val.indexOf('xref:')) {
117
- const newVal = rewriteXrefs(val, contentCatalog, assemblyModel, ctx, false, pagesInOutline, idSeparators)
121
+ const newVal = rewriteXrefs(
122
+ val,
123
+ contentCatalog,
124
+ assemblyModel,
125
+ ctx,
126
+ false,
127
+ pagesInOutline,
128
+ idSeparators,
129
+ null,
130
+ doc
131
+ )
118
132
  if (newVal !== val) (attributesModified ??= {})[name] = newVal
119
133
  }
120
134
  }
@@ -124,42 +138,65 @@ function prepareAsciiDocConfig (contentCatalog, ctx, pagesInOutline, asciidocCon
124
138
  }
125
139
 
126
140
  function buildAsciiDocHeader (componentVersion, navtitle, assemblyModel) {
127
- const doctype = assemblyModel.doctype ?? 'book'
141
+ const { name: componentName, version, displayVersion, title } = componentVersion
128
142
  const navtitlePlain = sanitize(navtitle)
129
143
  const navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
130
- const doctitle = (componentVersion.title === navtitlePlain ? '' : componentVersion.title + ': ') + navtitleAsciiDoc
131
- const version = componentVersion.version === 'master' ? '' : componentVersion.version
132
- const displayVersion = componentVersion.displayVersion
133
- const buffer = [
134
- `= ${doctitle}`,
135
- ...(version ? [`:revnumber: ${displayVersion}`] : []),
136
- ...(doctype === 'article' ? [] : [`:doctype: ${doctype}`]),
137
- ':underscore: _',
138
- // Q: should we pass these via the CLI so they cannot be modified?
139
- `:page-component-name: ${componentVersion.name}`,
140
- `:page-component-version:${version ? ' ' + version : ''}`,
141
- ':page-version: {page-component-version}',
142
- `:page-component-display-version: ${displayVersion}`,
143
- `:page-component-title: ${componentVersion.title}`,
144
- ]
145
- return Object.assign(buffer, { navtitle })
144
+ const doctitle = (title === navtitlePlain ? '' : title + ': ') + navtitleAsciiDoc
145
+ const versioned = !!(version && version !== 'master')
146
+ return {
147
+ header: [
148
+ `= ${doctitle}`,
149
+ ...(versioned ? [`:revnumber: ${displayVersion}`] : []),
150
+ `:doctype: ${(assemblyModel.doctype ??= 'book')}`,
151
+ ':underscore: _',
152
+ `:page-component-name: ${componentName}`,
153
+ `:page-component-version:${versioned ? ' ' + version : ''}`,
154
+ ':page-version: {page-component-version}',
155
+ `:page-component-display-version: ${displayVersion}`,
156
+ `:page-component-title: ${title}`,
157
+ ],
158
+ currentComponentVersion: componentVersion,
159
+ navtitle,
160
+ setAuthor (author) {
161
+ this.header.splice(1, 0, author)
162
+ },
163
+ setDoctitle (doctitle_) {
164
+ this.header[0] = `= ${doctitle_}`
165
+ },
166
+ setDoctype (doctype) {
167
+ this.header[this.header.findIndex((line) => line.startsWith(':doctype: '))] = `:doctype: ${doctype}`
168
+ },
169
+ setDocid (docid) {
170
+ this.header.unshift(`[#${docid}]`)
171
+ },
172
+ serialize () {
173
+ const normalizedHeader = this.header.filter((it) => it !== ':doctype: article')
174
+ return this.body
175
+ ? Buffer.from(normalizedHeader.join('\n') + '\n\n' + this.body.join('\n') + '\n')
176
+ : Buffer.from(normalizedHeader.join('\n') + '\n')
177
+ },
178
+ }
146
179
  }
147
180
 
148
181
  function selectPagesInOutline (outlineEntry, pagesByUrl, accum) {
149
182
  accum ??= Object.assign(new Map(), { assembled: { pages: new Map(), assets: new Set() } })
150
- const page = outlineEntry.urlType === 'internal' ? pagesByUrl.get(outlineEntry.url) : undefined
151
- if (page) {
152
- accum.set(createResourceKey(page.src), page)
153
- accum.set(outlineEntry.url, page)
183
+ const { urlType, url, hash, unresolved, items = [] } = outlineEntry
184
+ if (urlType === 'internal' && !unresolved) {
185
+ const pathname = (outlineEntry.pathname ??= hash ? url.substring(0, url.length - hash.length) : url)
186
+ let page
187
+ if (!accum.has(pathname) && (page = pagesByUrl.get(pathname))) {
188
+ accum.set(createResourceKey(page.src), page)
189
+ accum.set(pathname, page)
190
+ }
154
191
  }
155
- for (const item of outlineEntry.items || []) selectPagesInOutline(item, pagesByUrl, accum)
192
+ for (const item of items) selectPagesInOutline(item, pagesByUrl, accum)
156
193
  return accum
157
194
  }
158
195
 
159
196
  function mergeAsciiDoc (
160
197
  loadAsciiDoc,
161
198
  contentCatalog,
162
- buffer,
199
+ builder,
163
200
  componentVersion,
164
201
  outlineEntry,
165
202
  files,
@@ -168,31 +205,31 @@ function mergeAsciiDoc (
168
205
  asciidocConfig,
169
206
  mutableAttributes,
170
207
  assemblyModel,
171
- lastComponentVersion = componentVersion,
172
208
  level = 0,
173
209
  supportsParts = false
174
210
  ) {
175
- const { items = [], roles = [], unresolved, urlType, url, hash } = outlineEntry
211
+ const { items = [], roles = [], unresolved, urlType, url, pathname, hash } = outlineEntry
176
212
  // TODO: we could try to be smart about it and make sure the page with fragment is included at least once
177
- if (hash || roles.includes('site-only')) {
178
- buffer.inBody ??= false
179
- return buffer
213
+ if (roles.includes('site-only') || (urlType === 'external' && !URL.canParse(url))) {
214
+ builder.body ??= []
215
+ return builder
180
216
  }
181
- let navtitle = outlineEntry.navtitle ?? outlineEntry.content
217
+ const assembled = pagesInOutline.assembled
218
+ // FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
219
+ const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(pathname) : undefined
220
+ let navtitle = outlineEntry.navtitle ?? (page && hash ? page.asciidoc.navtitle : outlineEntry.content)
182
221
  let navtitlePlain = sanitize(navtitle)
183
222
  let navtitleAsciiDoc = unconvertInlineAsciiDoc(navtitle)
184
223
  const { filetype, linkReferenceStyle, pubRoot, sectionMergeStrategy, siteRoot, logger, rootLevel } = assemblyModel
185
- const assembled = pagesInOutline.assembled
186
- // FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
187
- const page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
188
- const atDocumentRoot = !buffer.inBody
189
- const atBookRoot = atDocumentRoot && !level && assemblyModel.doctype === 'book' && (supportsParts = true)
224
+ const doctype = assemblyModel.doctype
225
+ const atDocumentRoot = !builder.body?.length
226
+ let atBookRoot = atDocumentRoot && !level && doctype === 'book' && (supportsParts = true)
190
227
  const hasItems = items.length > 0
191
228
  if (page && !assembled.pages.has(page)) {
192
229
  const contents = page.src.contents
193
230
  if (contents == null) {
194
- buffer.inBody ??= false
195
- return buffer
231
+ builder.body ??= []
232
+ return builder
196
233
  }
197
234
  const pageAsAsciiDoc = new page.constructor(
198
235
  Object.assign({}, page, { contents: trimAsciiDoc(contents), mediaType: page.src.mediaType })
@@ -208,20 +245,25 @@ function mergeAsciiDoc (
208
245
  },
209
246
  })
210
247
  : doc.getLogger()
211
- doc.source_header_attributes ??= doc.parent.$to_h()
248
+ doc.source_header_attributes ??= toHash()
212
249
  const isRootPage = atDocumentRoot && !level
213
250
  const { component, version, module: module_, relative, origin } = page.src
214
- if (isRootPage && doc.hasAttribute('assembly-slug')) buffer.slug = doc.getAttribute('assembly-slug')
251
+ const doctypeOverride = doc.getAttribute('assembly-doctype')
252
+ if (doctypeOverride) {
253
+ if (doctypeOverride !== doctype) builder.setDoctype(doctypeOverride)
254
+ if (doctypeOverride !== 'book') atBookRoot = supportsParts = false
255
+ }
256
+ if (isRootPage && doc.hasAttribute('assembly-slug')) builder.slug = doc.getAttribute('assembly-slug')
215
257
  let hasAssemblyNavtitleAttr
216
258
  if ((hasAssemblyNavtitleAttr = !!doc.getAttribute('assembly-navtitle'))) {
217
259
  navtitleAsciiDoc = doc.getAttribute('assembly-navtitle')
218
260
  navtitlePlain = sanitize((navtitle = doc.$apply_reftext_subs(navtitleAsciiDoc)))
219
- if (buffer.inBody == null) {
220
- buffer.navtitle = navtitle
261
+ if (builder.body == null) {
262
+ builder.navtitle = navtitle
221
263
  if (rootLevel) {
222
- const doctitle =
264
+ builder.setDoctitle(
223
265
  (componentVersion.title === navtitlePlain ? '' : componentVersion.title + ': ') + navtitleAsciiDoc
224
- buffer[0] = `= ${doctitle}`
266
+ )
225
267
  }
226
268
  }
227
269
  }
@@ -247,13 +289,10 @@ function mergeAsciiDoc (
247
289
  let nextSectionLevel = 1
248
290
  const lines = doc.getSourceLines()
249
291
  const ignoreLines = []
250
- if (!buffer.inBody) {
251
- buffer.inBody = true
252
- buffer.endHeaderIdx = buffer.length - 1
253
- }
254
- buffer.push('')
292
+ const buffer = (builder.body ??= [])
293
+ if (buffer.length) buffer.push('')
255
294
  buffer.push(`:docname: ${docname}`)
256
- if (component !== lastComponentVersion.name) {
295
+ if (component !== builder.currentComponentVersion.name) {
257
296
  const thisComponentVersion =
258
297
  component === componentVersion.name && version === componentVersion.version
259
298
  ? componentVersion
@@ -264,7 +303,7 @@ function mergeAsciiDoc (
264
303
  buffer.push(':page-version: {page-component-version}')
265
304
  buffer.push(`:page-component-display-version: ${thisComponentVersion.displayVersion}`)
266
305
  buffer.push(`:page-component-title: ${thisComponentVersion.title}`)
267
- lastComponentVersion = thisComponentVersion
306
+ builder.currentComponentVersion = thisComponentVersion
268
307
  }
269
308
  }
270
309
  buffer.push(`:page-module: ${module_}`)
@@ -274,12 +313,10 @@ function mergeAsciiDoc (
274
313
  buffer.push(`:page-origin-refname: ${origin.branch || origin.tag}`)
275
314
  buffer.push(`:page-origin-reftype: ${origin.branch ? 'branch' : 'tag'}`)
276
315
  buffer.push(`:page-origin-refhash: ${origin.worktree ? '(worktree)' : origin.refhash}`)
277
- let headerHasBlockAttrs
278
316
  if (doc.hasHeader()) {
279
317
  for (const entry of processDocumentHeader(doc, lines, ignoreLines, isRootPage)) {
280
318
  if (entry.type === 'author_line') {
281
- buffer.splice(1, 0, entry.lines[0])
282
- buffer.endHeaderIdx += 1
319
+ builder.setAuthor(entry.lines[0])
283
320
  } else if (entry.type === 'attribute_entry') {
284
321
  const name = entry.name
285
322
  if (!entry.negated && !doc.isAttributeLocked(name)) {
@@ -320,14 +357,9 @@ function mergeAsciiDoc (
320
357
  if (newVal !== val) entry.lines = `:${entry.name}: ${newVal}`.split('\n')
321
358
  }
322
359
  }
323
- if (entry.promote) {
324
- buffer.splice(buffer.endHeaderIdx + 1, 0, ...entry.lines)
325
- buffer.endHeaderIdx += entry.lines.length
326
- } else {
327
- buffer.push(...entry.lines)
328
- }
360
+ ;(entry.promote ? builder.header : buffer).push(...entry.lines)
329
361
  } else {
330
- if (entry.type === 'attrlist') headerHasBlockAttrs = true
362
+ if (entry.type === 'attrlist') builder.headerHasBlockAttrs = true
331
363
  buffer.push(...entry.lines)
332
364
  }
333
365
  }
@@ -351,15 +383,15 @@ function mergeAsciiDoc (
351
383
  if (htitleOverride != null) {
352
384
  const keepSections = hasSections && sectionMergeStrategy !== 'discrete'
353
385
  if (htitleOverride === '') {
354
- if (headerHasBlockAttrs || keepSections) {
386
+ if (builder.headerHasBlockAttrs || keepSections) {
355
387
  htitleAsciiDoc = '{empty}'
356
388
  notitle = true
357
389
  } else {
358
- if (hasPrefaceTitleAttr) buffer.splice(++buffer.endHeaderIdx, 0, ':preface-title:')
390
+ if (hasPrefaceTitleAttr) builder.header.push(':preface-title:')
359
391
  htitleAsciiDoc = undefined
360
392
  }
361
- } else if (hasPrefaceTitleAttr && !headerHasBlockAttrs && !keepSections) {
362
- buffer.splice(++buffer.endHeaderIdx, 0, `:preface-title: ${unconvertInlineAsciiDoc(htitleOverride)}`)
393
+ } else if (hasPrefaceTitleAttr && !builder.headerHasBlockAttrs && !keepSections) {
394
+ builder.header.push(`:preface-title: ${unconvertInlineAsciiDoc(htitleOverride)}`)
363
395
  htitleAsciiDoc = undefined
364
396
  } else {
365
397
  htitleAsciiDoc = unconvertInlineAsciiDoc(htitleOverride)
@@ -370,7 +402,7 @@ function mergeAsciiDoc (
370
402
  if (atDocumentRoot) {
371
403
  // Q: should we use htitleAsciiDoc to generate ID if not {empty} instead of pageStyle
372
404
  pageFragment = `#_${idSeparators.coordinate}${pageIdLeader}${pageStyle}`
373
- buffer.unshift(`[#${pageId}]`)
405
+ builder.setDocid(pageId)
374
406
  } else {
375
407
  pageFragment = `#${pageId}`
376
408
  }
@@ -400,7 +432,7 @@ function mergeAsciiDoc (
400
432
  buffer.push(`[${heading.style ?? pageStyle}${pageFragment}${rolesAttr}${notitleAttrs}]`)
401
433
  buffer.push(`${'='.repeat(heading.level)} ${heading.title}`)
402
434
  } else if (atDocumentRoot) {
403
- buffer.unshift(`[#${pageId}]`)
435
+ builder.setDocid(pageId)
404
436
  }
405
437
  if (sectionMergeStrategy === 'enclose' && hasItems && hasSections && !(atDocumentRoot && heading)) {
406
438
  // TODO: make overview section title configurable
@@ -408,7 +440,7 @@ function mergeAsciiDoc (
408
440
  //if (overviewTitle === navtitle) overviewTitle = doc.getAttribute('overview-title', 'Overview')
409
441
  const overviewTitle = doc.getAttribute('overview-title', 'Overview')
410
442
  const syntheticId = idSeparators.generateIdFromTitle(overviewTitle, idSeparators, pageIdLeader)
411
- if (heading) buffer.push('')
443
+ if (heading && buffer.length) buffer.push('')
412
444
  let hlevel = level + (nextSectionLevel = 2)
413
445
  if (hlevel > 6) {
414
446
  const blockStyle = `discrete.h${hlevel}`
@@ -429,7 +461,7 @@ function mergeAsciiDoc (
429
461
  if (doc.getDoctype() === 'manpage') {
430
462
  const firstSectionIdx = hasSections ? doc.getSections()[0].getLineNumber() - 1 : lines.length
431
463
  for (let idx = 0; idx < firstSectionIdx; idx++) {
432
- if (~ignoreLines.indexOf(idx)) continue
464
+ if (ignoreLines.includes(idx)) continue
433
465
  const line = lines[idx]
434
466
  if (line.startsWith('== ') && line.length > 3) {
435
467
  allBlocks.unshift({
@@ -473,7 +505,7 @@ function mergeAsciiDoc (
473
505
  })
474
506
  let skipping
475
507
  for (let idx = 0, lastIdx = lines.length - 1; idx <= lastIdx; idx++) {
476
- if (~ignoreLines.indexOf(idx)) continue
508
+ if (ignoreLines.includes(idx)) continue
477
509
  let line = lines[idx]
478
510
  if (line.startsWith('//')) {
479
511
  if (line.charAt(2) !== '/') continue
@@ -589,10 +621,13 @@ function mergeAsciiDoc (
589
621
  buffer,
590
622
  lines.filter((it) => it !== undefined)
591
623
  )
592
- const attributeEntries = Object.entries(doc.source_header_attributes.$$smap)
624
+ const attributeEntries = doc.source_header_attributes.$entries()
593
625
  if (attributeEntries.length) {
594
626
  const resolvedAttributeEntries = attributeEntries.reduce(
595
627
  (accum, [name, val]) => {
628
+ if (val) {
629
+ if (val['$nil?']()) val = null
630
+ }
596
631
  // Q: couldn't we just check if attribute is locked?
597
632
  if (name in mutableAttributes) {
598
633
  const initialVal = mutableAttributes[name]
@@ -610,19 +645,18 @@ function mergeAsciiDoc (
610
645
  )
611
646
  if (resolvedAttributeEntries.length > 1) safePush(buffer, resolvedAttributeEntries)
612
647
  }
613
- } else if (level) {
648
+ } else if (level && !hash) {
649
+ const buffer = (builder.body ??= [])
614
650
  if (atDocumentRoot && rootLevel === 0 && navtitlePlain === componentVersion.title) {
615
- buffer.inBody ??= false
616
651
  level--
617
652
  } else {
618
- buffer.inBody = true
619
- buffer.push('')
653
+ if (buffer.length) buffer.push('')
620
654
  const syntheticId = idSeparators.generateIdFromTitle(navtitleAsciiDoc, idSeparators)
621
655
  let sectionTitle = navtitleAsciiDoc
622
656
  if (urlType === 'external') {
623
657
  sectionTitle = `${url}[${navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
624
658
  } else if (urlType === 'internal' && !unresolved) {
625
- const resource = files.find((it) => it.pub.url === url)
659
+ const resource = files.find((it) => it.pub.url === pathname)
626
660
  if (resource) {
627
661
  if (resource.src.family === 'page' && pagesInOutline.has(resource.pub.url)) {
628
662
  const refid = generateScopedId(resource.src, componentVersion, idSeparators, filetype, true)
@@ -632,38 +666,39 @@ function mergeAsciiDoc (
632
666
  }
633
667
  }
634
668
  }
669
+ if (supportsParts && level === 1 && roles.includes('part')) {
670
+ roles.splice(roles.indexOf('part', 1))
671
+ level--
672
+ }
635
673
  const hlevel = level > 5 ? 6 : level + 1
636
674
  const hroles = urlType === 'internal' ? roles.slice(1) : roles
637
675
  buffer.push(`[${hlevel > 6 ? 'discrete' : ''}#${syntheticId}${hroles.reduce((str, it) => str + '.' + it, '')}]`)
638
676
  buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
639
677
  }
640
678
  }
641
- if (hasItems) {
642
- const nextLevel = level + 1
643
- // NOTE: drop first child if same as parent; should we keep if content is different?
644
- ;(urlType === 'internal' && urlType === items[0].urlType && url === items[0].url && !items[0].items
645
- ? items.slice(1)
646
- : items
647
- ).forEach((item) => {
648
- mergeAsciiDoc(
649
- loadAsciiDoc,
650
- contentCatalog,
651
- buffer,
652
- componentVersion,
653
- item,
654
- files,
655
- pagesInOutline,
656
- idSeparators,
657
- asciidocConfig,
658
- mutableAttributes,
659
- assemblyModel,
660
- lastComponentVersion,
661
- nextLevel,
662
- atBookRoot
663
- )
664
- })
665
- }
666
- return buffer
679
+ if (!hasItems) return
680
+ const nextLevel = level + 1
681
+ // NOTE: drop first child if same as parent; should we keep if content is different?
682
+ ;(urlType === 'internal' && items[0].urlType === 'internal' && pathname === items[0].url && !items[0].items?.length
683
+ ? items.slice(1)
684
+ : items
685
+ ).forEach((item) => {
686
+ mergeAsciiDoc(
687
+ loadAsciiDoc,
688
+ contentCatalog,
689
+ builder,
690
+ componentVersion,
691
+ item,
692
+ files,
693
+ pagesInOutline,
694
+ idSeparators,
695
+ asciidocConfig,
696
+ mutableAttributes,
697
+ assemblyModel,
698
+ nextLevel,
699
+ atBookRoot
700
+ )
701
+ })
667
702
  }
668
703
 
669
704
  function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
@@ -702,7 +737,7 @@ function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
702
737
  }
703
738
  const line = lines[idx]
704
739
  if (open === ':' || open === '-:') {
705
- if (open === ':') current.lines.push(line)
740
+ if (open === ':') current.appendLine(line)
706
741
  if (!line?.endsWith(' \\')) current = open = undefined
707
742
  } else if (line) {
708
743
  let attributeEntryMatch
@@ -710,13 +745,14 @@ function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
710
745
  if (chr0 === '/' && line.charAt(1) === '/') {
711
746
  if (line.startsWith('////') && line === '/'.repeat(line.length)) {
712
747
  if (open) {
713
- current.lines.push(line)
748
+ current.appendLine(line)
714
749
  if (open === line) current = open = undefined
715
750
  } else {
716
751
  entries.push((current = { type: 'block_comment', lines: [(open = line)] }))
752
+ current.appendLine = Array.prototype.push.bind(current.lines)
717
753
  }
718
754
  } else if (open) {
719
- current.lines.push(line)
755
+ current.appendLine(line)
720
756
  } else if (belowDoctitle && line.charAt(2) === '/') {
721
757
  break
722
758
  } else {
@@ -724,7 +760,7 @@ function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
724
760
  current = undefined
725
761
  }
726
762
  } else if (open) {
727
- current.lines.push(line)
763
+ current.appendLine(line)
728
764
  } else if (chr0 === ':' && ~line.indexOf(':', 2) && (attributeEntryMatch = line.match(ATTR_ENTRY_RX))) {
729
765
  let name = attributeEntryMatch[1]
730
766
  const negated = name.charAt() === '!' || name.charAt(name.length - 1) === '!'
@@ -733,7 +769,9 @@ function processDocumentHeader (doc, lines, ignoreLines, isRootPage) {
733
769
  if (line.endsWith(' \\')) open = '-:' // disallow value continuation
734
770
  } else {
735
771
  if (line.endsWith(' \\')) open = ':'
736
- entries.push((current = { type: 'attribute_entry', name, negated, lines: [line], lineno: idx + 1 }))
772
+ entries.push((current = { type: 'attribute_entry', name, negated, lines: [], lineno: idx + 1 }))
773
+ current.lines.push(negated ? `:!${name}:` : line)
774
+ current.appendLine = Array.prototype.push.bind(negated ? [] : current.lines)
737
775
  if (assemblyHeaderAttributes.has(name)) current.promote = true
738
776
  }
739
777
  } else if (belowDoctitle) {
@@ -807,13 +845,13 @@ function safePush (onto, entries) {
807
845
  }
808
846
 
809
847
  function generateIdFromTitle (titleAsciiDoc, idSeparators, idLeader) {
810
- const Section = this.$class().$const_get('::Asciidoctor::Section')
848
+ const Section = this.$class().$$base_module.Section
811
849
  const baseId = Section.$generate_id(titleAsciiDoc, this) // refs in catalog will always be empty
812
850
  let id = `_${idSeparators.coordinate}${idLeader ?? ''}${baseId}`
813
851
  const syntheticIds = (this.catalog.syntheticIds ??= {})
814
852
  if (id in syntheticIds) {
815
853
  const sep = (this.getAttribute('idseparator') ?? '_').charAt()
816
- let cnt = this.$class().$const_get('::Asciidoctor::Compliance').unique_id_start_index
854
+ let cnt = this.$class().$$base_module.Compliance.unique_id_start_index
817
855
  let candidate
818
856
  while ((candidate = `${id}${sep}${cnt}`) in syntheticIds) cnt++
819
857
  id = candidate