@baiyibai-antora/extensions 1.12.0 → 1.14.0
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/CHANGELOG.adoc +25 -0
- package/README.adoc +17 -1
- package/dist/extensions/attribute-distiller/index.d.ts +6 -0
- package/dist/extensions/attribute-distiller/index.d.ts.map +1 -1
- package/dist/extensions/attribute-distiller/index.js +92 -16
- package/dist/extensions/attribute-distiller/index.js.map +1 -1
- package/dist/extensions/autoterm/index.d.ts +34 -25
- package/dist/extensions/autoterm/index.d.ts.map +1 -1
- package/dist/extensions/autoterm/index.js +52 -215
- package/dist/extensions/autoterm/index.js.map +1 -1
- package/dist/extensions/cite-it/asciidoc.d.ts +29 -0
- package/dist/extensions/cite-it/asciidoc.d.ts.map +1 -0
- package/dist/extensions/cite-it/asciidoc.js +32 -0
- package/dist/extensions/cite-it/asciidoc.js.map +1 -0
- package/dist/extensions/cite-it/assembly-numbering.d.ts +202 -0
- package/dist/extensions/cite-it/assembly-numbering.d.ts.map +1 -0
- package/dist/extensions/cite-it/assembly-numbering.js +385 -0
- package/dist/extensions/cite-it/assembly-numbering.js.map +1 -0
- package/dist/extensions/cite-it/csl.d.ts +45 -0
- package/dist/extensions/cite-it/csl.d.ts.map +1 -0
- package/dist/extensions/cite-it/csl.js +139 -0
- package/dist/extensions/cite-it/csl.js.map +1 -0
- package/dist/extensions/cite-it/index.d.ts +300 -0
- package/dist/extensions/cite-it/index.d.ts.map +1 -0
- package/dist/extensions/cite-it/index.js +1098 -0
- package/dist/extensions/cite-it/index.js.map +1 -0
- package/dist/extensions/cite-it/macro.d.ts +104 -0
- package/dist/extensions/cite-it/macro.d.ts.map +1 -0
- package/dist/extensions/cite-it/macro.js +245 -0
- package/dist/extensions/cite-it/macro.js.map +1 -0
- package/dist/extensions/cite-it/registry.d.ts +64 -0
- package/dist/extensions/cite-it/registry.d.ts.map +1 -0
- package/dist/extensions/cite-it/registry.js +157 -0
- package/dist/extensions/cite-it/registry.js.map +1 -0
- package/dist/extensions/trademark-it/index.d.ts +91 -22
- package/dist/extensions/trademark-it/index.d.ts.map +1 -1
- package/dist/extensions/trademark-it/index.js +239 -242
- package/dist/extensions/trademark-it/index.js.map +1 -1
- package/dist/lib/asciidoc-conditionals.d.ts +59 -0
- package/dist/lib/asciidoc-conditionals.d.ts.map +1 -0
- package/dist/lib/asciidoc-conditionals.js +0 -0
- package/dist/lib/asciidoc-conditionals.js.map +1 -0
- package/dist/lib/asciidoc-indexterms.d.ts +126 -0
- package/dist/lib/asciidoc-indexterms.d.ts.map +1 -0
- package/dist/lib/asciidoc-indexterms.js +261 -0
- package/dist/lib/asciidoc-indexterms.js.map +1 -0
- package/dist/lib/asciidoc-table.d.ts +32 -0
- package/dist/lib/asciidoc-table.d.ts.map +1 -0
- package/dist/lib/asciidoc-table.js +55 -0
- package/dist/lib/asciidoc-table.js.map +1 -0
- package/package.json +19 -4
- package/resources/cite-it/ieee-bracket.csl +519 -0
- package/resources/cite-it/locale-en-US.xml +864 -0
|
@@ -83,16 +83,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
83
83
|
exports.formatList = formatList;
|
|
84
84
|
exports.parseTrademarks = parseTrademarks;
|
|
85
85
|
exports.mergeTrademarks = mergeTrademarks;
|
|
86
|
+
exports.stripComments = stripComments;
|
|
86
87
|
exports.stripNonContent = stripNonContent;
|
|
87
88
|
exports.extractSvgText = extractSvgText;
|
|
88
89
|
exports.findTrademarks = findTrademarks;
|
|
89
90
|
exports.generateTrademarkPartial = generateTrademarkPartial;
|
|
90
|
-
exports.parseIncludeTarget = parseIncludeTarget;
|
|
91
91
|
exports.resolveRelativeIncludePath = resolveRelativeIncludePath;
|
|
92
92
|
exports.applyIncludeFilters = applyIncludeFilters;
|
|
93
93
|
exports.collectIncludeText = collectIncludeText;
|
|
94
94
|
exports.register = register;
|
|
95
|
+
exports.getComponentVersions = getComponentVersions;
|
|
95
96
|
const log_time_1 = require("../../lib/log-time");
|
|
97
|
+
const asciidoc_table_1 = require("../../lib/asciidoc-table");
|
|
98
|
+
const asciidoc_conditionals_1 = require("../../lib/asciidoc-conditionals");
|
|
96
99
|
const fs = __importStar(require("node:fs"));
|
|
97
100
|
const path = __importStar(require("node:path"));
|
|
98
101
|
// ---------------------------------------------------------------------------
|
|
@@ -160,6 +163,28 @@ function ensureStringArray(v) {
|
|
|
160
163
|
return v.split(',').map((s) => s.trim()).filter(Boolean);
|
|
161
164
|
return [];
|
|
162
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Locates this extension's own entry in `playbook.antora.extensions` by
|
|
168
|
+
* whether it carries `trademark_it`/`trademarkIt`/`trademarkit` as an own
|
|
169
|
+
* key, not by matching a substring of its `require` string. A require-
|
|
170
|
+
* string match breaks the moment a consumer vendors or re-exports this
|
|
171
|
+
* package under a different name or path: the require string then never
|
|
172
|
+
* contains "trademark-it"/"trademark_it" at all, `.find()` returns
|
|
173
|
+
* nothing, and every configured setting silently falls back to hardcoded
|
|
174
|
+
* defaults, no warning, no error (confirmed as a real report, not a
|
|
175
|
+
* hypothetical, and the same bug in cite-it/autoterm). Checking for the
|
|
176
|
+
* config key itself identifies the right entry regardless of how it was
|
|
177
|
+
* required; the require-string check remains only as a fallback for the
|
|
178
|
+
* case where no entry carries the key at all.
|
|
179
|
+
*/
|
|
180
|
+
function findOwnExtEntry(entries) {
|
|
181
|
+
return entries.find((ext) => 'trademark_it' in ext || 'trademarkIt' in ext || 'trademarkit' in ext)
|
|
182
|
+
?? entries.find((ext) => {
|
|
183
|
+
const req = String(ext.require ?? '');
|
|
184
|
+
return req.includes('trademark-it') || req.includes('trademark_it');
|
|
185
|
+
})
|
|
186
|
+
?? {};
|
|
187
|
+
}
|
|
163
188
|
/**
|
|
164
189
|
* Build a case-insensitive membership test for an ignore list. Trademark
|
|
165
190
|
* scanning is case-insensitive, so ignore matching must be too; otherwise an
|
|
@@ -205,68 +230,19 @@ function superscriptSymbols(s) {
|
|
|
205
230
|
return s.replace(/(®)/g, '^$1^');
|
|
206
231
|
}
|
|
207
232
|
/**
|
|
208
|
-
* Parse an AsciiDoc table into an array of string-cell rows
|
|
209
|
-
* Handles the `|cell` style used in the trademark
|
|
210
|
-
*
|
|
211
|
-
* The table format is:
|
|
212
|
-
* |===
|
|
213
|
-
* |col0
|
|
214
|
-
* |col1
|
|
215
|
-
* ...
|
|
216
|
-
* |===
|
|
233
|
+
* Parse an AsciiDoc table into an array of string-cell rows, header row
|
|
234
|
+
* included as rows[0]. Handles the `|cell` style used in the trademark
|
|
235
|
+
* table: header cells inline on one line, data cells one per line.
|
|
217
236
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* the column count declared in the [cols=…] attribute.
|
|
237
|
+
* Delegates to the real Asciidoctor parser (see
|
|
238
|
+
* agent-memory/autoterm-trademark-it-api-rewrite-scope.md, Phase 1) rather
|
|
239
|
+
* than regexing the `|===` delimiters and `[cols=…]` attribute by hand.
|
|
222
240
|
*/
|
|
223
241
|
function parseTrademarkTable(asciidocText) {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return rows;
|
|
229
|
-
const tableBody = tableMatch[1];
|
|
230
|
-
// Determine column count from [cols="…"] attribute if present.
|
|
231
|
-
// Default to 5 (matching the trademark-table.adoc format).
|
|
232
|
-
let colCount = 5;
|
|
233
|
-
const colsMatch = asciidocText.match(/\[cols\s*=\s*["']([^"']+)["']/);
|
|
234
|
-
if (colsMatch) {
|
|
235
|
-
// Count comma-separated entries; handle repeat syntax like "3*1"
|
|
236
|
-
colCount = colsMatch[1].split(',').length;
|
|
237
|
-
}
|
|
238
|
-
// Extract all cell values. The trademark table uses two styles:
|
|
239
|
-
// • Header row — all cells inline on one line, each prefixed with |:
|
|
240
|
-
// |Term |Statement |Date |Verified By |URL
|
|
241
|
-
// • Data rows — one cell per line, each on its own line:
|
|
242
|
-
// |Apple®
|
|
243
|
-
// |Apple is a registered trademark…
|
|
244
|
-
//
|
|
245
|
-
// We detect inline multi-cell lines by splitting on | and checking whether
|
|
246
|
-
// more than one non-empty segment results.
|
|
247
|
-
const cells = [];
|
|
248
|
-
for (const raw of tableBody.split('\n')) {
|
|
249
|
-
const line = raw.trim();
|
|
250
|
-
if (!line || line === '|===')
|
|
251
|
-
continue;
|
|
252
|
-
if (!line.startsWith('|'))
|
|
253
|
-
continue;
|
|
254
|
-
// Split on | — the first segment before the leading | will be empty.
|
|
255
|
-
const parts = line.split('|').map((p) => p.trim()).filter((p) => p !== '');
|
|
256
|
-
if (parts.length > 1) {
|
|
257
|
-
// Inline multi-cell row (e.g. the header).
|
|
258
|
-
cells.push(...parts);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
// Single-cell-per-line row (all data rows).
|
|
262
|
-
cells.push(line.slice(1).trim());
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// Group cells into rows of colCount.
|
|
266
|
-
for (let i = 0; i + colCount - 1 < cells.length; i += colCount) {
|
|
267
|
-
rows.push(cells.slice(i, i + colCount));
|
|
268
|
-
}
|
|
269
|
-
return rows;
|
|
242
|
+
const table = (0, asciidoc_table_1.parseAdocTable)(asciidocText);
|
|
243
|
+
if (!table)
|
|
244
|
+
return [];
|
|
245
|
+
return [...table.head, ...table.body];
|
|
270
246
|
}
|
|
271
247
|
/**
|
|
272
248
|
* Parse the trademark definition AsciiDoc file and return a map of
|
|
@@ -430,12 +406,6 @@ function escapeRegex(s) {
|
|
|
430
406
|
.replace(/[-\u2011]|\\\{nbhy\}/g, '(?:-|\u2011|\\{nbhy\\})')
|
|
431
407
|
.replace(/[ \u00A0]/g, '(?:[ \u00A0])');
|
|
432
408
|
}
|
|
433
|
-
/**
|
|
434
|
-
* Strip AsciiDoc content that should not be scanned for trademarks:
|
|
435
|
-
* - Lines beginning with // (single-line comments)
|
|
436
|
-
* - ifdef::/ifndef::/ifeval:: conditional directive lines (attribute names only,
|
|
437
|
-
* not the body content between the delimiters)
|
|
438
|
-
*/
|
|
439
409
|
/**
|
|
440
410
|
* Convert a glob pattern to a RegExp.
|
|
441
411
|
* Supports * (any chars except /), ** (any chars including /), ? (single char).
|
|
@@ -453,79 +423,72 @@ function globToRegex(pattern) {
|
|
|
453
423
|
function matchesAnyGlob(path, patterns) {
|
|
454
424
|
return patterns.some((p) => globToRegex(p).test(path));
|
|
455
425
|
}
|
|
456
|
-
|
|
457
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Strip AsciiDoc content that should not be scanned for trademarks: single-
|
|
428
|
+
* line comments, and unsatisfied ifdef::/ifndef:: conditional blocks
|
|
429
|
+
* evaluated against real attribute state.
|
|
430
|
+
*
|
|
431
|
+
* Delegates conditional evaluation to the shared, real-Asciidoctor-backed
|
|
432
|
+
* evaluator in src/lib/asciidoc-conditionals.ts (see
|
|
433
|
+
* agent-memory/trademark-it-project.md) rather than the ignore-list-only
|
|
434
|
+
* model this function used before it: an ifdef:: block now survives only
|
|
435
|
+
* when its attribute expression is genuinely satisfied, an ifndef:: block
|
|
436
|
+
* only when it genuinely is not — previously both were kept unconditionally
|
|
437
|
+
* regardless of real state, and only an ifdef:: block could be force-
|
|
438
|
+
* suppressed via ignoreConditionals (that force-list still works the same
|
|
439
|
+
* way; it now layers on top of real evaluation instead of being the only
|
|
440
|
+
* mechanism). ifeval:: content is kept, since expressions are not
|
|
441
|
+
* evaluated, matching the shared evaluator's own documented contract.
|
|
442
|
+
* Block comments (////) are now also handled by the shared evaluator's own
|
|
443
|
+
* real reader, which recognizes them natively; this function no longer
|
|
444
|
+
* tracks them itself.
|
|
445
|
+
*
|
|
446
|
+
* `isDefined` defaults to "nothing is defined": calling this without real
|
|
447
|
+
* attribute state to offer is conservative (an ifdef:: block is hidden, an
|
|
448
|
+
* ifndef:: block is shown) rather than reproducing the old "always keep"
|
|
449
|
+
* default, which was never actually correct.
|
|
450
|
+
*
|
|
451
|
+
* Comments, single-line and block, are stripped by this function itself,
|
|
452
|
+
* not the shared evaluator: verified empirically (not assumed) that
|
|
453
|
+
* Asciidoctor's raw preprocessor reader — `parse: false`, what the shared
|
|
454
|
+
* evaluator uses — does not recognize a `////` block comment as a unit at
|
|
455
|
+
* all. Block-comment recognition turned out to be a parser-level concern,
|
|
456
|
+
* only correctly handled during a full parse (confirmed separately for the
|
|
457
|
+
* real converter autoterm's indexterm scanner uses, which is unaffected).
|
|
458
|
+
* A directive-like line sitting inside an unstripped block comment would
|
|
459
|
+
* otherwise still be evaluated for real by the shared evaluator, corrupting
|
|
460
|
+
* content that is supposed to be entirely inert — caught by this file's own
|
|
461
|
+
* test suite, not assumed correct from Phase 2's autoterm work.
|
|
462
|
+
*/
|
|
463
|
+
/**
|
|
464
|
+
* Blanks `//` and `////`-delimited comment lines, preserving line count.
|
|
465
|
+
* Exported for cite-it, a second consumer that needs the same stripping
|
|
466
|
+
* before scanning page text for `citeit:[key]` usage.
|
|
467
|
+
*/
|
|
468
|
+
function stripComments(text) {
|
|
469
|
+
const out = [];
|
|
458
470
|
let inBlockComment = false;
|
|
459
|
-
// Stack of suppressed conditional attribute names (for block-form ifdef/ifndef).
|
|
460
|
-
// Each entry is the attribute name that opened the suppressed block.
|
|
461
|
-
const suppressStack = [];
|
|
462
471
|
for (const line of text.split('\n')) {
|
|
463
472
|
const trimmed = line.trimStart();
|
|
464
|
-
// Block comment delimiter ////
|
|
465
473
|
if (trimmed.startsWith('////')) {
|
|
466
474
|
inBlockComment = !inBlockComment;
|
|
467
|
-
|
|
475
|
+
out.push('');
|
|
468
476
|
continue;
|
|
469
477
|
}
|
|
470
478
|
if (inBlockComment) {
|
|
471
|
-
|
|
479
|
+
out.push('');
|
|
472
480
|
continue;
|
|
473
481
|
}
|
|
474
|
-
// Single-line comments
|
|
475
482
|
if (trimmed.startsWith('//')) {
|
|
476
|
-
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
// endif:: — close the innermost suppressed conditional block (if any).
|
|
480
|
-
if (/^endif::/.test(trimmed)) {
|
|
481
|
-
if (suppressStack.length > 0)
|
|
482
|
-
suppressStack.pop();
|
|
483
|
-
result.push('');
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
// Conditional directive lines.
|
|
487
|
-
// Inline form: ifdef::attr[content] — keep content unless attr is ignored.
|
|
488
|
-
// Block form: ifdef::attr[] — open a block; suppress if attr is ignored.
|
|
489
|
-
const condMatch = trimmed.match(/^(i(?:fdef|fndef|feval))::([^\[]*)\[([^\]]*)\]/);
|
|
490
|
-
if (condMatch) {
|
|
491
|
-
const directive = condMatch[1]; // ifdef / ifndef / ifeval
|
|
492
|
-
const attrExpr = condMatch[2]; // attribute name(s), may contain + or ,
|
|
493
|
-
const body = condMatch[3]; // inline body (empty for block form)
|
|
494
|
-
if (body === '') {
|
|
495
|
-
// Block-form directive: push onto stack if any listed attr is in ignoreConditionals.
|
|
496
|
-
const attrs = attrExpr.split(/[+,]/).map((a) => a.trim()).filter(Boolean);
|
|
497
|
-
const shouldSuppress = ignoreConditionals.length > 0 &&
|
|
498
|
-
(directive === 'ifdef'
|
|
499
|
-
? attrs.some((a) => ignoreConditionals.includes(a))
|
|
500
|
-
: false); // only suppress ifdef:: blocks, not ifndef:: / ifeval::
|
|
501
|
-
if (shouldSuppress)
|
|
502
|
-
suppressStack.push(attrExpr);
|
|
503
|
-
result.push('');
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
// Inline form: keep body unless inside a suppressed block OR the
|
|
507
|
-
// directive's own attribute is in ignoreConditionals.
|
|
508
|
-
const inlineAttrs = attrExpr.split(/[+,]/).map((a) => a.trim()).filter(Boolean);
|
|
509
|
-
const inlineSuppressed = ignoreConditionals.length > 0 &&
|
|
510
|
-
directive === 'ifdef' &&
|
|
511
|
-
inlineAttrs.some((a) => ignoreConditionals.includes(a));
|
|
512
|
-
result.push((suppressStack.length === 0 && !inlineSuppressed) ? body : '');
|
|
513
|
-
}
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
// Bare block-form directive without brackets on this line.
|
|
517
|
-
if (/^i(?:fdef|fndef|feval)::/.test(trimmed)) {
|
|
518
|
-
result.push('');
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
// Suppress content inside a matched conditional block.
|
|
522
|
-
if (suppressStack.length > 0) {
|
|
523
|
-
result.push('');
|
|
483
|
+
out.push('');
|
|
524
484
|
continue;
|
|
525
485
|
}
|
|
526
|
-
|
|
486
|
+
out.push(line);
|
|
527
487
|
}
|
|
528
|
-
return
|
|
488
|
+
return out.join('\n');
|
|
489
|
+
}
|
|
490
|
+
function stripNonContent(text, isDefined = () => false, ignoreConditionals = []) {
|
|
491
|
+
return (0, asciidoc_conditionals_1.suppressUnsetConditionals)(stripComments(text), isDefined, ignoreConditionals);
|
|
529
492
|
}
|
|
530
493
|
/**
|
|
531
494
|
* Extract human-readable text content from an SVG file for trademark scanning.
|
|
@@ -536,6 +499,21 @@ function stripNonContent(text, ignoreConditionals = []) {
|
|
|
536
499
|
* Attribute values (e.g. aria-label, inkscape:label) that commonly carry
|
|
537
500
|
* descriptive text are also extracted.
|
|
538
501
|
*
|
|
502
|
+
* <metadata>...</metadata> blocks are removed before extraction. The SVG
|
|
503
|
+
* spec defines <metadata> as a container for non-visual, non-rendered
|
|
504
|
+
* descriptive data (typically RDF/Dublin Core), never diagram content, so a
|
|
505
|
+
* false trademark hit inside one is never a real occurrence — confirmed
|
|
506
|
+
* against the spec (https://www.w3.org/TR/SVG2/struct.html#MetadataElement)
|
|
507
|
+
* rather than assumed. sodipodi:docname and content are excluded from the
|
|
508
|
+
* scanned attribute list for the same reason under a different guise:
|
|
509
|
+
* sodipodi:docname is Inkscape's saved filename, not diagram content, and
|
|
510
|
+
* content is not a real SVG attribute at all — draw.io/diagrams.net uses it
|
|
511
|
+
* to carry that tool's entire private editor-state document as one
|
|
512
|
+
* attribute value. Both are exactly the kind of export-tool metadata a
|
|
513
|
+
* reader never sees, reported as a false trademark 'hit' in practice
|
|
514
|
+
* (a saved filename like screenshot-chrome-windows.svg matching "Chrome"
|
|
515
|
+
* and "Windows" is what prompted this fix).
|
|
516
|
+
*
|
|
539
517
|
* All XML tags are stripped; entity references (& < > ")
|
|
540
518
|
* are decoded so that trademark terms are not split by encoding artefacts.
|
|
541
519
|
* Returns a plain text string safe to pass to findTrademarks.
|
|
@@ -559,24 +537,59 @@ function extractSvgText(svgSource) {
|
|
|
559
537
|
const source = /<\s*\/?[a-zA-Z]/.test(svgSource)
|
|
560
538
|
? decodeXmlEntities(svgSource)
|
|
561
539
|
: svgSource;
|
|
540
|
+
// Remove XML comments and non-visual containers before any extraction
|
|
541
|
+
// runs. Comments are never rendered, but the element/attribute regexes
|
|
542
|
+
// below have no concept of comment boundaries: prose inside a comment
|
|
543
|
+
// that happens to mention a real tag name (e.g. a fixture's own
|
|
544
|
+
// "- <title> element" documentation of itself) can extend a later
|
|
545
|
+
// element's lazy match all the way back through the comment, capturing
|
|
546
|
+
// everything in between — reproduced against this package's own SVG test
|
|
547
|
+
// fixture. <metadata> is excluded per the SVG spec's own definition of it
|
|
548
|
+
// as a non-rendered descriptive container (RDF/Dublin Core is the common
|
|
549
|
+
// real-world content) — https://www.w3.org/TR/SVG2/struct.html#MetadataElement.
|
|
550
|
+
// <script> and <style> are excluded because neither is ever visible text
|
|
551
|
+
// by definition — script is behavior, style is presentation — and both
|
|
552
|
+
// commonly carry literal browser/OS names as real content rather than
|
|
553
|
+
// incidental noise: vendor-prefixed CSS, OS-specific font-family
|
|
554
|
+
// fallbacks ("Segoe UI" is Windows-specific), and browser-sniffing script
|
|
555
|
+
// (checking navigator.userAgent for "Chrome") are all standard,
|
|
556
|
+
// unremarkable SVG authoring practice, not something a reader is meant to
|
|
557
|
+
// read as prose. Their content is also the standard real-world use of a
|
|
558
|
+
// CDATA section in SVG, wrapped precisely because it contains characters
|
|
559
|
+
// XML would otherwise try to parse.
|
|
560
|
+
// <script> and <style> are stripped wholesale above, which removes their
|
|
561
|
+
// CDATA-wrapped content along with them — CDATA's standard real-world use
|
|
562
|
+
// in SVG. What's unwrapped here instead is CDATA appearing in genuinely
|
|
563
|
+
// visible content (e.g. <desc><![CDATA[Real text]]></desc>): the final
|
|
564
|
+
// "strip any remaining tag" step below would otherwise treat the whole
|
|
565
|
+
// `<![CDATA[...]]>` span as one tag (it has no `>` until its own closing
|
|
566
|
+
// `]]>`) and discard the real text inside it entirely, not just the
|
|
567
|
+
// marker syntax.
|
|
568
|
+
const cleaned = source
|
|
569
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
570
|
+
.replace(/<metadata[^>]*>[\s\S]*?<\/metadata>/gi, ' ')
|
|
571
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
|
572
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
|
573
|
+
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
|
|
562
574
|
const textElements = ['text', 'tspan', 'title', 'desc', 'textPath'];
|
|
563
|
-
const textAttrs = ['aria-label', 'inkscape:label'
|
|
575
|
+
const textAttrs = ['aria-label', 'inkscape:label'];
|
|
564
576
|
const chunks = [];
|
|
565
577
|
// Extract content of text-bearing elements.
|
|
566
578
|
for (const tag of textElements) {
|
|
567
579
|
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'gi');
|
|
568
580
|
let m;
|
|
569
|
-
while ((m = re.exec(
|
|
581
|
+
while ((m = re.exec(cleaned)) !== null) {
|
|
570
582
|
chunks.push(m[1]);
|
|
571
583
|
}
|
|
572
584
|
}
|
|
573
585
|
// Extract named attributes that carry descriptive text.
|
|
574
586
|
for (const attr of textAttrs) {
|
|
575
|
-
// Leading boundary so short attribute
|
|
576
|
-
//
|
|
587
|
+
// Leading boundary so a short attribute name does not match the tail of
|
|
588
|
+
// a longer one (e.g. a hypothetical "label" would otherwise also match
|
|
589
|
+
// "aria-label" and "inkscape:label" themselves).
|
|
577
590
|
const re = new RegExp(`(?<![\\w:-])${attr.replace(':', '\\:')}\\s*=\\s*["']([^"']+)["']`, 'gi');
|
|
578
591
|
let m;
|
|
579
|
-
while ((m = re.exec(
|
|
592
|
+
while ((m = re.exec(cleaned)) !== null) {
|
|
580
593
|
chunks.push(m[1]);
|
|
581
594
|
}
|
|
582
595
|
}
|
|
@@ -589,9 +602,9 @@ function extractSvgText(svgSource) {
|
|
|
589
602
|
* terms that were actually found. Every member of a group is tested so that
|
|
590
603
|
* buildable declarations can be assembled from only the terms present.
|
|
591
604
|
*/
|
|
592
|
-
function findTrademarks(sourceText, trademarks, ignoreConditionals = []) {
|
|
605
|
+
function findTrademarks(sourceText, trademarks, isDefined = () => false, ignoreConditionals = []) {
|
|
593
606
|
const found = new Map();
|
|
594
|
-
const scanText = stripNonContent(sourceText, ignoreConditionals);
|
|
607
|
+
const scanText = stripNonContent(sourceText, isDefined, ignoreConditionals);
|
|
595
608
|
for (const entry of trademarks.values()) {
|
|
596
609
|
// Check the canonical base term and every alias.
|
|
597
610
|
const termsToCheck = entry.combinedTerms;
|
|
@@ -616,9 +629,9 @@ function findTrademarks(sourceText, trademarks, ignoreConditionals = []) {
|
|
|
616
629
|
* A file using several members of one group therefore reports each of them, not just the first.
|
|
617
630
|
* Used for detailed debugging to track the origin of each found trademark.
|
|
618
631
|
*/
|
|
619
|
-
function findTrademarksInFile(sourceText, trademarks, ignoreConditionals = []) {
|
|
632
|
+
function findTrademarksInFile(sourceText, trademarks, isDefined = () => false, ignoreConditionals = []) {
|
|
620
633
|
const results = [];
|
|
621
|
-
const scanText = stripNonContent(sourceText, ignoreConditionals);
|
|
634
|
+
const scanText = stripNonContent(sourceText, isDefined, ignoreConditionals);
|
|
622
635
|
// Locate first-occurrence line numbers on the stripped lines (not raw) so
|
|
623
636
|
// that line numbers point to real content, not to comment lines where the
|
|
624
637
|
// term may appear before its actual use. stripNonContent preserves line
|
|
@@ -855,88 +868,14 @@ function generateTrademarkPartial(foundBaseTerms, trademarks, cols = '22,78', le
|
|
|
855
868
|
// scanning the including component.
|
|
856
869
|
// ---------------------------------------------------------------------------
|
|
857
870
|
/**
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
864
|
-
* Missing parts fall back to the provided context defaults. An empty family
|
|
865
|
-
* tells the catalog lookup to match any family.
|
|
866
|
-
* Returns null for plain filesystem paths (no '$' and no ':' present).
|
|
867
|
-
*/
|
|
868
|
-
function parseIncludeTarget(target, defaultComponent, defaultVersion, defaultModule) {
|
|
869
|
-
let ref = target.trim();
|
|
870
|
-
// version@ prefix
|
|
871
|
-
let version = defaultVersion;
|
|
872
|
-
const atIdx = ref.indexOf('@');
|
|
873
|
-
if (atIdx >= 0) {
|
|
874
|
-
version = ref.slice(0, atIdx);
|
|
875
|
-
ref = ref.slice(atIdx + 1);
|
|
876
|
-
}
|
|
877
|
-
// '$' is the family/relative separator — present in standard Antora resource IDs.
|
|
878
|
-
const dollarIdx = ref.lastIndexOf('$');
|
|
879
|
-
if (dollarIdx >= 0) {
|
|
880
|
-
// Standard Antora resource ID with explicit family: [component:][module:]family$relative
|
|
881
|
-
const relative = ref.slice(dollarIdx + 1);
|
|
882
|
-
const prefixAndFamily = ref.slice(0, dollarIdx);
|
|
883
|
-
const lastColon = prefixAndFamily.lastIndexOf(':');
|
|
884
|
-
const family = lastColon >= 0 ? prefixAndFamily.slice(lastColon + 1) : prefixAndFamily;
|
|
885
|
-
const prefix = lastColon >= 0 ? prefixAndFamily.slice(0, lastColon) : '';
|
|
886
|
-
// prefix = '' | 'module' | 'component:module'
|
|
887
|
-
const parts = prefix ? prefix.split(':') : [];
|
|
888
|
-
let component = defaultComponent;
|
|
889
|
-
let module = defaultModule;
|
|
890
|
-
if (parts.length >= 2) {
|
|
891
|
-
component = parts[0];
|
|
892
|
-
module = parts[1];
|
|
893
|
-
}
|
|
894
|
-
else if (parts.length === 1) {
|
|
895
|
-
module = parts[0];
|
|
896
|
-
}
|
|
897
|
-
return { component, version, module, family, relative };
|
|
898
|
-
}
|
|
899
|
-
// No '$': a plain filesystem path has no colons; a colon-separated string is
|
|
900
|
-
// an Antora resource ID with an implicit family (e.g. the page shorthand
|
|
901
|
-
// 'topazu:overview:overview.adoc' used by delegating page stubs).
|
|
902
|
-
if (!ref.includes(':'))
|
|
903
|
-
return null;
|
|
904
|
-
// Parse as [component:]module:relative with family left empty so the catalog
|
|
905
|
-
// lookup in collectIncludeText can match any family.
|
|
906
|
-
const colonParts = ref.split(':');
|
|
907
|
-
let component = defaultComponent;
|
|
908
|
-
let module = defaultModule;
|
|
909
|
-
const relative = colonParts[colonParts.length - 1];
|
|
910
|
-
if (colonParts.length >= 3) {
|
|
911
|
-
component = colonParts[0];
|
|
912
|
-
module = colonParts[1];
|
|
913
|
-
}
|
|
914
|
-
else if (colonParts.length === 2) {
|
|
915
|
-
module = colonParts[0];
|
|
916
|
-
}
|
|
917
|
-
// family: '' signals the caller to match any family in the catalog.
|
|
918
|
-
return { component, version, module, family: '', relative };
|
|
919
|
-
}
|
|
920
|
-
/**
|
|
921
|
-
* Normalize a slash-separated path, resolving '.' and '..' segments.
|
|
922
|
-
* Returns null when '..' climbs above the root, which marks the target as
|
|
923
|
-
* outside the content tree.
|
|
871
|
+
* A target that looks like an Antora resource ID rather than a plain
|
|
872
|
+
* filesystem path: it carries a version@ prefix, a component/module
|
|
873
|
+
* qualifier, or a family$relative separator. Matches the exact detection
|
|
874
|
+
* `@antora/asciidoc-loader` itself uses to choose between resolveResource
|
|
875
|
+
* and getByPath, kept here as one small regex rather than a dependency, per
|
|
876
|
+
* agent-memory/autoterm-trademark-it-api-rewrite-scope.md (Phase 3).
|
|
924
877
|
*/
|
|
925
|
-
|
|
926
|
-
const out = [];
|
|
927
|
-
for (const seg of path.split('/')) {
|
|
928
|
-
if (seg === '' || seg === '.')
|
|
929
|
-
continue;
|
|
930
|
-
if (seg === '..') {
|
|
931
|
-
if (out.length === 0)
|
|
932
|
-
return null;
|
|
933
|
-
out.pop();
|
|
934
|
-
continue;
|
|
935
|
-
}
|
|
936
|
-
out.push(seg);
|
|
937
|
-
}
|
|
938
|
-
return out.join('/');
|
|
939
|
-
}
|
|
878
|
+
const RESOURCE_ID_RE = /[$:@]/;
|
|
940
879
|
/**
|
|
941
880
|
* Resolve a plain relative include target against the including file's
|
|
942
881
|
* catalog path, mirroring Asciidoctor's resolution of filesystem-style
|
|
@@ -949,7 +888,8 @@ function resolveRelativeIncludePath(target, currentPath) {
|
|
|
949
888
|
if (!currentPath)
|
|
950
889
|
return null;
|
|
951
890
|
const dir = currentPath.split('/').slice(0, -1).join('/');
|
|
952
|
-
|
|
891
|
+
const candidate = path.posix.join(dir, target.trim());
|
|
892
|
+
return candidate.startsWith('..') ? null : candidate;
|
|
953
893
|
}
|
|
954
894
|
/**
|
|
955
895
|
* Apply the tag=, tags=, and lines= attributes of an include:: directive to
|
|
@@ -1069,12 +1009,22 @@ function applyIncludeFilters(text, attrlist) {
|
|
|
1069
1009
|
* Recursively collect the text of all files referenced via include:: directives
|
|
1070
1010
|
* in `sourceText`, looking each up in the content catalog.
|
|
1071
1011
|
*
|
|
1012
|
+
* File resolution delegates to Antora's own ContentCatalog#resolveResource
|
|
1013
|
+
* (resource-ID targets: version@, component:module:family$relative, and the
|
|
1014
|
+
* familyless page-shorthand form) and #getByPath (plain relative targets,
|
|
1015
|
+
* resolved against `currentPath` first). See
|
|
1016
|
+
* agent-memory/autoterm-trademark-it-api-rewrite-scope.md (Phase 3) for why:
|
|
1017
|
+
* both are Antora's real, public, documented resolution logic, not a
|
|
1018
|
+
* reimplementation of its resource-ID grammar. Everything else here —
|
|
1019
|
+
* recursion, cycle/dedup tracking via `visited`, tag=/tags=/lines= filtering
|
|
1020
|
+
* via applyIncludeFilters — is unchanged; only file lookup moved.
|
|
1021
|
+
*
|
|
1072
1022
|
* `visited` prevents revisiting files already being scanned or already resolved
|
|
1073
1023
|
* in a previous recursion step. Callers should pre-populate it with the keys
|
|
1074
1024
|
* of all files already in the main scan set.
|
|
1075
1025
|
*
|
|
1076
|
-
* Cross-component includes without an explicit version@ prefix
|
|
1077
|
-
*
|
|
1026
|
+
* Cross-component includes without an explicit version@ prefix resolve to
|
|
1027
|
+
* that component's latest version, per resolveResource's own semantics.
|
|
1078
1028
|
*
|
|
1079
1029
|
* `scanExclude` glob patterns (same as the top-level scan_exclude config) are
|
|
1080
1030
|
* applied to each resolved file's path. Matched files are skipped and added to
|
|
@@ -1098,25 +1048,30 @@ function collectIncludeText(sourceText, contentCatalog, defaultComponent, defaul
|
|
|
1098
1048
|
const attrlist = match[2];
|
|
1099
1049
|
if (target.includes('{'))
|
|
1100
1050
|
continue;
|
|
1101
|
-
const parsed = parseIncludeTarget(target, defaultComponent, defaultVersion, defaultModule);
|
|
1102
1051
|
let file;
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1052
|
+
if (RESOURCE_ID_RE.test(target) && contentCatalog.resolveResource) {
|
|
1053
|
+
// Antora's own resource-ID resolver: version@, component:module:
|
|
1054
|
+
// qualifiers, family$relative, and the familyless page-shorthand form
|
|
1055
|
+
// are all its concern, not this function's. A malformed spec throws
|
|
1056
|
+
// rather than returning undefined; treated the same as unresolved.
|
|
1057
|
+
try {
|
|
1058
|
+
file = contentCatalog.resolveResource(target, { component: defaultComponent, version: defaultVersion, module: defaultModule }, 'page');
|
|
1059
|
+
}
|
|
1060
|
+
catch {
|
|
1061
|
+
file = undefined;
|
|
1062
|
+
}
|
|
1110
1063
|
}
|
|
1111
|
-
else {
|
|
1064
|
+
else if (!RESOURCE_ID_RE.test(target)) {
|
|
1112
1065
|
// A plain filesystem-style target resolves relative to the including
|
|
1113
1066
|
// file's own path within the same component and version.
|
|
1114
1067
|
const candidate = resolveRelativeIncludePath(target, currentPath);
|
|
1115
1068
|
if (!candidate)
|
|
1116
1069
|
continue;
|
|
1117
|
-
file = contentCatalog.
|
|
1118
|
-
|
|
1119
|
-
f.src.
|
|
1070
|
+
file = contentCatalog.getByPath
|
|
1071
|
+
? contentCatalog.getByPath({ component: defaultComponent, version: defaultVersion, path: candidate })
|
|
1072
|
+
: contentCatalog.getFiles().find((f) => f.src.component === defaultComponent &&
|
|
1073
|
+
f.src.version === defaultVersion &&
|
|
1074
|
+
f.src.path === candidate);
|
|
1120
1075
|
}
|
|
1121
1076
|
if (!file)
|
|
1122
1077
|
continue;
|
|
@@ -1174,10 +1129,7 @@ function register(context) {
|
|
|
1174
1129
|
}
|
|
1175
1130
|
});
|
|
1176
1131
|
ctx.once('playbookBuilt', async ({ playbook }) => {
|
|
1177
|
-
const extEntry = (playbook?.antora?.extensions ?? [])
|
|
1178
|
-
const req = String(ext.require ?? '');
|
|
1179
|
-
return req.includes('trademark-it') || req.includes('trademark_it');
|
|
1180
|
-
}) ?? {};
|
|
1132
|
+
const extEntry = findOwnExtEntry(playbook?.antora?.extensions ?? []);
|
|
1181
1133
|
const raw = extEntry.trademark_it ??
|
|
1182
1134
|
extEntry.trademarkIt ??
|
|
1183
1135
|
extEntry.trademarkit ??
|
|
@@ -1310,6 +1262,24 @@ function register(context) {
|
|
|
1310
1262
|
(compScanExclude.length === 0 || !matchesAnyGlob(f.src.path ?? f.src.relative ?? '', compScanExclude)));
|
|
1311
1263
|
if (scanFiles.length === 0)
|
|
1312
1264
|
continue;
|
|
1265
|
+
// Effective attribute state for conditional evaluation: the
|
|
1266
|
+
// component-version's compiled asciidoc attributes, overlaid per
|
|
1267
|
+
// scanned file with its own header entries — the same real-state
|
|
1268
|
+
// model autoterm's scan loop uses, see agent-memory/
|
|
1269
|
+
// trademark-it-project.md. compIgnoreConditionals still
|
|
1270
|
+
// force-suppresses ifdef:: blocks on top of real evaluation.
|
|
1271
|
+
const componentAttrs = (contentCatalog.getComponents?.() ?? [])
|
|
1272
|
+
.find((c) => c.name === component)?.versions
|
|
1273
|
+
.find((v) => (v.version ?? v.name) === version)?.asciidoc?.attributes ?? {};
|
|
1274
|
+
const makeIsDefined = (fileContent, family) => {
|
|
1275
|
+
const headerAttrs = family === 'page' ? (0, asciidoc_conditionals_1.pageHeaderAttributes)(fileContent) : new Map();
|
|
1276
|
+
return (attr) => {
|
|
1277
|
+
if (headerAttrs.has(attr))
|
|
1278
|
+
return headerAttrs.get(attr) === true;
|
|
1279
|
+
const v = componentAttrs[attr];
|
|
1280
|
+
return v !== undefined && v !== null && v !== false;
|
|
1281
|
+
};
|
|
1282
|
+
};
|
|
1313
1283
|
// Pre-populate the visited set with all files already being scanned so
|
|
1314
1284
|
// that include resolution does not produce duplicate text.
|
|
1315
1285
|
const includeVisited = new Set();
|
|
@@ -1322,28 +1292,50 @@ function register(context) {
|
|
|
1322
1292
|
// Resolve include:: directives per file so that trademark terms appearing
|
|
1323
1293
|
// only in cross-component (or cross-module) included files are detected.
|
|
1324
1294
|
const includeChunks = [];
|
|
1295
|
+
// Each scanned file's own conditional state is evaluated against
|
|
1296
|
+
// ITS OWN attributes (component attrs overlaid with that file's own
|
|
1297
|
+
// header entries) before joining into one combined scan blob below —
|
|
1298
|
+
// a single shared isDefined applied to the whole concatenation would
|
|
1299
|
+
// let one file's page-header override incorrectly leak onto every
|
|
1300
|
+
// other file's conditionals too. Resolved include text is stripped
|
|
1301
|
+
// with the SAME including file's isDefined, mirroring autoterm's own
|
|
1302
|
+
// scan loop exactly: a delegating page stub that includes an entire
|
|
1303
|
+
// other page (a real, deliberately-supported pattern, see
|
|
1304
|
+
// trademark-it-include-test.js) pulls in that page's raw text via
|
|
1305
|
+
// collectIncludeText, and any ifdef::/ifndef:: guards inside it need
|
|
1306
|
+
// evaluating too — reproduced directly against the real demo site,
|
|
1307
|
+
// not assumed: its own docs component includes the whole
|
|
1308
|
+
// trademark-it.adoc reference page from extensions, and leaving the
|
|
1309
|
+
// included copy's ifdef::removed[Jira...] example unstripped let it
|
|
1310
|
+
// leak into the real generated table.
|
|
1311
|
+
const strippedFileChunks = [];
|
|
1325
1312
|
for (const f of scanFiles) {
|
|
1326
1313
|
const fileContent = f.contents.toString('utf8');
|
|
1314
|
+
const isDefined = makeIsDefined(fileContent, f.src.family);
|
|
1315
|
+
strippedFileChunks.push(stripNonContent(fileContent, isDefined, compIgnoreConditionals));
|
|
1327
1316
|
// Log per-file trademark detection for debugging, split into
|
|
1328
1317
|
// added vs ignored entries and prefixed with the component name.
|
|
1329
1318
|
const scanFilePath = `${component}:${f.src.path ?? f.src.relative ?? ''}`;
|
|
1330
|
-
const fileMatches = findTrademarksInFile(fileContent, compTrademarks, compIgnoreConditionals);
|
|
1319
|
+
const fileMatches = findTrademarksInFile(fileContent, compTrademarks, isDefined, compIgnoreConditionals);
|
|
1331
1320
|
logFileMatches(logger, scanFilePath, fileMatches, isCompIgnored);
|
|
1332
1321
|
const onIncludedFile = quietMode ? undefined : (chain, includedContent) => {
|
|
1333
|
-
const includeMatches = findTrademarksInFile(includedContent, compTrademarks, compIgnoreConditionals);
|
|
1322
|
+
const includeMatches = findTrademarksInFile(includedContent, compTrademarks, isDefined, compIgnoreConditionals);
|
|
1334
1323
|
logFileMatches(logger, [scanFilePath, ...chain].join(' → '), includeMatches, isCompIgnored);
|
|
1335
1324
|
};
|
|
1336
1325
|
const extra = collectIncludeText(fileContent, contentCatalog, component, version, f.src.module ?? 'ROOT', includeVisited, compScanExclude, onIncludedFile, [], f.src.path ?? '');
|
|
1337
1326
|
if (extra)
|
|
1338
|
-
includeChunks.push(extra);
|
|
1327
|
+
includeChunks.push(stripNonContent(extra, isDefined, compIgnoreConditionals));
|
|
1339
1328
|
}
|
|
1340
1329
|
// Concatenate all source text for this component-version, including any
|
|
1341
|
-
// text resolved from cross-component include:: directives.
|
|
1330
|
+
// text resolved from cross-component include:: directives. Each scan
|
|
1331
|
+
// file's own text is already stripped above; findTrademarks' own
|
|
1332
|
+
// stripNonContent pass over the combined text is therefore a no-op
|
|
1333
|
+
// for that portion (no directives or comments remain to act on).
|
|
1342
1334
|
const combinedText = [
|
|
1343
|
-
...
|
|
1335
|
+
...strippedFileChunks,
|
|
1344
1336
|
...includeChunks,
|
|
1345
1337
|
].join('\n');
|
|
1346
|
-
const found = findTrademarks(combinedText, compTrademarks
|
|
1338
|
+
const found = findTrademarks(combinedText, compTrademarks);
|
|
1347
1339
|
// Optionally scan SVG image files for text-bearing elements.
|
|
1348
1340
|
if (compScanSvgs) {
|
|
1349
1341
|
const svgFiles = contentCatalog.getFiles().filter((f) => f.src.component === component &&
|
|
@@ -1353,7 +1345,10 @@ function register(context) {
|
|
|
1353
1345
|
(compScanExclude.length === 0 || !matchesAnyGlob(f.src.path ?? f.src.relative ?? '', compScanExclude)));
|
|
1354
1346
|
for (const f of svgFiles) {
|
|
1355
1347
|
const svgText = extractSvgText(f.contents.toString('utf8'));
|
|
1356
|
-
|
|
1348
|
+
// extracted SVG text carries no AsciiDoc conditionals or comments
|
|
1349
|
+
// to evaluate; isDefined and ignoreConditionals stay at their
|
|
1350
|
+
// defaults.
|
|
1351
|
+
const svgFound = findTrademarks(svgText, compTrademarks);
|
|
1357
1352
|
const svgMatches = [];
|
|
1358
1353
|
for (const [term, members] of svgFound) {
|
|
1359
1354
|
const existing = found.get(term) ?? new Set();
|
|
@@ -1519,7 +1514,9 @@ function resolveTrademarkFile(trademarkFileRef, contentCatalog, playbook, logger
|
|
|
1519
1514
|
}
|
|
1520
1515
|
/**
|
|
1521
1516
|
* Derive the set of unique component-version pairs from the catalog.
|
|
1522
|
-
* Returns a Map<component, Set<version>>.
|
|
1517
|
+
* Returns a Map<component, Set<version>>. Exported for cite-it, a third
|
|
1518
|
+
* consumer of this same enumeration (autoterm still builds its own inline,
|
|
1519
|
+
* a known small duplication; see root CLAUDE.md, "Content Extraction").
|
|
1523
1520
|
*/
|
|
1524
1521
|
function getComponentVersions(contentCatalog) {
|
|
1525
1522
|
const result = new Map();
|