@asciidoctor/core 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/browser/index.js +230 -81
- package/build/node/index.cjs +277 -93
- package/package.json +2 -2
- package/src/abstract_node.js +6 -3
- package/src/browser/reader.js +2 -2
- package/src/browser.js +8 -1
- package/src/converter/html5.js +39 -9
- package/src/converter/manpage.js +1 -1
- package/src/converter/template.js +47 -13
- package/src/converter.js +22 -9
- package/src/document.js +71 -21
- package/src/index.js +8 -1
- package/src/logging.js +41 -10
- package/src/parser.js +13 -2
- package/src/reader.js +25 -13
- package/src/substitutors.js +9 -9
- package/types/abstract_node.d.ts +4 -3
- package/types/document.d.ts +16 -0
- package/types/index.d.cts +2 -1
- package/types/index.d.ts +2 -1
- package/types/logging.d.ts +49 -5
- package/types/reader.d.ts +2 -2
- package/types/substitutors.d.ts +18 -18
package/build/node/index.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const node_module = require('node:module');
|
|
4
|
+
const node_url = require('node:url');
|
|
4
5
|
const node_fs = require('node:fs');
|
|
5
6
|
const path = require('node:path');
|
|
6
7
|
|
|
7
8
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
8
|
-
var version = "4.0.
|
|
9
|
+
var version = "4.0.2";
|
|
9
10
|
const packageJson = {
|
|
10
11
|
version: version};
|
|
11
12
|
|
|
@@ -1752,14 +1753,23 @@ Logger.BasicFormatter = class {
|
|
|
1752
1753
|
|
|
1753
1754
|
Logger.AutoFormattingMessage = {
|
|
1754
1755
|
/**
|
|
1755
|
-
* Attach auto-formatting to any plain object carrying
|
|
1756
|
-
*
|
|
1756
|
+
* Attach auto-formatting to any plain object carrying
|
|
1757
|
+
* { text, source_location, include_location }.
|
|
1758
|
+
*
|
|
1759
|
+
* The location(s) are rendered only by inspect()/toString() (used when a
|
|
1760
|
+
* stderr Logger formats the line); the structured `source_location` /
|
|
1761
|
+
* `include_location` remain on the object so a MemoryLogger can record them
|
|
1762
|
+
* on the resulting LogMessage without duplicating them inside `text`.
|
|
1763
|
+
* @param {{text: string, source_location?: any, include_location?: any}} obj
|
|
1757
1764
|
* @returns {typeof obj} The same object with inspect() and toString() added.
|
|
1758
1765
|
*/
|
|
1759
1766
|
attach(obj) {
|
|
1760
1767
|
obj.inspect = function () {
|
|
1761
1768
|
const sloc = this.source_location;
|
|
1762
|
-
|
|
1769
|
+
const iloc = this.include_location;
|
|
1770
|
+
let text = sloc ? `${sloc}: ${this.text}` : this.text;
|
|
1771
|
+
if (iloc) text += ` (${iloc})`;
|
|
1772
|
+
return text
|
|
1763
1773
|
};
|
|
1764
1774
|
obj.toString = obj.inspect;
|
|
1765
1775
|
return obj
|
|
@@ -1770,27 +1780,45 @@ Logger.AutoFormattingMessage = {
|
|
|
1770
1780
|
|
|
1771
1781
|
/** Wrapper stored by MemoryLogger; provides getSeverity/getText/getSourceLocation. */
|
|
1772
1782
|
class LogMessage {
|
|
1783
|
+
/**
|
|
1784
|
+
* @param {string} severity - Severity label, e.g. 'ERROR'.
|
|
1785
|
+
* @param {string|{text: string, source_location?: import('./reader.js').Cursor}|null} message
|
|
1786
|
+
*/
|
|
1773
1787
|
constructor(severity, message) {
|
|
1774
1788
|
this.message = message;
|
|
1789
|
+
/** @type {string} */
|
|
1775
1790
|
this.severity = severity; // string label, e.g. 'ERROR'
|
|
1776
1791
|
// AutoFormattingMessage objects carry { text, source_location }
|
|
1777
1792
|
if (message !== null && typeof message === 'object' && 'text' in message) {
|
|
1778
|
-
|
|
1779
|
-
this.
|
|
1793
|
+
/** @type {string} */
|
|
1794
|
+
this.text = message.text;
|
|
1795
|
+
/** @type {import('./reader.js').Cursor|null} */
|
|
1796
|
+
this.sourceLocation = message.source_location ?? null;
|
|
1780
1797
|
} else {
|
|
1781
|
-
this.
|
|
1782
|
-
this.
|
|
1798
|
+
this.text = message != null ? String(message) : '';
|
|
1799
|
+
this.sourceLocation = null;
|
|
1783
1800
|
}
|
|
1784
1801
|
}
|
|
1785
1802
|
|
|
1803
|
+
/**
|
|
1804
|
+
* @returns {string} The severity label, e.g. 'ERROR'.
|
|
1805
|
+
*/
|
|
1786
1806
|
getSeverity() {
|
|
1787
1807
|
return this.severity
|
|
1788
1808
|
}
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* @returns {string} The message text.
|
|
1812
|
+
*/
|
|
1789
1813
|
getText() {
|
|
1790
|
-
return this.
|
|
1814
|
+
return this.text
|
|
1791
1815
|
}
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* @returns {import('./reader.js').Cursor|undefined} The source location, if any.
|
|
1819
|
+
*/
|
|
1792
1820
|
getSourceLocation() {
|
|
1793
|
-
return this.
|
|
1821
|
+
return this.sourceLocation ?? undefined
|
|
1794
1822
|
}
|
|
1795
1823
|
}
|
|
1796
1824
|
|
|
@@ -1803,6 +1831,7 @@ class MemoryLogger {
|
|
|
1803
1831
|
// matching Ruby's MemoryLogger (level: UNKNOWN). The add() method stores all
|
|
1804
1832
|
// messages unconditionally — level is only used by the isDebug() guard.
|
|
1805
1833
|
this.level = Severity.UNKNOWN;
|
|
1834
|
+
/** @type {LogMessage[]} */
|
|
1806
1835
|
this.messages = [];
|
|
1807
1836
|
}
|
|
1808
1837
|
|
|
@@ -1810,6 +1839,9 @@ class MemoryLogger {
|
|
|
1810
1839
|
return new MemoryLogger()
|
|
1811
1840
|
}
|
|
1812
1841
|
|
|
1842
|
+
/**
|
|
1843
|
+
* @returns {LogMessage[]} The log messages recorded so far, in order.
|
|
1844
|
+
*/
|
|
1813
1845
|
getMessages() {
|
|
1814
1846
|
return this.messages
|
|
1815
1847
|
}
|
|
@@ -3008,9 +3040,10 @@ class AbstractNode {
|
|
|
3008
3040
|
* Construct a URI reference or data URI to the target image.
|
|
3009
3041
|
*
|
|
3010
3042
|
* If the target image is already a URI it is left untouched (unless data-uri
|
|
3011
|
-
* conversion is requested).
|
|
3012
|
-
*
|
|
3013
|
-
* the
|
|
3043
|
+
* conversion is requested). If the target image is a data URI, then it is
|
|
3044
|
+
* already an embedded image, so it is returned as-is. The image is resolved
|
|
3045
|
+
* relative to the directory named by assetDirKey. When data-uri is enabled and
|
|
3046
|
+
* the safe level permits, the image is embedded as a Base64 data URI.
|
|
3014
3047
|
*
|
|
3015
3048
|
* NOTE: When the document has both 'data-uri' and 'allow-uri-read' enabled
|
|
3016
3049
|
* and the resolved image URL is a remote URI, this method returns a Promise
|
|
@@ -3021,6 +3054,8 @@ class AbstractNode {
|
|
|
3021
3054
|
* @returns {Promise<string>} a Promise resolving to a String reference or data URI.
|
|
3022
3055
|
*/
|
|
3023
3056
|
async imageUri(targetImage, assetDirKey = 'imagesdir') {
|
|
3057
|
+
// A data URI is already an embedded image, so use it as-is rather than reading or re-encoding it.
|
|
3058
|
+
if (targetImage.startsWith('data:')) return targetImage
|
|
3024
3059
|
const doc = this.document;
|
|
3025
3060
|
if (doc.safe < SafeMode.SECURE && doc.hasAttribute('data-uri')) {
|
|
3026
3061
|
let imagesBase;
|
|
@@ -5428,7 +5463,18 @@ class SecurityError extends Error {
|
|
|
5428
5463
|
function applyBackendTraits(instance) {
|
|
5429
5464
|
instance._backendTraits = null;
|
|
5430
5465
|
|
|
5431
|
-
|
|
5466
|
+
// Install a Ruby-style trait accessor method, but never clobber a flat string
|
|
5467
|
+
// property the converter already declared (convention #2). Overwriting e.g.
|
|
5468
|
+
// `converter.outfilesuffix = '.html'` with a method would silently turn the
|
|
5469
|
+
// author's string into a function; the backend traits stay reachable through
|
|
5470
|
+
// `_getBackendTraits()` instead.
|
|
5471
|
+
const defineTraitAccessor = (name, fn) => {
|
|
5472
|
+
const existing = Object.getOwnPropertyDescriptor(instance, name);
|
|
5473
|
+
if (existing && typeof existing.value !== 'function') return
|
|
5474
|
+
instance[name] = fn;
|
|
5475
|
+
};
|
|
5476
|
+
|
|
5477
|
+
defineTraitAccessor('basebackend', function (value = null) {
|
|
5432
5478
|
if (value) {
|
|
5433
5479
|
const traits = (this._backendTraits ??= {});
|
|
5434
5480
|
traits.basebackend = value;
|
|
@@ -5442,19 +5488,19 @@ function applyBackendTraits(instance) {
|
|
|
5442
5488
|
return value
|
|
5443
5489
|
}
|
|
5444
5490
|
return this._getBackendTraits().basebackend
|
|
5445
|
-
};
|
|
5446
|
-
|
|
5491
|
+
});
|
|
5492
|
+
defineTraitAccessor('filetype', function (value = null) {
|
|
5447
5493
|
if (value) return (this._getBackendTraits().filetype = value)
|
|
5448
5494
|
return this._getBackendTraits().filetype
|
|
5449
|
-
};
|
|
5450
|
-
|
|
5495
|
+
});
|
|
5496
|
+
defineTraitAccessor('htmlsyntax', function (value = null) {
|
|
5451
5497
|
if (value) return (this._getBackendTraits().htmlsyntax = value)
|
|
5452
5498
|
return this._getBackendTraits().htmlsyntax
|
|
5453
|
-
};
|
|
5454
|
-
|
|
5499
|
+
});
|
|
5500
|
+
defineTraitAccessor('outfilesuffix', function (value = null) {
|
|
5455
5501
|
if (value) return (this._getBackendTraits().outfilesuffix = value)
|
|
5456
5502
|
return this._getBackendTraits().outfilesuffix
|
|
5457
|
-
};
|
|
5503
|
+
});
|
|
5458
5504
|
instance.supportsTemplates = function (value = true) {
|
|
5459
5505
|
this._getBackendTraits().supportsTemplates = value;
|
|
5460
5506
|
};
|
|
@@ -5538,7 +5584,9 @@ function normalizeConverter(converter, backend) {
|
|
|
5538
5584
|
}
|
|
5539
5585
|
}
|
|
5540
5586
|
|
|
5541
|
-
// Apply the BackendTraits mixin so Document can
|
|
5587
|
+
// Apply the BackendTraits mixin so Document can read traits via
|
|
5588
|
+
// _getBackendTraits(). Flat string properties (convention #2) are preserved:
|
|
5589
|
+
// applyBackendTraits does not overwrite an existing same-named data property.
|
|
5542
5590
|
applyBackendTraits(converter);
|
|
5543
5591
|
if (traits) {
|
|
5544
5592
|
converter._backendTraits = traits;
|
|
@@ -6275,7 +6323,7 @@ function resolveBrowserIncludePath(reader, target, attrlist) {
|
|
|
6275
6323
|
// ── Rule 2: target is an absolute URL (http:// / https:// / …) ───────────
|
|
6276
6324
|
} else if (isUriish(pTarget)) {
|
|
6277
6325
|
const descends = pathResolver.descendsFrom(pTarget, baseDir);
|
|
6278
|
-
if (descends === false && !doc.
|
|
6326
|
+
if (descends === false && !doc.hasAttribute('allow-uri-read')) {
|
|
6279
6327
|
return reader.replaceNextLine(_linkReplacement(reader, target, attrlist))
|
|
6280
6328
|
}
|
|
6281
6329
|
incPath = relpath = pTarget;
|
|
@@ -6309,7 +6357,7 @@ function resolveBrowserIncludePath(reader, target, attrlist) {
|
|
|
6309
6357
|
} else {
|
|
6310
6358
|
// Nested include: context dir is an absolute URL.
|
|
6311
6359
|
const ctxDescends = pathResolver.descendsFrom(ctxDir, baseDir);
|
|
6312
|
-
if (ctxDescends !== false || doc.
|
|
6360
|
+
if (ctxDescends !== false || doc.hasAttribute('allow-uri-read')) {
|
|
6313
6361
|
incPath = `${ctxDir}/${pTarget}`;
|
|
6314
6362
|
relpath = ctxDescends !== false ? incPath.slice(ctxDescends) : pTarget;
|
|
6315
6363
|
} else {
|
|
@@ -6973,22 +7021,34 @@ class Reader {
|
|
|
6973
7021
|
}
|
|
6974
7022
|
|
|
6975
7023
|
/** @param {string} msg @param {{ sourceLocation?: any, includeLocation?: any }} [opts] */
|
|
6976
|
-
_logWarn(msg,
|
|
6977
|
-
|
|
6978
|
-
if (includeLocation) text += ` (${includeLocation.lineInfo})`;
|
|
6979
|
-
this.logger.warn(text);
|
|
7024
|
+
_logWarn(msg, opts = {}) {
|
|
7025
|
+
this.logger.warn(this._messageWithContext(msg, opts));
|
|
6980
7026
|
}
|
|
6981
7027
|
_logError(msg, opts = {}) {
|
|
6982
|
-
|
|
6983
|
-
? `${opts.sourceLocation.lineInfo}: ${msg}`
|
|
6984
|
-
: msg;
|
|
6985
|
-
if (opts.includeLocation) text += ` (${opts.includeLocation.lineInfo})`;
|
|
6986
|
-
this.logger.error(text);
|
|
7028
|
+
this.logger.error(this._messageWithContext(msg, opts));
|
|
6987
7029
|
}
|
|
6988
7030
|
/** @param {string} msg @param {{ sourceLocation?: any }} [opts] */
|
|
6989
|
-
_logInfo(msg,
|
|
6990
|
-
|
|
6991
|
-
|
|
7031
|
+
_logInfo(msg, opts = {}) {
|
|
7032
|
+
this.logger.info(this._messageWithContext(msg, opts));
|
|
7033
|
+
}
|
|
7034
|
+
|
|
7035
|
+
/**
|
|
7036
|
+
* Build an auto-formatting message that keeps the cursor as a structured
|
|
7037
|
+
* source_location (rather than baking it into the text). When displayed by a
|
|
7038
|
+
* stderr Logger the location is rendered as a "<path>: line <N>: " prefix, but
|
|
7039
|
+
* a MemoryLogger records it separately on the LogMessage so consumers can call
|
|
7040
|
+
* getSourceLocation(). Mirrors Ruby's Logging#message_with_context.
|
|
7041
|
+
* @param {string} msg
|
|
7042
|
+
* @param {{ sourceLocation?: any, includeLocation?: any }} [opts]
|
|
7043
|
+
* @internal
|
|
7044
|
+
*/
|
|
7045
|
+
_messageWithContext(msg, { sourceLocation, includeLocation } = {}) {
|
|
7046
|
+
if (!sourceLocation && !includeLocation) return msg
|
|
7047
|
+
return Logger.AutoFormattingMessage.attach({
|
|
7048
|
+
text: msg,
|
|
7049
|
+
source_location: sourceLocation ?? null,
|
|
7050
|
+
include_location: includeLocation ?? null,
|
|
7051
|
+
})
|
|
6992
7052
|
}
|
|
6993
7053
|
}
|
|
6994
7054
|
|
|
@@ -7746,7 +7806,7 @@ class PreprocessorReader extends Reader {
|
|
|
7746
7806
|
}
|
|
7747
7807
|
|
|
7748
7808
|
if (isUriish(target) || typeof this._dir !== 'string') {
|
|
7749
|
-
if (!doc.
|
|
7809
|
+
if (!doc.hasAttribute('allow-uri-read')) {
|
|
7750
7810
|
this._logWarn(
|
|
7751
7811
|
`cannot include contents of URI: ${target} (allow-uri-read attribute not enabled)`,
|
|
7752
7812
|
{ sourceLocation: this.cursor }
|
|
@@ -10356,7 +10416,12 @@ class Parser {
|
|
|
10356
10416
|
}
|
|
10357
10417
|
}
|
|
10358
10418
|
(intro ?? section).blocks.push(newBlock);
|
|
10359
|
-
|
|
10419
|
+
// Reset the shared attributes object for the next block. Use Reflect.ownKeys
|
|
10420
|
+
// (not Object.keys) so the Symbol-keyed attribute entries (ATTR_ENTRIES_KEY)
|
|
10421
|
+
// are cleared too; otherwise the array of AttributeEntry objects leaks and
|
|
10422
|
+
// accumulates across blocks, causing reassigned attributes (e.g. a body-level
|
|
10423
|
+
// `:name:` redefined later) to all resolve to the final value at playback time.
|
|
10424
|
+
for (const key of Reflect.ownKeys(attributes)) delete attributes[key];
|
|
10360
10425
|
}
|
|
10361
10426
|
}
|
|
10362
10427
|
|
|
@@ -11203,7 +11268,13 @@ class Parser {
|
|
|
11203
11268
|
}
|
|
11204
11269
|
}
|
|
11205
11270
|
|
|
11206
|
-
|
|
11271
|
+
// Reflect.ownKeys (not Object.keys) so a block carrying only Symbol-keyed
|
|
11272
|
+
// attribute entries (ATTR_ENTRIES_KEY) — e.g. an `:attr:` entry immediately
|
|
11273
|
+
// preceding a list or table — still receives them, matching Ruby's
|
|
11274
|
+
// `attributes.empty?` where the `:attribute_entries` key is counted. Without this
|
|
11275
|
+
// the entries are dropped and the attribute is not played back for that block.
|
|
11276
|
+
if (Reflect.ownKeys(attributes).length > 0)
|
|
11277
|
+
block.updateAttributes(attributes);
|
|
11207
11278
|
block.commitSubs();
|
|
11208
11279
|
|
|
11209
11280
|
if (block.hasSub('callouts')) {
|
|
@@ -16044,7 +16115,7 @@ class Document extends AbstractBlock {
|
|
|
16044
16115
|
|
|
16045
16116
|
const attrOverrides = this._attributeOverrides;
|
|
16046
16117
|
attrOverrides.asciidoctor = '';
|
|
16047
|
-
attrOverrides['asciidoctor-version'] =
|
|
16118
|
+
attrOverrides['asciidoctor-version'] = packageJson.version;
|
|
16048
16119
|
|
|
16049
16120
|
const safeModeName = SafeMode.nameForValue(this.safe);
|
|
16050
16121
|
attrOverrides['safe-mode-name'] = safeModeName;
|
|
@@ -16231,13 +16302,21 @@ class Document extends AbstractBlock {
|
|
|
16231
16302
|
}
|
|
16232
16303
|
|
|
16233
16304
|
// Pre-compute all async text values (titles, list item text, cell text, reftexts)
|
|
16234
|
-
// so that synchronous getters work correctly during conversion.
|
|
16235
|
-
|
|
16236
|
-
//
|
|
16237
|
-
//
|
|
16238
|
-
//
|
|
16239
|
-
|
|
16240
|
-
|
|
16305
|
+
// so that synchronous getters work correctly during conversion. _resolveAllTexts
|
|
16306
|
+
// replays attribute entries in document order (mirroring conversion) so body-level
|
|
16307
|
+
// attribute (re)assignments are in scope; snapshot and restore the document
|
|
16308
|
+
// attributes so the downstream steps and conversion still start from the restored
|
|
16309
|
+
// (header) state, matching Ruby's restore_attributes-before-convert invariant.
|
|
16310
|
+
//
|
|
16311
|
+
// This runs in two passes because list item / table cell / dlist text may contain
|
|
16312
|
+
// natural cross-references (e.g. <<Some section title>>) that resolve against the
|
|
16313
|
+
// reftext→id map. Pass 1 substitutes titles and reftexts only, so every section
|
|
16314
|
+
// reftext is known; the map is then built; pass 2 substitutes the block content
|
|
16315
|
+
// text, where resolveId() can now match those natural references.
|
|
16316
|
+
const attributesSnapshot = { ...this.attributes };
|
|
16317
|
+
// Pass 1: titles + reftexts (no block content text yet).
|
|
16318
|
+
await this._resolveAllTexts(this, false);
|
|
16319
|
+
this._restoreAttributeSnapshot(attributesSnapshot);
|
|
16241
16320
|
// Pre-compute reftext for all registered inline anchor nodes.
|
|
16242
16321
|
for (const ref of Object.values(this.catalog.refs)) {
|
|
16243
16322
|
if (ref && typeof ref.precomputeReftext === 'function') {
|
|
@@ -16246,6 +16325,15 @@ class Document extends AbstractBlock {
|
|
|
16246
16325
|
}
|
|
16247
16326
|
// Build the reftext→id lookup map so that resolveId() is synchronous.
|
|
16248
16327
|
await this._buildReftextsMap();
|
|
16328
|
+
// Pass 2: list item / table cell / dlist text, now that natural cross-references
|
|
16329
|
+
// can be resolved against the reftext→id map.
|
|
16330
|
+
await this._resolveAllTexts(this, true);
|
|
16331
|
+
this._restoreAttributeSnapshot(attributesSnapshot);
|
|
16332
|
+
// Reset the footnote counter so that body-content footnotes (processed during conversion)
|
|
16333
|
+
// start numbering from 1, reproducing Ruby's "out of sequence" quirk: title footnotes are
|
|
16334
|
+
// numbered during parsing via apply_title_subs, then the counter restarts for body content.
|
|
16335
|
+
delete this.attributes['footnote-number'];
|
|
16336
|
+
delete this._counters['footnote-number'];
|
|
16249
16337
|
|
|
16250
16338
|
this._parsed = true;
|
|
16251
16339
|
return this
|
|
@@ -17300,12 +17388,39 @@ class Document extends AbstractBlock {
|
|
|
17300
17388
|
: ['attributes']
|
|
17301
17389
|
}
|
|
17302
17390
|
|
|
17391
|
+
/**
|
|
17392
|
+
* @private
|
|
17393
|
+
* Restore the document attributes to a previously captured snapshot, discarding any
|
|
17394
|
+
* body-level (re)assignments replayed while pre-computing text. Mirrors Ruby's
|
|
17395
|
+
* restore_attributes-before-convert invariant.
|
|
17396
|
+
* @param {Object} snapshot - The attributes snapshot to restore.
|
|
17397
|
+
*/
|
|
17398
|
+
_restoreAttributeSnapshot(snapshot) {
|
|
17399
|
+
for (const key of Reflect.ownKeys(this.attributes))
|
|
17400
|
+
delete this.attributes[key];
|
|
17401
|
+
Object.assign(this.attributes, snapshot);
|
|
17402
|
+
}
|
|
17403
|
+
|
|
17303
17404
|
/**
|
|
17304
17405
|
* @private
|
|
17305
17406
|
* Walk the block tree and pre-compute all async text values.
|
|
17306
17407
|
* Handles titles (AbstractBlock), list item text, table cell text, and reftexts.
|
|
17307
|
-
|
|
17308
|
-
|
|
17408
|
+
*
|
|
17409
|
+
* Runs in two passes (see {@link parse}): with `resolveContent` false only titles and
|
|
17410
|
+
* reftexts are substituted (so the reftext→id map can be built); with `resolveContent`
|
|
17411
|
+
* true the list item / table cell / dlist text is substituted, resolving any natural
|
|
17412
|
+
* cross-references against the now-complete map. Title/reftext pre-computation is
|
|
17413
|
+
* idempotent (results are cached), so running it in both passes is a no-op the second time.
|
|
17414
|
+
* @param {AbstractBlock} block - The block to resolve.
|
|
17415
|
+
* @param {boolean} resolveContent - Whether to substitute list item / cell / dlist text.
|
|
17416
|
+
*/
|
|
17417
|
+
async _resolveAllTexts(block, resolveContent) {
|
|
17418
|
+
// Replay this block's attribute entries (in document order, since the walk is
|
|
17419
|
+
// depth-first pre-order like conversion) so that body-level attribute assignments
|
|
17420
|
+
// and reassignments are in scope when the block's — and its descendants' and later
|
|
17421
|
+
// siblings' — title / list item / table cell / reftext values are substituted.
|
|
17422
|
+
// Mirrors AbstractBlock#convert, which calls playbackAttributes before converting.
|
|
17423
|
+
this.playbackAttributes(block.attributes);
|
|
17309
17424
|
// The header section lives outside document.blocks; pre-compute its title here so
|
|
17310
17425
|
// that doc.doctitle() returns the fully-substituted title (with replacements applied,
|
|
17311
17426
|
// e.g. ' → ’) rather than the header-subs-only fallback.
|
|
@@ -17325,12 +17440,12 @@ class Document extends AbstractBlock {
|
|
|
17325
17440
|
// dlist.blocks is an array of [[term, ...], item_or_null] pairs.
|
|
17326
17441
|
for (const [terms, item] of block.blocks ?? []) {
|
|
17327
17442
|
for (const term of terms ?? []) {
|
|
17328
|
-
await term.precomputeText?.();
|
|
17329
|
-
await this._resolveAllTexts(term);
|
|
17443
|
+
if (resolveContent) await term.precomputeText?.();
|
|
17444
|
+
await this._resolveAllTexts(term, resolveContent);
|
|
17330
17445
|
}
|
|
17331
17446
|
if (item) {
|
|
17332
|
-
await item.precomputeText?.();
|
|
17333
|
-
await this._resolveAllTexts(item);
|
|
17447
|
+
if (resolveContent) await item.precomputeText?.();
|
|
17448
|
+
await this._resolveAllTexts(item, resolveContent);
|
|
17334
17449
|
}
|
|
17335
17450
|
}
|
|
17336
17451
|
} else if (ctx === 'table') {
|
|
@@ -17340,14 +17455,14 @@ class Document extends AbstractBlock {
|
|
|
17340
17455
|
...(block.rows?.foot ?? []),
|
|
17341
17456
|
]) {
|
|
17342
17457
|
for (const cell of row) {
|
|
17343
|
-
await cell.precomputeText?.();
|
|
17458
|
+
if (resolveContent) await cell.precomputeText?.();
|
|
17344
17459
|
await cell.precomputeReftext?.();
|
|
17345
17460
|
}
|
|
17346
17461
|
}
|
|
17347
17462
|
} else {
|
|
17348
17463
|
for (const child of block.blocks ?? []) {
|
|
17349
|
-
await child.precomputeText?.();
|
|
17350
|
-
await this._resolveAllTexts(child);
|
|
17464
|
+
if (resolveContent) await child.precomputeText?.();
|
|
17465
|
+
await this._resolveAllTexts(child, resolveContent);
|
|
17351
17466
|
}
|
|
17352
17467
|
}
|
|
17353
17468
|
}
|
|
@@ -17639,14 +17754,19 @@ class Document extends AbstractBlock {
|
|
|
17639
17754
|
let newBasebackend, newFiletype;
|
|
17640
17755
|
|
|
17641
17756
|
if (converter && typeof converter._getBackendTraits === 'function') {
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17757
|
+
// Read the traits object directly rather than the same-named accessor
|
|
17758
|
+
// methods. A user converter that declares flat string properties
|
|
17759
|
+
// (`converter.outfilesuffix = '.html'`, convention #2) keeps them intact,
|
|
17760
|
+
// so callers reading `converter.outfilesuffix` still see the string.
|
|
17761
|
+
const traits = converter._getBackendTraits();
|
|
17762
|
+
newBasebackend = traits.basebackend;
|
|
17763
|
+
newFiletype = traits.filetype;
|
|
17764
|
+
const htmlsyntax = traits.htmlsyntax;
|
|
17645
17765
|
if (htmlsyntax) attrs.htmlsyntax = htmlsyntax;
|
|
17646
17766
|
if (init) {
|
|
17647
|
-
attrs.outfilesuffix ??=
|
|
17767
|
+
attrs.outfilesuffix ??= traits.outfilesuffix;
|
|
17648
17768
|
} else if (!this.isAttributeLocked('outfilesuffix')) {
|
|
17649
|
-
attrs.outfilesuffix =
|
|
17769
|
+
attrs.outfilesuffix = traits.outfilesuffix;
|
|
17650
17770
|
}
|
|
17651
17771
|
} else if (converter) {
|
|
17652
17772
|
const traits = deriveBackendTraits(newBackend);
|
|
@@ -18034,7 +18154,7 @@ const Substitutors = {
|
|
|
18034
18154
|
*
|
|
18035
18155
|
* @param {string|string[]} text - The text to process; must not be null.
|
|
18036
18156
|
* @param {string[]} [subs=NORMAL_SUBS] - The substitutions to perform.
|
|
18037
|
-
* @returns {string|string[]} Text with substitutions applied.
|
|
18157
|
+
* @returns {Promise<string|string[]>} Text with substitutions applied.
|
|
18038
18158
|
*/
|
|
18039
18159
|
async applySubs(text, subs = NORMAL_SUBS) {
|
|
18040
18160
|
const isEmpty = Array.isArray(text) ? text.length === 0 : text.length === 0;
|
|
@@ -18148,7 +18268,7 @@ const Substitutors = {
|
|
|
18148
18268
|
* Substitute quoted text (emphasis, strong, monospaced, etc.)
|
|
18149
18269
|
*
|
|
18150
18270
|
* @param {string} text
|
|
18151
|
-
* @returns {string}
|
|
18271
|
+
* @returns {Promise<string>}
|
|
18152
18272
|
*/
|
|
18153
18273
|
async subQuotes(text) {
|
|
18154
18274
|
const compat = this.document.compatMode;
|
|
@@ -18320,7 +18440,7 @@ const Substitutors = {
|
|
|
18320
18440
|
* Substitute inline macros (links, images, etc.)
|
|
18321
18441
|
*
|
|
18322
18442
|
* @param {string} text
|
|
18323
|
-
* @returns {string}
|
|
18443
|
+
* @returns {Promise<string>}
|
|
18324
18444
|
*/
|
|
18325
18445
|
async subMacros(text) {
|
|
18326
18446
|
const foundSquareBracket = text.includes('[');
|
|
@@ -19215,7 +19335,7 @@ const Substitutors = {
|
|
|
19215
19335
|
* Substitute post replacements (hard line breaks).
|
|
19216
19336
|
*
|
|
19217
19337
|
* @param {string} text
|
|
19218
|
-
* @returns {string}
|
|
19338
|
+
* @returns {Promise<string>}
|
|
19219
19339
|
*/
|
|
19220
19340
|
async subPostReplacements(text) {
|
|
19221
19341
|
if (
|
|
@@ -19253,7 +19373,7 @@ const Substitutors = {
|
|
|
19253
19373
|
*
|
|
19254
19374
|
* @param {string} source
|
|
19255
19375
|
* @param {boolean} processCallouts
|
|
19256
|
-
* @returns {string}
|
|
19376
|
+
* @returns {Promise<string>}
|
|
19257
19377
|
*/
|
|
19258
19378
|
async subSource(source, processCallouts) {
|
|
19259
19379
|
return processCallouts
|
|
@@ -19265,7 +19385,7 @@ const Substitutors = {
|
|
|
19265
19385
|
* Substitute callout source references.
|
|
19266
19386
|
*
|
|
19267
19387
|
* @param {string} text
|
|
19268
|
-
* @returns {string}
|
|
19388
|
+
* @returns {Promise<string>}
|
|
19269
19389
|
*/
|
|
19270
19390
|
async subCallouts(text) {
|
|
19271
19391
|
const calloutRx = this.hasAttribute('line-comment')
|
|
@@ -19294,7 +19414,7 @@ const Substitutors = {
|
|
|
19294
19414
|
*
|
|
19295
19415
|
* @param {string} source
|
|
19296
19416
|
* @param {boolean} processCallouts
|
|
19297
|
-
* @returns {string}
|
|
19417
|
+
* @returns {Promise<string>}
|
|
19298
19418
|
*/
|
|
19299
19419
|
async highlightSource(source, processCallouts) {
|
|
19300
19420
|
const syntaxHl = this.document.syntaxHighlighter;
|
|
@@ -19640,7 +19760,7 @@ const Substitutors = {
|
|
|
19640
19760
|
* Restore passthrough text by reinserting into placeholder positions.
|
|
19641
19761
|
*
|
|
19642
19762
|
* @param {string} text
|
|
19643
|
-
* @returns {string}
|
|
19763
|
+
* @returns {Promise<string>}
|
|
19644
19764
|
*/
|
|
19645
19765
|
async restorePassthroughs(text) {
|
|
19646
19766
|
if (!text.includes(PASS_START)) return text
|
|
@@ -19832,7 +19952,7 @@ const Substitutors = {
|
|
|
19832
19952
|
* @param {string} attrlist
|
|
19833
19953
|
* @param {string[]} [posattrs=[]]
|
|
19834
19954
|
* @param {Object} [opts={}]
|
|
19835
|
-
* @returns {Object}
|
|
19955
|
+
* @returns {Promise<Object>}
|
|
19836
19956
|
*/
|
|
19837
19957
|
async parseAttributes(attrlist, posattrs = [], opts = {}) {
|
|
19838
19958
|
if (!attrlist || attrlist.length === 0) return {}
|
|
@@ -20957,7 +21077,7 @@ class Html5Converter extends ConverterBase {
|
|
|
20957
21077
|
let reproducible;
|
|
20958
21078
|
if (!(reproducible = node.hasAttribute('reproducible'))) {
|
|
20959
21079
|
result.push(
|
|
20960
|
-
`<meta name="generator" content="Asciidoctor ${node.getAttribute('asciidoctor-version')}"${slash}>`
|
|
21080
|
+
`<meta name="generator" content="Asciidoctor.js ${node.getAttribute('asciidoctor-version')}"${slash}>`
|
|
20961
21081
|
);
|
|
20962
21082
|
}
|
|
20963
21083
|
if (node.hasAttribute('app-name')) {
|
|
@@ -21674,7 +21794,9 @@ ${await node.content()}
|
|
|
21674
21794
|
const slash = this._voidSlash;
|
|
21675
21795
|
let img, src;
|
|
21676
21796
|
if (
|
|
21677
|
-
(node.hasAttribute('format', 'svg') ||
|
|
21797
|
+
(node.hasAttribute('format', 'svg') ||
|
|
21798
|
+
target.includes('.svg') ||
|
|
21799
|
+
target.startsWith('data:image/svg+xml')) &&
|
|
21678
21800
|
node.document.safe < SafeMode.SECURE
|
|
21679
21801
|
) {
|
|
21680
21802
|
if (node.hasOption('inline')) {
|
|
@@ -22515,7 +22637,9 @@ Your browser does not support the video tag.
|
|
|
22515
22637
|
if (node.hasAttribute('title'))
|
|
22516
22638
|
attrs += ` title="${node.getAttribute('title')}"`;
|
|
22517
22639
|
if (
|
|
22518
|
-
(node.hasAttribute('format', 'svg') ||
|
|
22640
|
+
(node.hasAttribute('format', 'svg') ||
|
|
22641
|
+
target.includes('.svg') ||
|
|
22642
|
+
target.startsWith('data:image/svg+xml')) &&
|
|
22519
22643
|
node.document.safe < SafeMode.SECURE
|
|
22520
22644
|
) {
|
|
22521
22645
|
if (node.hasOption('inline')) {
|
|
@@ -22608,12 +22732,17 @@ Your browser does not support the video tag.
|
|
|
22608
22732
|
|
|
22609
22733
|
// NOTE expose readSvgContents for Bespoke converter
|
|
22610
22734
|
async readSvgContents(node, target) {
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22735
|
+
// A data-URI carries the SVG in the target itself (e.g. an embedded diagram
|
|
22736
|
+
// produced with `:data-uri:`), so decode it directly rather than trying to
|
|
22737
|
+
// read it as a file or remote URI.
|
|
22738
|
+
let svg = target.startsWith('data:')
|
|
22739
|
+
? this._decodeDataUri(target)
|
|
22740
|
+
: await node.readContents(target, {
|
|
22741
|
+
start: node.document.getAttribute('imagesdir'),
|
|
22742
|
+
normalize: true,
|
|
22743
|
+
label: 'SVG',
|
|
22744
|
+
warnIfEmpty: true,
|
|
22745
|
+
});
|
|
22617
22746
|
if (!svg) return null
|
|
22618
22747
|
if (!svg.startsWith('<svg')) svg = svg.replace(SvgPreambleRx, '');
|
|
22619
22748
|
// Fix incomplete SVG start tag (missing closing >) by inserting > before the first child element.
|
|
@@ -22645,6 +22774,27 @@ Your browser does not support the video tag.
|
|
|
22645
22774
|
|
|
22646
22775
|
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
22647
22776
|
|
|
22777
|
+
/**
|
|
22778
|
+
* Decode an inline `data:` URI to its text contents (e.g. an SVG document) so
|
|
22779
|
+
* an image whose target is a data-URI can be embedded inline. Supports both
|
|
22780
|
+
* Base64 (`;base64,`) and percent-encoded payloads. Returns null when the
|
|
22781
|
+
* payload is missing.
|
|
22782
|
+
*
|
|
22783
|
+
* @internal
|
|
22784
|
+
* @private
|
|
22785
|
+
*/
|
|
22786
|
+
_decodeDataUri(target) {
|
|
22787
|
+
const comma = target.indexOf(',');
|
|
22788
|
+
if (comma === -1) return null
|
|
22789
|
+
const meta = target.slice('data:'.length, comma);
|
|
22790
|
+
const data = target.slice(comma + 1);
|
|
22791
|
+
if (/;base64\b/i.test(meta)) {
|
|
22792
|
+
const bytes = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
22793
|
+
return new TextDecoder('utf-8').decode(bytes)
|
|
22794
|
+
}
|
|
22795
|
+
return decodeURIComponent(data)
|
|
22796
|
+
}
|
|
22797
|
+
|
|
22648
22798
|
/**
|
|
22649
22799
|
* @internal
|
|
22650
22800
|
* @private
|
|
@@ -22887,8 +23037,7 @@ const composite = /*#__PURE__*/Object.freeze({
|
|
|
22887
23037
|
// - Ruby File.directory?/File.file? → fsp.stat().isDirectory()/isFile() (async).
|
|
22888
23038
|
// - Ruby File.basename / File.expand_path → node:path basename / resolve.
|
|
22889
23039
|
// - PathResolver.system_path → pathResolver.systemPath().
|
|
22890
|
-
// - template.render(node, opts) → template.render({node, opts, helpers}).
|
|
22891
|
-
// - Helpers loaded from helpers.js/helpers.cjs; can export configure(enginesContext).
|
|
23040
|
+
// - template.render(node, opts) → template.render({node, opts, helpers}).// - Helpers loaded from helpers.js/helpers.cjs/helpers.mjs; can export configure(enginesContext).
|
|
22892
23041
|
// - Custom engines registered via TemplateConverter.TemplateEngine.register(ext, adapter).
|
|
22893
23042
|
// - Thread safety / Mutex → not needed (single-threaded JS).
|
|
22894
23043
|
// - load_eruby / eRuby support → not applicable in JS environment.
|
|
@@ -23162,7 +23311,11 @@ class TemplateConverter extends ConverterBase {
|
|
|
23162
23311
|
if (!(await _isFile(file))) continue
|
|
23163
23312
|
|
|
23164
23313
|
// Collect helpers separately; process after all templates.
|
|
23165
|
-
if (
|
|
23314
|
+
if (
|
|
23315
|
+
basename === 'helpers.js' ||
|
|
23316
|
+
basename === 'helpers.cjs' ||
|
|
23317
|
+
basename === 'helpers.mjs'
|
|
23318
|
+
) {
|
|
23166
23319
|
helpersFile = file;
|
|
23167
23320
|
continue
|
|
23168
23321
|
}
|
|
@@ -23230,8 +23383,8 @@ class TemplateConverter extends ConverterBase {
|
|
|
23230
23383
|
const pugOpts = { ...(this.engineOptions.pug ?? {}), filename: file };
|
|
23231
23384
|
const renderFn = pug.compileFile(file, pugOpts);
|
|
23232
23385
|
template = { render: renderFn, file };
|
|
23233
|
-
} else if (ext === 'js' || ext === 'cjs') {
|
|
23234
|
-
const renderFn =
|
|
23386
|
+
} else if (ext === 'js' || ext === 'cjs' || ext === 'mjs') {
|
|
23387
|
+
const renderFn = await _loadModule(file, ext);
|
|
23235
23388
|
template = { render: renderFn, file };
|
|
23236
23389
|
} else {
|
|
23237
23390
|
// Fall back to custom TemplateEngine registry.
|
|
@@ -23246,16 +23399,20 @@ class TemplateConverter extends ConverterBase {
|
|
|
23246
23399
|
result[name] = template;
|
|
23247
23400
|
}
|
|
23248
23401
|
|
|
23249
|
-
// Load helpers if found (or if a helpers
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23255
|
-
|
|
23402
|
+
// Load helpers if found (or if a helpers file exists at the top of the dir).
|
|
23403
|
+
if (!helpersFile) {
|
|
23404
|
+
for (const ext of ['js', 'cjs', 'mjs']) {
|
|
23405
|
+
const fallback = path.join(templateDir, `helpers.${ext}`);
|
|
23406
|
+
if (await _isFile(fallback)) {
|
|
23407
|
+
helpersFile = fallback;
|
|
23408
|
+
break
|
|
23409
|
+
}
|
|
23410
|
+
}
|
|
23411
|
+
}
|
|
23256
23412
|
|
|
23257
23413
|
if (helpersFile) {
|
|
23258
|
-
const
|
|
23414
|
+
const helpersExt = helpersFile.slice(helpersFile.lastIndexOf('.') + 1);
|
|
23415
|
+
const ctx = await _loadModule(helpersFile, helpersExt);
|
|
23259
23416
|
if (typeof ctx.configure === 'function') ctx.configure(enginesCtx);
|
|
23260
23417
|
result['helpers.js'] = { file: helpersFile, ctx };
|
|
23261
23418
|
}
|
|
@@ -23283,6 +23440,32 @@ class TemplateConverter extends ConverterBase {
|
|
|
23283
23440
|
|
|
23284
23441
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
23285
23442
|
|
|
23443
|
+
/**
|
|
23444
|
+
* Load a JavaScript module (template render function or helpers object) by
|
|
23445
|
+
* file extension.
|
|
23446
|
+
*
|
|
23447
|
+
* - `.cjs` files are always CommonJS: require() returns `module.exports`.
|
|
23448
|
+
* - `.js` and `.mjs` files are loaded with a dynamic `import()`, which Node
|
|
23449
|
+
* resolves as either ES modules or CommonJS. The export is taken from the
|
|
23450
|
+
* module's default export (`export default …` in ESM, `module.exports = …`
|
|
23451
|
+
* in CommonJS via interop); when there is no default export the module
|
|
23452
|
+
* namespace is returned instead, so ESM helpers exposing named exports
|
|
23453
|
+
* (`export function configure() {}`) work too.
|
|
23454
|
+
* @param {string} file - absolute path to the module file
|
|
23455
|
+
* @param {string} ext - file extension without the leading dot ('js' | 'cjs' | 'mjs')
|
|
23456
|
+
* @returns {Promise<*>} the module's default export, or its namespace
|
|
23457
|
+
* @internal
|
|
23458
|
+
* @private
|
|
23459
|
+
*/
|
|
23460
|
+
async function _loadModule(file, ext) {
|
|
23461
|
+
if (ext === 'cjs') {
|
|
23462
|
+
const mod = _require(file);
|
|
23463
|
+
return mod?.default ? mod.default : mod
|
|
23464
|
+
}
|
|
23465
|
+
const mod = await import(node_url.pathToFileURL(file).href);
|
|
23466
|
+
return mod.default ?? mod
|
|
23467
|
+
}
|
|
23468
|
+
|
|
23286
23469
|
/**
|
|
23287
23470
|
* @internal
|
|
23288
23471
|
* @private
|
|
@@ -24471,7 +24654,7 @@ class ManPageConverter extends ConverterBase {
|
|
|
24471
24654
|
`'\\" t
|
|
24472
24655
|
.\\" Title: ${mantitle}
|
|
24473
24656
|
.\\" Author: ${node.hasAttribute('authors') ? node.getAttribute('authors') : '[see the "AUTHOR(S)" section]'}
|
|
24474
|
-
.\\" Generator: Asciidoctor ${node.getAttribute('asciidoctor-version')}`,
|
|
24657
|
+
.\\" Generator: Asciidoctor.js ${node.getAttribute('asciidoctor-version')}`,
|
|
24475
24658
|
];
|
|
24476
24659
|
|
|
24477
24660
|
if (docdate) result.push(`.\\" Date: ${docdate}`);
|
|
@@ -25329,6 +25512,7 @@ exports.Inline = Inline;
|
|
|
25329
25512
|
exports.InlineMacroProcessor = InlineMacroProcessor;
|
|
25330
25513
|
exports.List = List;
|
|
25331
25514
|
exports.ListItem = ListItem;
|
|
25515
|
+
exports.LogMessage = LogMessage;
|
|
25332
25516
|
exports.Logger = Logger;
|
|
25333
25517
|
exports.LoggerManager = LoggerManager;
|
|
25334
25518
|
exports.MemoryHttpCache = MemoryHttpCache;
|