@asciidoctor/core 3.0.3 → 4.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +42 -10
  2. package/build/browser/index.js +24154 -0
  3. package/build/node/index.cjs +24735 -0
  4. package/{dist/css/asciidoctor.css → data/asciidoctor-default.css} +54 -53
  5. package/package.json +53 -100
  6. package/src/abstract_block.js +849 -0
  7. package/src/abstract_node.js +954 -0
  8. package/src/attribute_entry.js +12 -0
  9. package/src/attribute_list.js +380 -0
  10. package/src/block.js +168 -0
  11. package/src/browser/asset.js +22 -0
  12. package/src/browser/reader.js +138 -0
  13. package/src/browser.js +121 -0
  14. package/src/callouts.js +85 -0
  15. package/src/compliance.js +54 -0
  16. package/src/constants.js +665 -0
  17. package/src/convert.js +370 -0
  18. package/src/converter/composite.js +83 -0
  19. package/src/converter/docbook5.js +1031 -0
  20. package/src/converter/html5.js +1899 -0
  21. package/src/converter/manpage.js +935 -0
  22. package/src/converter/template.js +459 -0
  23. package/src/converter.js +478 -0
  24. package/src/data/stylesheet-data.js +2 -0
  25. package/src/document.js +2134 -0
  26. package/src/extensions.js +1952 -0
  27. package/src/footnote.js +28 -0
  28. package/src/helpers.js +355 -0
  29. package/src/index.js +138 -0
  30. package/src/inline.js +158 -0
  31. package/src/list.js +240 -0
  32. package/src/load.js +276 -0
  33. package/src/logging.js +526 -0
  34. package/src/parser.js +3661 -0
  35. package/src/path_resolver.js +472 -0
  36. package/src/reader.js +1755 -0
  37. package/src/rx.js +829 -0
  38. package/src/section.js +354 -0
  39. package/src/stylesheets.js +30 -0
  40. package/src/substitutors.js +2241 -0
  41. package/src/syntaxHighlighter/highlightjs.js +90 -0
  42. package/src/syntaxHighlighter/html_pipeline.js +33 -0
  43. package/src/syntax_highlighter.js +304 -0
  44. package/src/table.js +952 -0
  45. package/src/timings.js +78 -0
  46. package/types/abstract_block.d.ts +343 -0
  47. package/types/abstract_node.d.ts +471 -0
  48. package/types/attribute_entry.d.ts +7 -0
  49. package/types/attribute_list.d.ts +52 -0
  50. package/types/block.d.ts +55 -0
  51. package/types/browser/asset.d.ts +7 -0
  52. package/types/browser/reader.d.ts +29 -0
  53. package/types/callouts.d.ts +36 -0
  54. package/types/compliance.d.ts +23 -0
  55. package/types/constants.d.ts +268 -0
  56. package/types/convert.d.ts +34 -0
  57. package/types/converter/composite.d.ts +20 -0
  58. package/types/converter/docbook5.d.ts +41 -0
  59. package/types/converter/html5.d.ts +51 -0
  60. package/types/converter/manpage.d.ts +59 -0
  61. package/types/converter/template.d.ts +83 -0
  62. package/types/converter.d.ts +150 -0
  63. package/types/data/stylesheet-data.d.ts +2 -0
  64. package/types/document.d.ts +495 -0
  65. package/types/extensions.d.ts +876 -0
  66. package/types/footnote.d.ts +18 -0
  67. package/types/helpers.d.ts +146 -0
  68. package/types/index.d.ts +73 -3731
  69. package/types/inline.d.ts +69 -0
  70. package/types/list.d.ts +114 -0
  71. package/types/load.d.ts +39 -0
  72. package/types/logging.d.ts +187 -0
  73. package/types/parser.d.ts +114 -0
  74. package/types/path_resolver.d.ts +103 -0
  75. package/types/reader.d.ts +184 -0
  76. package/types/rx.d.ts +513 -0
  77. package/types/section.d.ts +122 -0
  78. package/types/stylesheets.d.ts +10 -0
  79. package/types/substitutors.d.ts +208 -0
  80. package/types/syntaxHighlighter/highlightjs.d.ts +33 -0
  81. package/types/syntaxHighlighter/html_pipeline.d.ts +16 -0
  82. package/types/syntax_highlighter.d.ts +167 -0
  83. package/types/table.d.ts +231 -0
  84. package/types/timings.d.ts +25 -0
  85. package/types/tsconfig.json +9 -0
  86. package/LICENSE +0 -21
  87. package/dist/browser/asciidoctor.js +0 -47654
  88. package/dist/browser/asciidoctor.min.js +0 -1452
  89. package/dist/graalvm/asciidoctor.js +0 -47402
  90. package/dist/node/asciidoctor.cjs +0 -21567
  91. package/dist/node/asciidoctor.js +0 -23037
@@ -0,0 +1,1952 @@
1
+ // ESM conversion of extensions.rb
2
+ //
3
+ // Ruby-to-JavaScript notes:
4
+ // - Ruby modules used as mixins → plain JS objects applied via Object.assign.
5
+ // - Ruby :symbols used as keys → plain strings throughout.
6
+ // - Ruby ::Set → JavaScript Set.
7
+ // - Ruby defined? @foo → this._foo !== undefined.
8
+ // - Ruby instance_exec(&block) → block.call(instance) or instance method call.
9
+ // - Ruby singleton_class.enable_dsl → Object.assign(instance, kindClass.DSL).
10
+ // - Ruby class << self → static methods.
11
+ // - Ruby Helpers.resolve_class → typeof fn === 'function' check.
12
+ // - Ruby @@class_var (InlineMacroProcessor.rx_cache) → static property.
13
+ // - Config option keys keep snake_case to match the Ruby/parser convention.
14
+ // - String class-name resolution (e.g. preprocessor 'MyClass') is not supported;
15
+ // pass the class constructor or an instance directly.
16
+ // - Parser.parseBlocks / block.subAttributes / block.assignCaption are forward
17
+ // references; they will be resolved when those modules implement the methods.
18
+
19
+ import { Section } from './section.js'
20
+ import { Block } from './block.js'
21
+ import { List, ListItem } from './list.js'
22
+ import { Inline } from './inline.js'
23
+ import { Reader } from './reader.js'
24
+ import { Parser } from './parser.js'
25
+ import { AttributeList } from './attribute_list.js'
26
+ import { basename } from './helpers.js'
27
+ import { ATTR_REF_HEAD } from './constants.js'
28
+ import { MacroNameRx, CC_ANY } from './rx.js'
29
+
30
+ // ── DSL Mixins ────────────────────────────────────────────────────────────────
31
+
32
+ /**
33
+ * @internal Builder DSL mixin for configuring a Processor instance.
34
+ * Applied to a processor instance via Object.assign(instance, DslMixin).
35
+ *
36
+ * The process() method has dual behaviour (mirrors Ruby's block / no-block):
37
+ * - Called with a single Function argument → stores it as the process block.
38
+ * - Called with non-Function arguments → invokes the stored process block.
39
+ *
40
+ * The this context inside a stored process function is bound to the processor
41
+ * instance at definition time.
42
+ */
43
+ export const ProcessorDsl = {
44
+ option(key, value) {
45
+ this.config[key] = value
46
+ },
47
+
48
+ process(...args) {
49
+ if (args.length === 1 && typeof args[0] === 'function') {
50
+ this._processBlock = args[0].bind(this)
51
+ } else if (this._processBlock !== undefined) {
52
+ return this._processBlock(...args)
53
+ } else {
54
+ throw new Error(
55
+ `${this.constructor.name} #process method called before being registered`
56
+ )
57
+ }
58
+ },
59
+
60
+ processBlockGiven() {
61
+ return this._processBlock !== undefined
62
+ },
63
+ }
64
+
65
+ export const DocumentProcessorDsl = {
66
+ ...ProcessorDsl,
67
+
68
+ prefer() {
69
+ this.option('position', '>>')
70
+ },
71
+
72
+ /** Alias for {@link prefer}. */
73
+ prepend() {
74
+ this.prefer()
75
+ },
76
+ }
77
+
78
+ export const SyntaxProcessorDsl = {
79
+ ...ProcessorDsl,
80
+
81
+ named(value) {
82
+ // When applied to a processor instance, set the name directly.
83
+ // When applied to a class (via static enableDsl), store in config.
84
+ if (this instanceof Processor) {
85
+ this.name = value
86
+ } else {
87
+ this.option('name', value)
88
+ }
89
+ },
90
+
91
+ contentModel(value) {
92
+ this.option('content_model', value)
93
+ },
94
+
95
+ /** Alias for {@link contentModel}. */
96
+ parseContentAs(value) {
97
+ this.option('content_model', value)
98
+ },
99
+
100
+ positionalAttributes(...value) {
101
+ this.option('positional_attrs', value.flat().map(String))
102
+ },
103
+
104
+ /** Alias for {@link positionalAttributes}. */
105
+ namePositionalAttributes(...value) {
106
+ this.option('positional_attrs', value.flat().map(String))
107
+ },
108
+
109
+ positionalAttrs(...value) {
110
+ this.option('positional_attrs', value.flat().map(String))
111
+ },
112
+
113
+ defaultAttributes(value) {
114
+ this.option('default_attrs', value)
115
+ },
116
+
117
+ /** @deprecated Alias for {@link defaultAttributes}. */
118
+ defaultAttrs(value) {
119
+ this.option('default_attrs', value)
120
+ },
121
+
122
+ /**
123
+ * Resolve and register positional attribute names and default values.
124
+ *
125
+ * Accepts any of:
126
+ * resolveAttributes() → positional_attrs: [], default_attrs: {}
127
+ * resolveAttributes('foo', 'bar') → positional maps (Array-style)
128
+ * resolveAttributes({...}) → positional maps (Object-style)
129
+ *
130
+ * Array-style tokens understand positional-index notation (e.g. '1:name',
131
+ * '@:name') and default-value notation (e.g. 'name=value', '1:name=value').
132
+ *
133
+ * @param {...*} args - Positional attribute specifications.
134
+ */
135
+ resolveAttributes(...args) {
136
+ // Normalise: if 0 or 1 argument given, unwrap into a single value.
137
+ if (args.length <= 1) {
138
+ const first = args.length === 0 ? true : args[0]
139
+ if (typeof first === 'string' || typeof first === 'symbol') {
140
+ args = [first]
141
+ } else {
142
+ args = first // true, Array, or plain Object
143
+ }
144
+ }
145
+
146
+ if (args === true) {
147
+ this.option('positional_attrs', [])
148
+ this.option('default_attrs', {})
149
+ } else if (Array.isArray(args)) {
150
+ const names = []
151
+ const defaults = {}
152
+ for (let arg of args) {
153
+ arg = String(arg)
154
+ if (arg.includes('=')) {
155
+ const eqIdx = arg.indexOf('=')
156
+ let name = arg.slice(0, eqIdx)
157
+ const value = arg.slice(eqIdx + 1)
158
+ if (name.includes(':')) {
159
+ const colonIdx = name.indexOf(':')
160
+ const idxStr = name.slice(0, colonIdx)
161
+ name = name.slice(colonIdx + 1)
162
+ const idx = idxStr === '@' ? names.length : parseInt(idxStr, 10)
163
+ names[idx] = name
164
+ }
165
+ defaults[name] = value
166
+ } else if (arg.includes(':')) {
167
+ const colonIdx = arg.indexOf(':')
168
+ const idxStr = arg.slice(0, colonIdx)
169
+ const name = arg.slice(colonIdx + 1)
170
+ const idx = idxStr === '@' ? names.length : parseInt(idxStr, 10)
171
+ names[idx] = name
172
+ } else {
173
+ names.push(arg)
174
+ }
175
+ }
176
+ this.option(
177
+ 'positional_attrs',
178
+ names.filter((n) => n != null)
179
+ )
180
+ this.option('default_attrs', defaults)
181
+ } else if (typeof args === 'object' && args !== null) {
182
+ const names = []
183
+ const defaults = {}
184
+ for (const [key, val] of Object.entries(args)) {
185
+ let name = String(key)
186
+ if (name.includes(':')) {
187
+ const colonIdx = name.indexOf(':')
188
+ const idxStr = name.slice(0, colonIdx)
189
+ name = name.slice(colonIdx + 1)
190
+ const idx = idxStr === '@' ? names.length : parseInt(idxStr, 10)
191
+ names[idx] = name
192
+ }
193
+ if (val) defaults[name] = val
194
+ }
195
+ this.option(
196
+ 'positional_attrs',
197
+ names.filter((n) => n != null)
198
+ )
199
+ this.option('default_attrs', defaults)
200
+ } else {
201
+ throw new Error(`unsupported attributes specification for macro: ${args}`)
202
+ }
203
+ },
204
+
205
+ /** @deprecated Alias for {@link resolveAttributes}. */
206
+ resolvesAttributes(...args) {
207
+ this.resolveAttributes(...args)
208
+ },
209
+ }
210
+
211
+ export const IncludeProcessorDsl = {
212
+ ...DocumentProcessorDsl,
213
+
214
+ handles(...args) {
215
+ if (args.length === 1 && typeof args[0] === 'function') {
216
+ const fn = args[0]
217
+ // Normalise arity-1 handle blocks to accept (doc, target)
218
+ this._handlesBlock =
219
+ fn.length === 1 ? (_doc, target) => fn(target) : fn.bind(this)
220
+ } else if (this._handlesBlock !== undefined) {
221
+ return this._handlesBlock(args[0], args[1])
222
+ } else {
223
+ return true
224
+ }
225
+ },
226
+ }
227
+
228
+ export const DocinfoProcessorDsl = {
229
+ ...DocumentProcessorDsl,
230
+
231
+ atLocation(value) {
232
+ this.option('location', value)
233
+ },
234
+ }
235
+
236
+ export const BlockProcessorDsl = {
237
+ ...SyntaxProcessorDsl,
238
+
239
+ contexts(...value) {
240
+ this.option('contexts', new Set(value.flat()))
241
+ },
242
+
243
+ // aliases
244
+ onContexts(...value) {
245
+ this.contexts(...value)
246
+ },
247
+ onContext(...value) {
248
+ this.contexts(...value)
249
+ },
250
+ bindTo(...value) {
251
+ this.contexts(...value)
252
+ },
253
+ }
254
+
255
+ export const MacroProcessorDsl = {
256
+ ...SyntaxProcessorDsl,
257
+
258
+ /**
259
+ * Override: passing a falsy value sets content_model to :text instead of
260
+ * configuring positional attributes.
261
+ *
262
+ * @param {...*} args - Positional attribute specifications.
263
+ */
264
+ resolveAttributes(...args) {
265
+ if (args.length === 1 && !args[0]) {
266
+ this.option('content_model', 'text')
267
+ } else {
268
+ SyntaxProcessorDsl.resolveAttributes.call(this, ...args)
269
+ this.option('content_model', 'attributes')
270
+ }
271
+ },
272
+
273
+ /** @deprecated Alias for {@link resolveAttributes}. */
274
+ resolvesAttributes(...args) {
275
+ this.resolveAttributes(...args)
276
+ },
277
+ }
278
+
279
+ export const InlineMacroProcessorDsl = {
280
+ ...MacroProcessorDsl,
281
+
282
+ format(value) {
283
+ this.option('format', value)
284
+ },
285
+
286
+ /** Alias for {@link format}. */
287
+ matchFormat(value) {
288
+ this.option('format', value)
289
+ },
290
+ /** @deprecated Alias for {@link format}. */
291
+ usingFormat(value) {
292
+ this.option('format', value)
293
+ },
294
+
295
+ match(value) {
296
+ this.option('regexp', value)
297
+ },
298
+ }
299
+
300
+ // ── Processor ────────────────────────────────────────────────────────────────
301
+
302
+ /**
303
+ * Abstract base class for document and syntax processors.
304
+ *
305
+ * Provides a class-level config map (via static config / static option) and a
306
+ * set of convenience factory methods for creating AST nodes.
307
+ */
308
+ export class Processor {
309
+ /**
310
+ * Get the static configuration map for this processor class.
311
+ * Uses hasOwnProperty to avoid inheriting a parent class's config object
312
+ * through the prototype chain when a subclass first accesses config.
313
+ *
314
+ * @returns {object}
315
+ */
316
+ static get config() {
317
+ if (!Object.hasOwn(this, '_config')) this._config = {}
318
+ return this._config
319
+ }
320
+
321
+ /**
322
+ * Set a default option value for all instances of this processor class.
323
+ *
324
+ * @param {string} key - The option key.
325
+ * @param {*} value - The option value.
326
+ */
327
+ static option(key, value) {
328
+ this.config[key] = value
329
+ }
330
+
331
+ /**
332
+ * Mix the DSL object for this processor class into its prototype.
333
+ */
334
+ static enableDsl() {
335
+ const DSL = this.DSL
336
+ if (DSL) Object.assign(this.prototype, DSL)
337
+ }
338
+ /** Alias for {@link enableDsl}. */
339
+ static useDsl() {
340
+ this.enableDsl()
341
+ }
342
+
343
+ constructor(config = {}) {
344
+ this.config = { ...this.constructor.config, ...config }
345
+ }
346
+
347
+ updateConfig(config) {
348
+ Object.assign(this.config, config)
349
+ }
350
+
351
+ process(..._args) {
352
+ throw new Error(
353
+ `${this.constructor.name} subclass must implement the process method`
354
+ )
355
+ }
356
+
357
+ /**
358
+ * Create a Section node in the same manner as the parser.
359
+ *
360
+ * @param {Section|Document} parent - The parent Section or Document of this new Section.
361
+ * @param {string} title - The String title of the new Section.
362
+ * @param {object} attrs - A plain object of attributes to control how the section is built.
363
+ * Use the style attribute to set the name of a special section (e.g. appendix).
364
+ * Use the id attribute to assign an explicit ID, or set it to false to
365
+ * disable automatic ID generation (when sectids document attribute is set).
366
+ * @param {object} [opts={}] - An optional plain object of options:
367
+ * - level {number} - The Integer level to assign; defaults to parent.level + 1.
368
+ * - numbered {boolean} - Flag to force numbering.
369
+ * @returns {Section} a Section node with all properties properly initialized.
370
+ */
371
+ createSection(parent, title, attrs, opts = {}) {
372
+ const doc = parent.document
373
+ const doctype = doc.doctype
374
+ const book = doctype === 'book'
375
+ const level = opts.level ?? parent.level + 1
376
+
377
+ let sectname,
378
+ special = false
379
+ const style = attrs.style
380
+ if (style) {
381
+ delete attrs.style
382
+ if (book && style === 'abstract') {
383
+ sectname = 'chapter'
384
+ // level intentionally set to 1 (overrides local const)
385
+ Object.defineProperty(opts, '_level', { value: 1 })
386
+ } else {
387
+ sectname = style
388
+ special = true
389
+ if (level === 0) {
390
+ // promote to level 1
391
+ }
392
+ }
393
+ } else if (book) {
394
+ sectname = level === 0 ? 'part' : level > 1 ? 'section' : 'chapter'
395
+ } else if (doctype === 'manpage' && title.toLowerCase() === 'synopsis') {
396
+ sectname = 'synopsis'
397
+ special = true
398
+ } else {
399
+ sectname = 'section'
400
+ }
401
+
402
+ // Re-derive level if style forced it (appendix/abstract style override)
403
+ const effectiveLevel =
404
+ style && book && style === 'abstract'
405
+ ? 1
406
+ : style && special && level === 0
407
+ ? 1
408
+ : level
409
+
410
+ const sect = new Section(parent, effectiveLevel)
411
+ sect.title = title
412
+ sect.sectname = sectname
413
+
414
+ if (special) {
415
+ sect.special = true
416
+ if ('numbered' in opts ? opts.numbered : style === 'appendix') {
417
+ sect.numbered = true
418
+ } else if (!('numbered' in opts) && doc.hasAttribute('sectnums', 'all')) {
419
+ sect.numbered = book && effectiveLevel === 1 ? 'chapter' : true
420
+ }
421
+ } else if (effectiveLevel > 0) {
422
+ if ('numbered' in opts ? opts.numbered : doc.hasAttribute('sectnums')) {
423
+ sect.numbered = sect.special ? !!parent.numbered : true
424
+ }
425
+ } else if (
426
+ 'numbered' in opts ? opts.numbered : book && doc.hasAttribute('partnums')
427
+ ) {
428
+ sect.numbered = true
429
+ }
430
+
431
+ if (attrs.id === false) {
432
+ delete attrs.id
433
+ } else {
434
+ sect.id = attrs.id =
435
+ attrs.id ||
436
+ (doc.hasAttribute('sectids')
437
+ ? Section.generateId(sect.title, doc)
438
+ : null)
439
+ }
440
+ sect.updateAttributes(attrs)
441
+ return sect
442
+ }
443
+
444
+ /**
445
+ * Create a generic block node and link it to the specified parent.
446
+ *
447
+ * @param {Block|Section} parent - The parent node.
448
+ * @param {string} context - The block context (e.g. 'paragraph', 'listing').
449
+ * @param {string|string[]|null} source - The source content.
450
+ * @param {object} attrs - A plain object of attributes.
451
+ * @param {object} [opts={}] - An optional plain object of options.
452
+ * @returns {Block} a Block node with all properties properly initialized.
453
+ */
454
+ createBlock(parent, context, source, attrs, opts = {}) {
455
+ return new Block(parent, context, { source, attributes: attrs, ...opts })
456
+ }
457
+
458
+ /**
459
+ * Create a list node and link it to the specified parent.
460
+ *
461
+ * @param {Block|Section|Document} parent - The parent of this new list.
462
+ * @param {string} context - The list context ('ulist', 'olist', 'colist', 'dlist').
463
+ * @param {object|null} [attrs=null] - A plain object of attributes to set on this list block.
464
+ * @returns {List} a List node with all properties properly initialized.
465
+ */
466
+ createList(parent, context, attrs = null) {
467
+ const list = new List(parent, context)
468
+ if (attrs) list.updateAttributes(attrs)
469
+ return list
470
+ }
471
+
472
+ /**
473
+ * Create a list item node and link it to the specified parent.
474
+ *
475
+ * @param {List} parent - The parent List of this new list item.
476
+ * @param {string|null} [text=null] - The text of the list item.
477
+ * @returns {ListItem} a ListItem node with all properties properly initialized.
478
+ */
479
+ createListItem(parent, text = null) {
480
+ return new ListItem(parent, text)
481
+ }
482
+
483
+ /**
484
+ * Create an image block node and link it to the specified parent.
485
+ *
486
+ * @param {Block|Section|Document} parent - The parent of this new image block.
487
+ * @param {object} attrs - A plain object of attributes to control how the image block is built.
488
+ * The target attribute sets the image source; alt sets the alt text.
489
+ * @param {object} [opts={}] - An optional plain object of options.
490
+ * @returns {Block} a Block node with all properties properly initialized.
491
+ */
492
+ createImageBlock(parent, attrs, opts = {}) {
493
+ const target = attrs.target
494
+ if (!target)
495
+ throw new Error(
496
+ 'Unable to create an image block, target attribute is required'
497
+ )
498
+ if (!attrs.alt)
499
+ attrs.alt = attrs['default-alt'] = basename(target, true).replace(
500
+ /[_-]/g,
501
+ ' '
502
+ )
503
+ const title = 'title' in attrs ? attrs.title : null
504
+ if (title !== null) delete attrs.title
505
+ const block = this.createBlock(parent, 'image', null, attrs, opts)
506
+ if (title) {
507
+ block.title = title
508
+ const caption = attrs.caption
509
+ delete attrs.caption
510
+ block.assignCaption(caption, 'figure')
511
+ }
512
+ return block
513
+ }
514
+
515
+ /**
516
+ * Create an inline node and bind it to the specified parent.
517
+ *
518
+ * @param {Block} parent - The parent Block of this new inline node.
519
+ * @param {string} context - The context of the inline node ('quoted', 'anchor', etc.).
520
+ * @param {string} text - The text of the inline node.
521
+ * @param {object} [opts={}] - An optional plain object of options:
522
+ * - type {string} - The subtype of the inline node context.
523
+ * - attributes {object} - Attributes to set on the inline node.
524
+ * @returns {Inline} an Inline node with all properties properly initialized.
525
+ */
526
+ createInline(parent, context, text, opts = {}) {
527
+ const options = context === 'quoted' ? { type: 'unquoted', ...opts } : opts
528
+ return new Inline(parent, context, text, options)
529
+ }
530
+
531
+ /**
532
+ * Parse blocks in the content and attach them to the parent.
533
+ *
534
+ * @param {Block|Section} parent - The parent node.
535
+ * @param {string[]|Reader} content - Lines or a Reader.
536
+ * @param {object|null} [attributes=null] - Attributes to pass to the parser.
537
+ * @returns {Promise<Block|Section>} the parent node into which the blocks are parsed.
538
+ */
539
+ async parseContent(parent, content, attributes = null) {
540
+ const reader = content instanceof Reader ? content : new Reader(content)
541
+ await Parser.parseBlocks(reader, parent, attributes)
542
+ return parent
543
+ }
544
+
545
+ /**
546
+ * Parse the attrlist String into a plain object of attributes.
547
+ *
548
+ * @param {Block|Section} block - The current block (used for applying subs).
549
+ * @param {string} attrlist - The list of attributes as a String.
550
+ * @param {object} [opts={}] - An optional plain object of options:
551
+ * - positional_attributes {string[]} - Array of attribute names to map positional args to.
552
+ * - sub_attributes {boolean} - Enables attribute substitution on attrlist.
553
+ * @returns {Promise<object>} a plain object of parsed attributes.
554
+ */
555
+ async parseAttributes(block, attrlist, opts = {}) {
556
+ if (!attrlist || attrlist.length === 0) return {}
557
+ if (opts.sub_attributes && attrlist.includes(ATTR_REF_HEAD)) {
558
+ attrlist = block.subAttributes(attrlist)
559
+ }
560
+ return new AttributeList(attrlist).parse(opts.positional_attributes || [])
561
+ }
562
+
563
+ /** Shorthand for {@link createBlock} with context 'paragraph'. */
564
+ createParagraph(parent, ...rest) {
565
+ return this.createBlock(parent, 'paragraph', ...rest)
566
+ }
567
+
568
+ /** Shorthand for {@link createBlock} with context 'open'. */
569
+ createOpenBlock(parent, ...rest) {
570
+ return this.createBlock(parent, 'open', ...rest)
571
+ }
572
+
573
+ /** Shorthand for {@link createBlock} with context 'example'. */
574
+ createExampleBlock(parent, ...rest) {
575
+ return this.createBlock(parent, 'example', ...rest)
576
+ }
577
+
578
+ /** Shorthand for {@link createBlock} with context 'pass'. */
579
+ createPassBlock(parent, ...rest) {
580
+ return this.createBlock(parent, 'pass', ...rest)
581
+ }
582
+
583
+ /** Shorthand for {@link createBlock} with context 'listing'. */
584
+ createListingBlock(parent, ...rest) {
585
+ return this.createBlock(parent, 'listing', ...rest)
586
+ }
587
+
588
+ /** Shorthand for {@link createBlock} with context 'literal'. */
589
+ createLiteralBlock(parent, ...rest) {
590
+ return this.createBlock(parent, 'literal', ...rest)
591
+ }
592
+
593
+ /** Shorthand for {@link createInline} with context 'anchor'. */
594
+ createAnchor(parent, ...rest) {
595
+ return this.createInline(parent, 'anchor', ...rest)
596
+ }
597
+
598
+ /** Shorthand for {@link createInline} with context 'quoted'. */
599
+ createInlinePass(parent, ...rest) {
600
+ return this.createInline(parent, 'quoted', ...rest)
601
+ }
602
+ }
603
+
604
+ // ── Document processors ───────────────────────────────────────────────────────
605
+
606
+ /**
607
+ * Preprocessors are run after the source text is split into lines and
608
+ * normalised, but before parsing begins.
609
+ *
610
+ * Asciidoctor passes the document and the document's Reader to the process
611
+ * method of the Preprocessor instance. The Preprocessor can modify the Reader
612
+ * as necessary and either return the same Reader (or falsy) or a substitute Reader.
613
+ *
614
+ * Implementations must extend Preprocessor and override {@link process}.
615
+ *
616
+ * @example
617
+ * class CommentStripPreprocessor extends Preprocessor {
618
+ * process(document, reader) {
619
+ * const lines = reader.getLines().filter((l) => !l.startsWith('//'))
620
+ * reader.pushInclude(lines, null, null, 1, {})
621
+ * return reader
622
+ * }
623
+ * }
624
+ * // Register:
625
+ * Extensions.register(function () { this.preprocessor(CommentStripPreprocessor) })
626
+ */
627
+ export class Preprocessor extends Processor {
628
+ process(_document, _reader) {
629
+ throw new Error(
630
+ `${this.constructor.name} must implement the process method`
631
+ )
632
+ }
633
+ }
634
+ Preprocessor.DSL = DocumentProcessorDsl
635
+
636
+ /**
637
+ * TreeProcessors are run on the Document after the source has been
638
+ * parsed into an abstract syntax tree (AST).
639
+ *
640
+ * Implementations must extend TreeProcessor and override {@link process}.
641
+ *
642
+ * @example
643
+ * class ShoutTreeProcessor extends TreeProcessor {
644
+ * process(document) {
645
+ * for (const block of document.findBy({ context: 'paragraph' })) {
646
+ * block.source = block.source.toUpperCase()
647
+ * }
648
+ * }
649
+ * }
650
+ * Extensions.register(function () { this.treeProcessor(ShoutTreeProcessor) })
651
+ */
652
+ export class TreeProcessor extends Processor {
653
+ process(_document) {
654
+ throw new Error(
655
+ `${this.constructor.name} must implement the process method`
656
+ )
657
+ }
658
+ }
659
+ TreeProcessor.DSL = DocumentProcessorDsl
660
+
661
+ /** @deprecated Alias for {@link TreeProcessor} kept for backwards compatibility. */
662
+ export const Treeprocessor = TreeProcessor
663
+
664
+ /**
665
+ * Postprocessors are run after the document is converted, but before
666
+ * it is written to the output stream.
667
+ *
668
+ * The `process` method receives the document and the converted output string, and
669
+ * must return the (possibly modified) output string.
670
+ *
671
+ * Implementations must extend Postprocessor and override {@link process}.
672
+ *
673
+ * @example
674
+ * class FooterPostprocessor extends Postprocessor {
675
+ * process(document, output) {
676
+ * return output.replace('</body>', '<footer>Generated by Acme</footer></body>')
677
+ * }
678
+ * }
679
+ * Extensions.register(function () { this.postprocessor(FooterPostprocessor) })
680
+ */
681
+ export class Postprocessor extends Processor {
682
+ process(_document, _output) {
683
+ throw new Error(
684
+ `${this.constructor.name} must implement the process method`
685
+ )
686
+ }
687
+ }
688
+ Postprocessor.DSL = DocumentProcessorDsl
689
+
690
+ /**
691
+ * IncludeProcessors handle include::<target>[] directives.
692
+ *
693
+ * Implementations must extend IncludeProcessor.
694
+ */
695
+ export class IncludeProcessor extends Processor {
696
+ process(_document, _reader, _target, _attributes) {
697
+ throw new Error(
698
+ `${this.constructor.name} must implement the process method`
699
+ )
700
+ }
701
+
702
+ handles(_doc, _target) {
703
+ return true
704
+ }
705
+ }
706
+ IncludeProcessor.DSL = IncludeProcessorDsl
707
+
708
+ /**
709
+ * DocinfoProcessors add additional content to the header and/or footer
710
+ * of the generated document.
711
+ *
712
+ * Implementations must extend DocinfoProcessor.
713
+ */
714
+ export class DocinfoProcessor extends Processor {
715
+ constructor(config = {}) {
716
+ super(config)
717
+ this.config.location ??= 'head'
718
+ }
719
+
720
+ process(_document) {
721
+ throw new Error(
722
+ `${this.constructor.name} must implement the process method`
723
+ )
724
+ }
725
+ }
726
+ DocinfoProcessor.DSL = DocinfoProcessorDsl
727
+
728
+ // ── Syntax processors ─────────────────────────────────────────────────────────
729
+
730
+ /**
731
+ * BlockProcessors handle delimited blocks and paragraphs with a custom name.
732
+ *
733
+ * The `process(parent, reader, attributes)` method receives:
734
+ * - `parent` {AbstractBlock} — the enclosing block
735
+ * - `reader` {Reader} — positioned at the block content
736
+ * - `attributes` {Object} — parsed block attributes
737
+ *
738
+ * It must return a block node (created with `this.createBlock(...)`, etc.)
739
+ * or call `this.parseContent(parent, reader)` to delegate parsing.
740
+ *
741
+ * Use the static `config` object or the DSL helpers (`contexts()`,
742
+ * `contentModel()`, `resolveAttributes()`, …) to declare the block's behaviour
743
+ * before registering.
744
+ *
745
+ * Implementations must extend BlockProcessor and override {@link process}.
746
+ *
747
+ * @example <caption>Custom delimited block that wraps content in a div</caption>
748
+ * class ShoutBlock extends BlockProcessor {
749
+ * process(parent, reader, attrs) {
750
+ * const block = this.createBlock(parent, 'paragraph', reader.readLines().join('\n').toUpperCase(), attrs)
751
+ * return block
752
+ * }
753
+ * }
754
+ * ShoutBlock.config = { name: 'shout', contexts: ['paragraph'], content_model: 'simple' }
755
+ * Extensions.register(function () { this.block(ShoutBlock) })
756
+ * // AsciiDoc usage: [shout]\nHello world
757
+ */
758
+ export class BlockProcessor extends Processor {
759
+ constructor(name = null, config = {}) {
760
+ super(config)
761
+ this.name = name || this.config.name || null
762
+
763
+ // Normalise contexts config to a Set.
764
+ const ctx = this.config.contexts
765
+ if (ctx == null) {
766
+ this.config.contexts = new Set(['open', 'paragraph'])
767
+ } else if (typeof ctx === 'string') {
768
+ this.config.contexts = new Set([ctx])
769
+ } else if (Array.isArray(ctx)) {
770
+ this.config.contexts = new Set(ctx)
771
+ }
772
+
773
+ this.config.content_model ??= 'compound'
774
+ }
775
+
776
+ process(_parent, _reader, _attributes) {
777
+ throw new Error(
778
+ `${this.constructor.name} must implement the process method`
779
+ )
780
+ }
781
+ }
782
+ BlockProcessor.DSL = BlockProcessorDsl
783
+
784
+ /**
785
+ * Base class shared by BlockMacroProcessor and InlineMacroProcessor.
786
+ */
787
+ export class MacroProcessor extends Processor {
788
+ constructor(name = null, config = {}) {
789
+ super(config)
790
+ this.name = name || this.config.name || null
791
+ this.config.content_model ??= 'attributes'
792
+ }
793
+
794
+ process(_parent, _target, _attributes) {
795
+ throw new Error(
796
+ `${this.constructor.name} must implement the process method`
797
+ )
798
+ }
799
+ }
800
+
801
+ /**
802
+ * BlockMacroProcessors handle block macros with a custom name.
803
+ *
804
+ * The `process(parent, target, attributes)` method receives:
805
+ * - `parent` {AbstractBlock} — the enclosing block
806
+ * - `target` {string} — the macro target (text between `name:` and `[`)
807
+ * - `attributes` {Object} — parsed macro attributes
808
+ *
809
+ * It must return a block node created with one of the `createBlock`,
810
+ * `createImage`, etc. helpers inherited from {@link Processor}.
811
+ *
812
+ * Implementations must extend BlockMacroProcessor and override {@link process}.
813
+ *
814
+ * @example <caption>Block macro that embeds a GitHub Gist</caption>
815
+ * class GistBlockMacro extends BlockMacroProcessor {
816
+ * process(parent, target, attrs) {
817
+ * const html = `<script src="https://gist.github.com/${target}.js"></script>`
818
+ * return this.createBlock(parent, 'pass', html)
819
+ * }
820
+ * }
821
+ * GistBlockMacro.config = { name: 'gist' }
822
+ * Extensions.register(function () { this.blockMacro(GistBlockMacro) })
823
+ * // AsciiDoc usage: gist::abc123[]
824
+ */
825
+ export class BlockMacroProcessor extends MacroProcessor {
826
+ /**
827
+ * Get the name, validating the format.
828
+ * The setter stores without validation to avoid throwing during construction.
829
+ */
830
+ get name() {
831
+ if (this._name != null && !MacroNameRx.test(String(this._name))) {
832
+ throw new Error(`invalid name for block macro: ${this._name}`)
833
+ }
834
+ return this._name
835
+ }
836
+
837
+ set name(value) {
838
+ this._name = value
839
+ }
840
+ }
841
+ BlockMacroProcessor.DSL = MacroProcessorDsl
842
+
843
+ /**
844
+ * InlineMacroProcessors handle inline macros with a custom name.
845
+ *
846
+ * The `process(parent, target, attributes)` method receives:
847
+ * - `parent` {AbstractBlock} — the enclosing block
848
+ * - `target` {string} — the macro target (text between `name:` and `[`)
849
+ * - `attributes` {Object} — parsed macro attributes (first positional attr is `attributes[1]`)
850
+ *
851
+ * It must return an {@link Inline} node created with `this.createInline(parent, 'quoted', text, opts)`.
852
+ *
853
+ * By default the macro format is `'long'` (`name:target[attrs]`). Set
854
+ * `config.format = 'short'` for a no-target form (`name:[attrs]`).
855
+ *
856
+ * Implementations must extend InlineMacroProcessor and override {@link process}.
857
+ *
858
+ * @example <caption>Inline macro that generates a keyboard shortcut span</caption>
859
+ * class KbdInlineMacro extends InlineMacroProcessor {
860
+ * process(parent, target, attrs) {
861
+ * return this.createInline(parent, 'quoted', target, { type: 'monospaced' })
862
+ * }
863
+ * }
864
+ * KbdInlineMacro.config = { name: 'kbd', format: 'short' }
865
+ * Extensions.register(function () { this.inlineMacro(KbdInlineMacro) })
866
+ * // AsciiDoc usage: kbd:[Ctrl+C]
867
+ */
868
+ export class InlineMacroProcessor extends MacroProcessor {
869
+ static rxCache = new Map()
870
+
871
+ /**
872
+ * Look up (and memoize) the regexp for this inline macro processor.
873
+ *
874
+ * @returns {RegExp}
875
+ */
876
+ get regexp() {
877
+ return (this.config.regexp ??= this.resolveRegexp(
878
+ String(this.name),
879
+ this.config.format
880
+ ))
881
+ }
882
+
883
+ resolveRegexp(name, format) {
884
+ if (!MacroNameRx.test(name)) {
885
+ throw new Error(`invalid name for inline macro: ${name}`)
886
+ }
887
+ const key = `${name}:${format}`
888
+ if (!InlineMacroProcessor.rxCache.has(key)) {
889
+ const targetPart = format === 'short' ? '(){0}' : '(\\S+?)'
890
+ InlineMacroProcessor.rxCache.set(
891
+ key,
892
+ new RegExp(
893
+ `\\\\?${name}:${targetPart}\\[(|(?:${CC_ANY})*?(?<!\\\\))\\]`
894
+ )
895
+ )
896
+ }
897
+ return InlineMacroProcessor.rxCache.get(key)
898
+ }
899
+ }
900
+ InlineMacroProcessor.DSL = InlineMacroProcessorDsl
901
+
902
+ // ── Extension proxy objects ───────────────────────────────────────────────────
903
+
904
+ /**
905
+ * Proxy that encapsulates the extension kind, config, and instance.
906
+ * This is what gets stored in the extension registry when activated.
907
+ */
908
+ export class Extension {
909
+ constructor(kind, instance, config) {
910
+ this.kind = kind
911
+ this.instance = instance
912
+ this.config = config
913
+ }
914
+ }
915
+
916
+ /**
917
+ * Specialisation of Extension that additionally stores a reference
918
+ * to the process method, accommodating both class-based processors and function blocks.
919
+ */
920
+ export class ProcessorExtension extends Extension {
921
+ constructor(kind, instance, processMethod = null) {
922
+ super(kind, instance, instance.config)
923
+ this.processMethod =
924
+ processMethod || ((...args) => instance.process(...args))
925
+ }
926
+ }
927
+
928
+ // ── Group ─────────────────────────────────────────────────────────────────────
929
+
930
+ /**
931
+ * A Group registers one or more extensions with a Registry.
932
+ *
933
+ * Subclass Group and pass the subclass to Extensions.register(), or call
934
+ * the static register() method directly.
935
+ */
936
+ export class Group {
937
+ static register(name = null) {
938
+ Extensions.register(name, this)
939
+ }
940
+
941
+ activate(_registry) {
942
+ throw new Error(
943
+ `${this.constructor.name} must implement the activate method`
944
+ )
945
+ }
946
+ }
947
+
948
+ // ── Registry ──────────────────────────────────────────────────────────────────
949
+
950
+ /** @internal Maps kind name → document-processor class. */
951
+ const DOCUMENT_PROCESSOR_CLASSES = {
952
+ preprocessor: Preprocessor,
953
+ tree_processor: TreeProcessor,
954
+ postprocessor: Postprocessor,
955
+ include_processor: IncludeProcessor,
956
+ docinfo_processor: DocinfoProcessor,
957
+ }
958
+
959
+ /** @internal Maps kind name → syntax-processor class. */
960
+ const SYNTAX_PROCESSOR_CLASSES = {
961
+ block: BlockProcessor,
962
+ block_macro: BlockMacroProcessor,
963
+ inline_macro: InlineMacroProcessor,
964
+ }
965
+
966
+ /**
967
+ * The primary entry point into the extension system.
968
+ *
969
+ * Registry holds the extensions which have been registered and activated, has
970
+ * methods for registering or defining a processor and looks up extensions
971
+ * stored in the registry during parsing.
972
+ */
973
+ export class Registry {
974
+ constructor(groups = {}) {
975
+ this.groups = groups
976
+ this._reset()
977
+ }
978
+
979
+ /**
980
+ * Activate all global extension Groups and the Groups associated with this registry.
981
+ *
982
+ * @param {Document} document - The Document on which the extensions are to be used.
983
+ * @returns {Registry} this Registry.
984
+ */
985
+ activate(document) {
986
+ if (this.document) this._reset()
987
+ this.document = document
988
+ const extGroups = [
989
+ ...Object.values(Extensions.groups()),
990
+ ...Object.values(this.groups),
991
+ ]
992
+ for (const group of extGroups) {
993
+ if (typeof group === 'function') {
994
+ // Check if it is a class (constructor) with an activate prototype method.
995
+ if (group.prototype && typeof group.prototype.activate === 'function') {
996
+ new group().activate(this)
997
+ } else {
998
+ // Plain function — call in the context of this registry (like instance_exec).
999
+ group.length === 0 ? group.call(this) : group(this)
1000
+ }
1001
+ } else if (group && typeof group.activate === 'function') {
1002
+ group.activate(this)
1003
+ }
1004
+ }
1005
+ return this
1006
+ }
1007
+
1008
+ /**
1009
+ * Register a Preprocessor with the extension registry.
1010
+ *
1011
+ * The processor may be:
1012
+ * - A Preprocessor subclass (constructor function)
1013
+ * - An instance of a Preprocessor subclass
1014
+ * - A Function that configures the processor via the DSL (block style)
1015
+ *
1016
+ * @example
1017
+ * // class style
1018
+ * preprocessor(FrontMatterPreprocessor)
1019
+ * // instance style
1020
+ * preprocessor(new FrontMatterPreprocessor())
1021
+ * // block style
1022
+ * preprocessor(function () {
1023
+ * this.process(function (doc, reader) { ... })
1024
+ * })
1025
+ *
1026
+ * @param {...*} args - Class constructor, instance, or block function.
1027
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1028
+ */
1029
+ preprocessor(...args) {
1030
+ return this._addDocumentProcessor('preprocessor', args)
1031
+ }
1032
+
1033
+ /**
1034
+ * Return the registered Preprocessor extensions, or null if none.
1035
+ *
1036
+ * @returns {ProcessorExtension[]|null}
1037
+ */
1038
+ preprocessors() {
1039
+ return this._preprocessor_extensions
1040
+ }
1041
+
1042
+ /**
1043
+ * Check whether any Preprocessor extensions have been registered.
1044
+ *
1045
+ * @returns {boolean}
1046
+ */
1047
+ hasPreprocessors() {
1048
+ return !!this._preprocessor_extensions
1049
+ }
1050
+
1051
+ /** @internal Core API compatibility alias for preprocessors(). */
1052
+ get preprocessor_extensions() {
1053
+ return this._preprocessor_extensions
1054
+ }
1055
+
1056
+ /**
1057
+ * Register a TreeProcessor with the extension registry.
1058
+ *
1059
+ * @param {...*} args - Class constructor, instance, or block function.
1060
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1061
+ */
1062
+ treeProcessor(...args) {
1063
+ return this._addDocumentProcessor('tree_processor', args)
1064
+ }
1065
+
1066
+ /** @deprecated Alias for {@link treeProcessor}. */
1067
+ treeprocessor(...args) {
1068
+ return this.treeProcessor(...args)
1069
+ }
1070
+
1071
+ /** Alias for {@link treeProcessor} (snake_case for prefer() / Registry method dispatch). */
1072
+ tree_processor(...args) {
1073
+ return this.treeProcessor(...args)
1074
+ }
1075
+
1076
+ /**
1077
+ * Return the registered TreeProcessor extensions, or null if none.
1078
+ *
1079
+ * @returns {ProcessorExtension[]|null}
1080
+ */
1081
+ treeProcessors() {
1082
+ return this._tree_processor_extensions
1083
+ }
1084
+
1085
+ /**
1086
+ * Check whether any TreeProcessor extensions have been registered.
1087
+ *
1088
+ * @returns {boolean}
1089
+ */
1090
+ hasTreeProcessors() {
1091
+ return !!this._tree_processor_extensions
1092
+ }
1093
+
1094
+ /** @deprecated Typo alias kept for backward compatibility. Use {@link hasTreeProcessors}. */
1095
+ hasTeeProcessors() {
1096
+ return !!this._tree_processor_extensions
1097
+ }
1098
+
1099
+ /** @deprecated Alias for {@link treeProcessors}. */
1100
+ treeprocessors() {
1101
+ return this._tree_processor_extensions
1102
+ }
1103
+
1104
+ /** @internal Core API compatibility alias for treeProcessors(). */
1105
+ get tree_processor_extensions() {
1106
+ return this._tree_processor_extensions
1107
+ }
1108
+
1109
+ /**
1110
+ * Register a Postprocessor with the extension registry.
1111
+ *
1112
+ * @param {...*} args - Class constructor, instance, or block function.
1113
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1114
+ */
1115
+ postprocessor(...args) {
1116
+ return this._addDocumentProcessor('postprocessor', args)
1117
+ }
1118
+
1119
+ /**
1120
+ * Return the registered Postprocessor extensions, or null if none.
1121
+ *
1122
+ * @returns {ProcessorExtension[]|null}
1123
+ */
1124
+ postprocessors() {
1125
+ return this._postprocessor_extensions
1126
+ }
1127
+
1128
+ /**
1129
+ * Check whether any Postprocessor extensions have been registered.
1130
+ *
1131
+ * @returns {boolean}
1132
+ */
1133
+ hasPostprocessors() {
1134
+ return !!this._postprocessor_extensions
1135
+ }
1136
+
1137
+ /** @internal Core API compatibility alias for postprocessors(). */
1138
+ get postprocessor_extensions() {
1139
+ return this._postprocessor_extensions
1140
+ }
1141
+
1142
+ /**
1143
+ * Register an IncludeProcessor with the extension registry.
1144
+ *
1145
+ * @param {...*} args - Class constructor, instance, or block function.
1146
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1147
+ */
1148
+ includeProcessor(...args) {
1149
+ return this._addDocumentProcessor('include_processor', args)
1150
+ }
1151
+
1152
+ /**
1153
+ * Return the registered IncludeProcessor extensions, or null if none.
1154
+ *
1155
+ * @returns {ProcessorExtension[]|null}
1156
+ */
1157
+ includeProcessors() {
1158
+ return this._include_processor_extensions
1159
+ }
1160
+
1161
+ /**
1162
+ * Check whether any IncludeProcessor extensions have been registered.
1163
+ *
1164
+ * @returns {boolean}
1165
+ */
1166
+ hasIncludeProcessors() {
1167
+ return !!this._include_processor_extensions
1168
+ }
1169
+
1170
+ /** Alias for {@link includeProcessor} (snake_case for prefer() / Registry method dispatch). */
1171
+ include_processor(...args) {
1172
+ return this.includeProcessor(...args)
1173
+ }
1174
+
1175
+ /** @internal Core API compatibility alias for includeProcessors(). */
1176
+ get include_processor_extensions() {
1177
+ return this._include_processor_extensions
1178
+ }
1179
+
1180
+ /**
1181
+ * Register a DocinfoProcessor with the extension registry.
1182
+ *
1183
+ * @param {...*} args - Class constructor, instance, or block function.
1184
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1185
+ */
1186
+ docinfoProcessor(...args) {
1187
+ return this._addDocumentProcessor('docinfo_processor', args)
1188
+ }
1189
+
1190
+ /**
1191
+ * Check whether any DocinfoProcessor extensions have been registered.
1192
+ *
1193
+ * @param {string|null} [location=null] - Optional location ('head' or 'footer') to filter by.
1194
+ * @returns {boolean}
1195
+ */
1196
+ hasDocinfoProcessors(location = null) {
1197
+ if (!this._docinfo_processor_extensions) return false
1198
+ if (location) {
1199
+ return this._docinfo_processor_extensions.some(
1200
+ (ext) => ext.config.location === location
1201
+ )
1202
+ }
1203
+ return true
1204
+ }
1205
+
1206
+ /**
1207
+ * Retrieve Extension proxy objects for DocinfoProcessor instances.
1208
+ *
1209
+ * @param {string|null} [location=null] - Optional location ('head' or 'footer') to filter by.
1210
+ * @returns {ProcessorExtension[]} array of Extension proxy objects.
1211
+ */
1212
+ docinfoProcessors(location = null) {
1213
+ if (!this._docinfo_processor_extensions) return []
1214
+ if (location) {
1215
+ return this._docinfo_processor_extensions.filter(
1216
+ (ext) => ext.config.location === location
1217
+ )
1218
+ }
1219
+ return this._docinfo_processor_extensions
1220
+ }
1221
+
1222
+ /** Alias for {@link docinfoProcessor} (snake_case for prefer() / Registry method dispatch). */
1223
+ docinfo_processor(...args) {
1224
+ return this.docinfoProcessor(...args)
1225
+ }
1226
+
1227
+ /** @internal Core API compatibility alias for docinfoProcessors(). */
1228
+ get docinfo_processor_extensions() {
1229
+ return this._docinfo_processor_extensions
1230
+ }
1231
+
1232
+ /**
1233
+ * Register a BlockProcessor with the extension registry.
1234
+ *
1235
+ * @example
1236
+ * // class style
1237
+ * block(ShoutBlock)
1238
+ * // class style with explicit name
1239
+ * block(ShoutBlock, 'shout')
1240
+ * // block style
1241
+ * block(function () {
1242
+ * this.named('shout')
1243
+ * this.process(function (parent, reader, attrs) { ... })
1244
+ * })
1245
+ * // block style with explicit name
1246
+ * block('shout', function () {
1247
+ * this.process(function (parent, reader, attrs) { ... })
1248
+ * })
1249
+ *
1250
+ * @param {...*} args - Class constructor, instance, block function, or name + one of those.
1251
+ * @returns {ProcessorExtension} an Extension proxy object.
1252
+ */
1253
+ block(...args) {
1254
+ return this._addSyntaxProcessor('block', args)
1255
+ }
1256
+
1257
+ /**
1258
+ * Check whether any BlockProcessor extensions have been registered.
1259
+ *
1260
+ * @returns {boolean}
1261
+ */
1262
+ hasBlocks() {
1263
+ return !!this._block_extensions
1264
+ }
1265
+
1266
+ /**
1267
+ * Check whether a BlockProcessor is registered for the given name and context.
1268
+ *
1269
+ * @param {string} name - The block name.
1270
+ * @param {string} context - The block context.
1271
+ * @returns {ProcessorExtension|false} the Extension proxy or false.
1272
+ */
1273
+ registeredForBlock(name, context) {
1274
+ const ext = this._block_extensions?.[String(name)]
1275
+ return ext ? ext.config.contexts.has(context) && ext : false
1276
+ }
1277
+
1278
+ /**
1279
+ * Retrieve the Extension proxy for the BlockProcessor registered with the given name.
1280
+ *
1281
+ * @param {string} name - The block name.
1282
+ * @returns {ProcessorExtension|null}
1283
+ */
1284
+ findBlockExtension(name) {
1285
+ return this._block_extensions?.[String(name)] ?? null
1286
+ }
1287
+
1288
+ /**
1289
+ * Register a BlockMacroProcessor with the extension registry.
1290
+ *
1291
+ * @param {...*} args - Class constructor, instance, or block function.
1292
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1293
+ */
1294
+ blockMacro(...args) {
1295
+ return this._addSyntaxProcessor('block_macro', args)
1296
+ }
1297
+
1298
+ /** @deprecated Alias for {@link blockMacro}. */
1299
+ block_macro(...args) {
1300
+ return this.blockMacro(...args)
1301
+ }
1302
+
1303
+ /**
1304
+ * Check whether any BlockMacroProcessor extensions have been registered.
1305
+ *
1306
+ * @returns {boolean}
1307
+ */
1308
+ hasBlockMacros() {
1309
+ return !!this._block_macro_extensions
1310
+ }
1311
+
1312
+ /**
1313
+ * Check whether a BlockMacroProcessor is registered for the given name.
1314
+ *
1315
+ * @param {string} name - The macro name.
1316
+ * @returns {ProcessorExtension|false}
1317
+ */
1318
+ registeredForBlockMacro(name) {
1319
+ return this._block_macro_extensions?.[String(name)] || false
1320
+ }
1321
+
1322
+ /**
1323
+ * Retrieve the Extension proxy for the BlockMacroProcessor registered with the given name.
1324
+ *
1325
+ * @param {string} name - The macro name.
1326
+ * @returns {ProcessorExtension|null}
1327
+ */
1328
+ findBlockMacroExtension(name) {
1329
+ return this._block_macro_extensions?.[String(name)] ?? null
1330
+ }
1331
+
1332
+ /**
1333
+ * Register an InlineMacroProcessor with the extension registry.
1334
+ *
1335
+ * @param {...*} args - Class constructor, instance, or block function.
1336
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1337
+ */
1338
+ inlineMacro(...args) {
1339
+ return this._addSyntaxProcessor('inline_macro', args)
1340
+ }
1341
+
1342
+ /** @deprecated Alias for {@link inlineMacro}. */
1343
+ inline_macro(...args) {
1344
+ return this.inlineMacro(...args)
1345
+ }
1346
+
1347
+ /**
1348
+ * Check whether any InlineMacroProcessor extensions have been registered.
1349
+ *
1350
+ * @returns {boolean}
1351
+ */
1352
+ hasInlineMacros() {
1353
+ return !!this._inline_macro_extensions
1354
+ }
1355
+
1356
+ /**
1357
+ * Check whether an InlineMacroProcessor is registered for the given name.
1358
+ *
1359
+ * @param {string} name - The macro name.
1360
+ * @returns {ProcessorExtension|false}
1361
+ */
1362
+ registeredForInlineMacro(name) {
1363
+ return this._inline_macro_extensions?.[String(name)] || false
1364
+ }
1365
+
1366
+ /**
1367
+ * Retrieve the Extension proxy for the InlineMacroProcessor registered with the given name.
1368
+ *
1369
+ * @param {string} name - The macro name.
1370
+ * @returns {ProcessorExtension|null}
1371
+ */
1372
+ findInlineMacroExtension(name) {
1373
+ return this._inline_macro_extensions?.[String(name)] ?? null
1374
+ }
1375
+
1376
+ /**
1377
+ * Retrieve all InlineMacroProcessor Extension proxy objects.
1378
+ *
1379
+ * @returns {ProcessorExtension[]}
1380
+ */
1381
+ inlineMacros() {
1382
+ return this._inline_macro_extensions
1383
+ ? Object.values(this._inline_macro_extensions)
1384
+ : []
1385
+ }
1386
+
1387
+ /**
1388
+ * Insert the document-processor Extension as the first of its kind in the extension registry.
1389
+ *
1390
+ * @example
1391
+ * registry.prefer('includeProcessor', function () {
1392
+ * this.process(function (document, reader, target, attrs) { ... })
1393
+ * })
1394
+ *
1395
+ * @param {...*} args - A ProcessorExtension, or a method name followed by processor args.
1396
+ * @returns {ProcessorExtension} the Extension stored in the registry.
1397
+ */
1398
+ prefer(...args) {
1399
+ const arg0 = args.shift()
1400
+ let extension
1401
+ if (arg0 instanceof ProcessorExtension) {
1402
+ extension = arg0
1403
+ } else {
1404
+ // arg0 is a method name; remaining args include the processor and optional block.
1405
+ extension = this[arg0](...args)
1406
+ }
1407
+ const storeKey = `_${extension.kind}_extensions`
1408
+ const store = this[storeKey]
1409
+ if (Array.isArray(store)) {
1410
+ const idx = store.indexOf(extension)
1411
+ if (idx > -1) store.splice(idx, 1)
1412
+ store.unshift(extension)
1413
+ }
1414
+ return extension
1415
+ }
1416
+
1417
+ // ── Private helpers ──────────────────────────────────────────────────────────
1418
+
1419
+ /** @internal */
1420
+ _addDocumentProcessor(kind, args) {
1421
+ const kindName = kind.replace(/_/g, ' ')
1422
+ const kindClass = DOCUMENT_PROCESSOR_CLASSES[kind]
1423
+ if (!this[`_${kind}_extensions`]) this[`_${kind}_extensions`] = []
1424
+ const store = this[`_${kind}_extensions`]
1425
+
1426
+ // Detect block style: last argument is a function that is NOT a class constructor.
1427
+ // Class constructors (ES6 classes) have a non-writable prototype descriptor;
1428
+ // plain functions (used as DSL blocks) have a writable prototype.
1429
+ const lastArg = args[args.length - 1]
1430
+ const hasBlock =
1431
+ args.length > 0 &&
1432
+ typeof lastArg === 'function' &&
1433
+ !!(
1434
+ Object.getOwnPropertyDescriptor(lastArg, 'prototype')?.writable ?? true
1435
+ )
1436
+
1437
+ let processorInstance
1438
+
1439
+ if (hasBlock) {
1440
+ const block = args.pop()
1441
+ const config = this._resolveArgs(args, 1)
1442
+ const processor = new kindClass(config)
1443
+ Object.assign(processor, kindClass.DSL)
1444
+ block.length === 0 ? block.call(processor) : block(processor)
1445
+ if (!processor.processBlockGiven()) {
1446
+ throw new Error(`No block specified to process ${kindName} extension`)
1447
+ }
1448
+ processorInstance = processor
1449
+ } else {
1450
+ const [processorArg, config] = this._resolveArgs(args, 2)
1451
+ if (typeof processorArg === 'function') {
1452
+ // Style 2: class constructor
1453
+ if (!(processorArg.prototype instanceof kindClass)) {
1454
+ throw new Error(
1455
+ `Invalid type for ${kindName} extension: ${processorArg}`
1456
+ )
1457
+ }
1458
+ processorInstance = new processorArg(config)
1459
+ } else if (processorArg instanceof kindClass) {
1460
+ // Style 3: already an instance
1461
+ processorArg.updateConfig(config)
1462
+ processorInstance = processorArg
1463
+ } else {
1464
+ throw new Error(
1465
+ `Invalid arguments specified for registering ${kindName} extension: ${args}`
1466
+ )
1467
+ }
1468
+ }
1469
+
1470
+ // Apply legacy handles adapter for IncludeProcessors with arity-1 handles method.
1471
+ if (kind === 'include_processor') {
1472
+ const handlesFn = processorInstance.handles
1473
+ if (typeof handlesFn === 'function' && handlesFn.length === 1) {
1474
+ const original = handlesFn.bind(processorInstance)
1475
+ processorInstance.handles = (_doc, target) => original(target)
1476
+ }
1477
+ }
1478
+
1479
+ const extension = new ProcessorExtension(kind, processorInstance)
1480
+ extension.config.position === '>>'
1481
+ ? store.unshift(extension)
1482
+ : store.push(extension)
1483
+ return extension
1484
+ }
1485
+
1486
+ /** @internal */
1487
+ _addSyntaxProcessor(kind, args) {
1488
+ const kindName = kind.replace(/_/g, ' ')
1489
+ const kindClass = SYNTAX_PROCESSOR_CLASSES[kind]
1490
+ if (!this[`_${kind}_extensions`])
1491
+ this[`_${kind}_extensions`] = Object.create(null)
1492
+ const store = this[`_${kind}_extensions`]
1493
+
1494
+ // Detect block style (same heuristic as _addDocumentProcessor).
1495
+ const lastArg = args[args.length - 1]
1496
+ const hasBlock =
1497
+ args.length > 0 &&
1498
+ typeof lastArg === 'function' &&
1499
+ !!(
1500
+ Object.getOwnPropertyDescriptor(lastArg, 'prototype')?.writable ?? true
1501
+ )
1502
+
1503
+ let processorInstance, name
1504
+
1505
+ if (hasBlock) {
1506
+ const block = args.pop()
1507
+ const [nameArg, config] = this._resolveArgs(args, 2)
1508
+ const processor = new kindClass(this._asSymbol(nameArg), config)
1509
+ Object.assign(processor, kindClass.DSL)
1510
+ block.length === 0 ? block.call(processor) : block(processor)
1511
+ name = this._asSymbol(processor.name)
1512
+ if (!name) throw new Error(`No name specified for ${kindName} extension`)
1513
+ if (!processor.processBlockGiven()) {
1514
+ throw new Error(`No block specified to process ${kindName} extension`)
1515
+ }
1516
+ processorInstance = processor
1517
+ } else {
1518
+ const [processorArg, nameArg, config] = this._resolveArgs(args, 3)
1519
+ if (typeof processorArg === 'function') {
1520
+ // Style 2: class constructor
1521
+ if (!(processorArg.prototype instanceof kindClass)) {
1522
+ throw new Error(
1523
+ `Class specified for ${kindName} extension does not inherit from ${kindClass.name}: ${processorArg}`
1524
+ )
1525
+ }
1526
+ processorInstance = new processorArg(this._asSymbol(nameArg), config)
1527
+ name = this._asSymbol(processorInstance.name)
1528
+ if (!name)
1529
+ throw new Error(
1530
+ `No name specified for ${kindName} extension: ${processorArg}`
1531
+ )
1532
+ } else if (processorArg instanceof kindClass) {
1533
+ // Style 3: already an instance
1534
+ processorArg.updateConfig(config)
1535
+ name = nameArg
1536
+ ? (processorArg.name = this._asSymbol(nameArg))
1537
+ : this._asSymbol(processorArg.name)
1538
+ if (!name)
1539
+ throw new Error(
1540
+ `No name specified for ${kindName} extension: ${processorArg}`
1541
+ )
1542
+ processorInstance = processorArg
1543
+ } else {
1544
+ throw new Error(
1545
+ `Invalid arguments specified for registering ${kindName} extension: ${args}`
1546
+ )
1547
+ }
1548
+ }
1549
+
1550
+ store[name] = new ProcessorExtension(kind, processorInstance)
1551
+ return store[name]
1552
+ }
1553
+
1554
+ /** @internal */
1555
+ _reset() {
1556
+ /** @internal */
1557
+ this._preprocessor_extensions = null
1558
+ /** @internal */
1559
+ this._tree_processor_extensions = null
1560
+ /** @internal */
1561
+ this._postprocessor_extensions = null
1562
+ /** @internal */
1563
+ this._include_processor_extensions = null
1564
+ /** @internal */
1565
+ this._docinfo_processor_extensions = null
1566
+ /** @internal */
1567
+ this._block_extensions = null
1568
+ /** @internal */
1569
+ this._block_macro_extensions = null
1570
+ /** @internal */
1571
+ this._inline_macro_extensions = null
1572
+ this.document = null
1573
+ }
1574
+
1575
+ /**
1576
+ * @internal Normalise an args array to the expected number of values.
1577
+ *
1578
+ * Pops a trailing plain-object as options (or uses {}), then pads / trims
1579
+ * the remaining args to (expect - 1) elements, then appends the options object.
1580
+ * If expect === 1, returns just the options object.
1581
+ */
1582
+ _resolveArgs(args, expect) {
1583
+ const last = args[args.length - 1]
1584
+ const opts =
1585
+ args.length > 0 &&
1586
+ last !== null &&
1587
+ typeof last === 'object' &&
1588
+ !Array.isArray(last) &&
1589
+ !(last instanceof Processor)
1590
+ ? args.pop()
1591
+ : {}
1592
+
1593
+ if (expect === 1) return opts
1594
+
1595
+ const missing = expect - 1 - args.length
1596
+ if (missing > 0) {
1597
+ for (let i = 0; i < missing; i++) args.push(undefined)
1598
+ } else if (missing < 0) {
1599
+ args.splice(args.length + missing, -missing)
1600
+ }
1601
+ args.push(opts)
1602
+ return args
1603
+ }
1604
+
1605
+ /** @internal */
1606
+ _asSymbol(name) {
1607
+ return name != null ? String(name) : null
1608
+ }
1609
+ }
1610
+
1611
+ // ── Extensions module namespace ───────────────────────────────────────────────
1612
+
1613
+ // Module-level state (mirrors Ruby module instance variables @auto_id / @groups).
1614
+ let _autoId = -1
1615
+ const _groups = Object.create(null)
1616
+
1617
+ /**
1618
+ * The primary entry point for registering extensions globally.
1619
+ *
1620
+ * Mirrors the class-level methods on the Ruby Asciidoctor::Extensions module.
1621
+ */
1622
+ export const Extensions = {
1623
+ /** @internal Generate a unique name for an anonymous extension group. */
1624
+ generateName() {
1625
+ return `extgrp${this.nextAutoId()}`
1626
+ },
1627
+
1628
+ /** @internal Increment and return the global auto-id counter. */
1629
+ nextAutoId() {
1630
+ return ++_autoId
1631
+ },
1632
+
1633
+ /**
1634
+ * Return the plain Object that maps names to registered groups.
1635
+ *
1636
+ * @returns {object}
1637
+ */
1638
+ groups() {
1639
+ return _groups
1640
+ },
1641
+
1642
+ /**
1643
+ * Create a new Registry, optionally pre-populated with a named block.
1644
+ *
1645
+ * @param {string|null} [name=null] - Optional name for the group; auto-generated if omitted.
1646
+ * @param {Function|null} [block=null] - Optional function to register as the group.
1647
+ * @returns {Registry}
1648
+ */
1649
+ create(name = null, block = null) {
1650
+ if (block) {
1651
+ return new Registry({ [name || this.generateName()]: block })
1652
+ }
1653
+ return new Registry()
1654
+ },
1655
+
1656
+ /**
1657
+ * Register an extension Group that subsequently registers extensions.
1658
+ *
1659
+ * @example
1660
+ * Extensions.register(UmlExtensions)
1661
+ * Extensions.register('uml', UmlExtensions)
1662
+ * Extensions.register(function () { this.blockMacro('plantuml', PlantUmlBlock) })
1663
+ * Extensions.register('uml', function () { this.blockMacro('plantuml', PlantUmlBlock) })
1664
+ *
1665
+ * @param {...*} args - Optional name followed by a Group class, instance, or function.
1666
+ * @returns {Function|object} the registered group.
1667
+ */
1668
+ register(...args) {
1669
+ const argc = args.length
1670
+ if (argc === 0) throw new Error('Extension group to register not specified')
1671
+ const group = args.pop()
1672
+ if (!group) throw new Error('Extension group to register not specified')
1673
+ const name = args.pop() ?? this.generateName()
1674
+ if (args.length > 0)
1675
+ throw new Error(`Wrong number of arguments (${argc} for 1..2)`)
1676
+ _groups[String(name)] = group
1677
+ return group
1678
+ },
1679
+
1680
+ /**
1681
+ * Unregister all statically-registered extension groups.
1682
+ */
1683
+ unregisterAll() {
1684
+ for (const key of Object.keys(_groups)) delete _groups[key]
1685
+ },
1686
+
1687
+ /**
1688
+ * Unregister statically-registered extension groups by name.
1689
+ *
1690
+ * @param {...string} names - One or more group names to unregister.
1691
+ */
1692
+ unregister(...names) {
1693
+ for (const name of names) delete _groups[String(name)]
1694
+ },
1695
+
1696
+ // ── Processor factory helpers (mirrors core API) ─────────────────────────────
1697
+ // Each pair: create<Kind>(name?, functions) → class constructor
1698
+ // new<Kind>(name?, functions) → instance of that class
1699
+ // The `name` argument is optional; if omitted the sole argument is `functions`.
1700
+
1701
+ /** @internal Build a subclass of BaseClass with the given prototype functions. */
1702
+ _buildProcessorClass(BaseClass, name, functions) {
1703
+ if (arguments.length === 2) {
1704
+ functions = name
1705
+ name = null
1706
+ }
1707
+ const klass = class extends BaseClass {}
1708
+ if (name) Object.defineProperty(klass, 'name', { value: name })
1709
+ Object.assign(klass.prototype, functions)
1710
+ return klass
1711
+ },
1712
+
1713
+ /**
1714
+ * Create a Preprocessor subclass with the given prototype functions.
1715
+ *
1716
+ * @param {string} [name] - Optional class name.
1717
+ * @param {object} [functions] - Methods to mix into the prototype.
1718
+ * @returns {typeof Preprocessor}
1719
+ */
1720
+ createPreprocessor(name, functions) {
1721
+ if (arguments.length === 1) {
1722
+ functions = name
1723
+ name = null
1724
+ }
1725
+ return this._buildProcessorClass(Preprocessor, name, functions)
1726
+ },
1727
+
1728
+ /**
1729
+ * Create and return a new Preprocessor instance with the given prototype functions.
1730
+ *
1731
+ * @param {string} [name] - Optional class name.
1732
+ * @param {object} [functions] - Methods to mix into the prototype.
1733
+ * @returns {Preprocessor}
1734
+ */
1735
+ newPreprocessor(name, functions) {
1736
+ if (arguments.length === 1) {
1737
+ functions = name
1738
+ name = null
1739
+ }
1740
+ return new (this.createPreprocessor(name, functions))()
1741
+ },
1742
+
1743
+ /**
1744
+ * Create a TreeProcessor subclass with the given prototype functions.
1745
+ *
1746
+ * @param {string} [name] - Optional class name.
1747
+ * @param {object} [functions] - Methods to mix into the prototype.
1748
+ * @returns {typeof TreeProcessor}
1749
+ */
1750
+ createTreeProcessor(name, functions) {
1751
+ if (arguments.length === 1) {
1752
+ functions = name
1753
+ name = null
1754
+ }
1755
+ return this._buildProcessorClass(TreeProcessor, name, functions)
1756
+ },
1757
+
1758
+ /**
1759
+ * Create and return a new TreeProcessor instance with the given prototype functions.
1760
+ *
1761
+ * @param {string} [name] - Optional class name.
1762
+ * @param {object} [functions] - Methods to mix into the prototype.
1763
+ * @returns {TreeProcessor}
1764
+ */
1765
+ newTreeProcessor(name, functions) {
1766
+ if (arguments.length === 1) {
1767
+ functions = name
1768
+ name = null
1769
+ }
1770
+ return new (this.createTreeProcessor(name, functions))()
1771
+ },
1772
+
1773
+ /**
1774
+ * Create a Postprocessor subclass with the given prototype functions.
1775
+ *
1776
+ * @param {string} [name] - Optional class name.
1777
+ * @param {object} [functions] - Methods to mix into the prototype.
1778
+ * @returns {typeof Postprocessor}
1779
+ */
1780
+ createPostprocessor(name, functions) {
1781
+ if (arguments.length === 1) {
1782
+ functions = name
1783
+ name = null
1784
+ }
1785
+ return this._buildProcessorClass(Postprocessor, name, functions)
1786
+ },
1787
+
1788
+ /**
1789
+ * Create and return a new Postprocessor instance with the given prototype functions.
1790
+ *
1791
+ * @param {string} [name] - Optional class name.
1792
+ * @param {object} [functions] - Methods to mix into the prototype.
1793
+ * @returns {Postprocessor}
1794
+ */
1795
+ newPostprocessor(name, functions) {
1796
+ if (arguments.length === 1) {
1797
+ functions = name
1798
+ name = null
1799
+ }
1800
+ return new (this.createPostprocessor(name, functions))()
1801
+ },
1802
+
1803
+ /**
1804
+ * Create an IncludeProcessor subclass with the given prototype functions.
1805
+ *
1806
+ * @param {string} [name] - Optional class name.
1807
+ * @param {object} [functions] - Methods to mix into the prototype.
1808
+ * @returns {typeof IncludeProcessor}
1809
+ */
1810
+ createIncludeProcessor(name, functions) {
1811
+ if (arguments.length === 1) {
1812
+ functions = name
1813
+ name = null
1814
+ }
1815
+ return this._buildProcessorClass(IncludeProcessor, name, functions)
1816
+ },
1817
+
1818
+ /**
1819
+ * Create and return a new IncludeProcessor instance with the given prototype functions.
1820
+ *
1821
+ * @param {string} [name] - Optional class name.
1822
+ * @param {object} [functions] - Methods to mix into the prototype.
1823
+ * @returns {IncludeProcessor}
1824
+ */
1825
+ newIncludeProcessor(name, functions) {
1826
+ if (arguments.length === 1) {
1827
+ functions = name
1828
+ name = null
1829
+ }
1830
+ return new (this.createIncludeProcessor(name, functions))()
1831
+ },
1832
+
1833
+ /**
1834
+ * Create a DocinfoProcessor subclass with the given prototype functions.
1835
+ *
1836
+ * @param {string} [name] - Optional class name.
1837
+ * @param {object} [functions] - Methods to mix into the prototype.
1838
+ * @returns {typeof DocinfoProcessor}
1839
+ */
1840
+ createDocinfoProcessor(name, functions) {
1841
+ if (arguments.length === 1) {
1842
+ functions = name
1843
+ name = null
1844
+ }
1845
+ return this._buildProcessorClass(DocinfoProcessor, name, functions)
1846
+ },
1847
+
1848
+ /**
1849
+ * Create and return a new DocinfoProcessor instance with the given prototype functions.
1850
+ *
1851
+ * @param {string} [name] - Optional class name.
1852
+ * @param {object} [functions] - Methods to mix into the prototype.
1853
+ * @returns {DocinfoProcessor}
1854
+ */
1855
+ newDocinfoProcessor(name, functions) {
1856
+ if (arguments.length === 1) {
1857
+ functions = name
1858
+ name = null
1859
+ }
1860
+ return new (this.createDocinfoProcessor(name, functions))()
1861
+ },
1862
+
1863
+ /**
1864
+ * Create a BlockProcessor subclass with the given prototype functions.
1865
+ *
1866
+ * @param {string} [name] - Optional class name.
1867
+ * @param {object} [functions] - Methods to mix into the prototype.
1868
+ * @returns {typeof BlockProcessor}
1869
+ */
1870
+ createBlockProcessor(name, functions) {
1871
+ if (arguments.length === 1) {
1872
+ functions = name
1873
+ name = null
1874
+ }
1875
+ return this._buildProcessorClass(BlockProcessor, name, functions)
1876
+ },
1877
+
1878
+ /**
1879
+ * Create and return a new BlockProcessor instance with the given prototype functions.
1880
+ *
1881
+ * @param {string} [name] - Optional class name.
1882
+ * @param {object} [functions] - Methods to mix into the prototype.
1883
+ * @returns {BlockProcessor}
1884
+ */
1885
+ newBlockProcessor(name, functions) {
1886
+ if (arguments.length === 1) {
1887
+ functions = name
1888
+ name = null
1889
+ }
1890
+ return new (this.createBlockProcessor(name, functions))()
1891
+ },
1892
+
1893
+ /**
1894
+ * Create an InlineMacroProcessor subclass with the given prototype functions.
1895
+ *
1896
+ * @param {string} [name] - Optional class name.
1897
+ * @param {object} [functions] - Methods to mix into the prototype.
1898
+ * @returns {typeof InlineMacroProcessor}
1899
+ */
1900
+ createInlineMacroProcessor(name, functions) {
1901
+ if (arguments.length === 1) {
1902
+ functions = name
1903
+ name = null
1904
+ }
1905
+ return this._buildProcessorClass(InlineMacroProcessor, name, functions)
1906
+ },
1907
+
1908
+ /**
1909
+ * Create and return a new InlineMacroProcessor instance with the given prototype functions.
1910
+ *
1911
+ * @param {string} [name] - Optional class name.
1912
+ * @param {object} [functions] - Methods to mix into the prototype.
1913
+ * @returns {InlineMacroProcessor}
1914
+ */
1915
+ newInlineMacroProcessor(name, functions) {
1916
+ if (arguments.length === 1) {
1917
+ functions = name
1918
+ name = null
1919
+ }
1920
+ return new (this.createInlineMacroProcessor(name, functions))()
1921
+ },
1922
+
1923
+ /**
1924
+ * Create a BlockMacroProcessor subclass with the given prototype functions.
1925
+ *
1926
+ * @param {string} [name] - Optional class name.
1927
+ * @param {object} [functions] - Methods to mix into the prototype.
1928
+ * @returns {typeof BlockMacroProcessor}
1929
+ */
1930
+ createBlockMacroProcessor(name, functions) {
1931
+ if (arguments.length === 1) {
1932
+ functions = name
1933
+ name = null
1934
+ }
1935
+ return this._buildProcessorClass(BlockMacroProcessor, name, functions)
1936
+ },
1937
+
1938
+ /**
1939
+ * Create and return a new BlockMacroProcessor instance with the given prototype functions.
1940
+ *
1941
+ * @param {string} [name] - Optional class name.
1942
+ * @param {object} [functions] - Methods to mix into the prototype.
1943
+ * @returns {BlockMacroProcessor}
1944
+ */
1945
+ newBlockMacroProcessor(name, functions) {
1946
+ if (arguments.length === 1) {
1947
+ functions = name
1948
+ name = null
1949
+ }
1950
+ return new (this.createBlockMacroProcessor(name, functions))()
1951
+ },
1952
+ }