wikiparser-node
Version:
A Node.js parser for MediaWiki markup with AST
845 lines (844 loc) • 36.4 kB
JavaScript
"use strict";
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TranscludeToken = void 0;
const string_1 = require("../util/string");
const lint_1 = require("../util/lint");
const debug_1 = require("../util/debug");
const constants_1 = require("../util/constants");
const rect_1 = require("../lib/rect");
const gapped_1 = require("../mixin/gapped");
const noEscape_1 = require("../mixin/noEscape");
const index_1 = __importDefault(require("../index"));
const index_2 = require("./index");
const parameter_1 = require("./parameter");
const atom_1 = require("./atom");
const syntax_1 = require("./syntax");
/* NOT FOR BROWSER */
const cached_1 = require("../mixin/cached");
const basicMagicWords = new Map([['=', '='], ['!', '|']]);
/**
* template or magic word
*
* 模板或魔术字
* @classdesc `{childNodes: [AtomToken|SyntaxToken, ...AtomToken[], ...ParameterToken[]]}`
*/
let TranscludeToken = (() => {
let _classDecorators = [noEscape_1.noEscape, (0, gapped_1.gapped)()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
let _classSuper = index_2.Token;
let _instanceExtraInitializers = [];
let _toHtmlInternal_decorators;
var TranscludeToken = class extends _classSuper {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_toHtmlInternal_decorators = [(0, cached_1.cached)()];
__esDecorate(this, null, _toHtmlInternal_decorators, { kind: "method", name: "toHtmlInternal", static: false, private: false, access: { has: obj => "toHtmlInternal" in obj, get: obj => obj.toHtmlInternal }, metadata: _metadata }, null, _instanceExtraInitializers);
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
TranscludeToken = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
modifier = (__runInitializers(this, _instanceExtraInitializers), '');
#type = 'template';
#colon = ':';
#raw = false;
#args = new Map();
#title;
/* NOT FOR BROWSER */
#keys = new Set();
/* NOT FOR BROWSER END */
get type() {
return this.#type;
}
/**
* module name
*
* 模块名
* @since v1.21.0
*/
get module() {
// eslint-disable-next-line no-unused-labels
LSP: return this.type === 'magic-word' && this.name === 'invoke' ? this.#getTitle().title : undefined;
}
/**
* function name
*
* 函数名
* @since v1.21.2
*/
get function() {
LSP: return this.type === 'magic-word' && this.name === 'invoke' // eslint-disable-line no-unused-labels
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
? this.childNodes[2]?.text().trim()
: undefined;
}
/* NOT FOR BROWSER */
/** whether to contain duplicated parameters / 是否存在重复参数 */
get duplication() {
return this.isTemplate() && Boolean(this.hasDuplicatedArgs());
}
set duplication(duplication) {
if (this.duplication && !duplication) {
this.fixDuplication();
}
}
/* NOT FOR BROWSER END */
/**
* @param title 模板标题或魔术字
* @param parts 参数各部分
* @throws `SyntaxError` 非法的模板名称
*/
constructor(title, parts, config, accum = []) {
let heading;
const m = /^(?:\s|\0\d+[cn]\x7F)*\0(\d+)h\x7F(?:\s|\0\d+[cn]\x7F)*/u.exec(title);
if (m) {
heading = Number(m[1]);
title = title.replace(`\0${heading}h\x7F`, accum[heading].toString().replace(/^\n/u, ''));
}
super(undefined, config, accum, {
AtomToken: 0, SyntaxToken: 0, ParameterToken: '1:',
});
const { parserFunction: [insensitive, sensitive], variable, functionHook } = config, argSubst = /^(?:\s|\0\d+[cn]\x7F)*\0\d+s\x7F/u.exec(title)?.[0];
if (argSubst) {
this.setAttribute('modifier', argSubst);
title = title.slice(argSubst.length);
}
else if (title.includes(':')) {
const [modifier, ...arg] = title.split(':'), [mt] = /^(?:\s|\0\d+[cn]\x7F)*/u.exec(arg[0] ?? '');
if (this.setModifier(`${modifier}:${mt}`)) {
title = arg.join(':').slice(mt.length);
}
}
const colon = title.search(/[::]/u), fullWidth = title[colon] === ':', isFunction = colon !== -1;
if (isFunction || parts.length === 0 && !this.#raw) {
const magicWord = isFunction ? title.slice(0, colon) : title, arg = isFunction && title.slice(colon + 1), cleaned = (0, string_1.removeComment)(magicWord), name = isFunction
? cleaned.slice(cleaned.search(/\S/u)) + (fullWidth ? ':' : '')
: cleaned.trim(), lcName = name.toLowerCase(), isOldSchema = Array.isArray(sensitive), isSensitive = isOldSchema
? sensitive.includes(name)
: Object.prototype.hasOwnProperty.call(sensitive, name), canonicalName = !isOldSchema && isSensitive
? sensitive[name]
: Object.prototype.hasOwnProperty.call(insensitive, lcName) && insensitive[lcName], isFunc = isOldSchema && isSensitive
|| !('functionHook' in config) || functionHook.includes(canonicalName), isVar = isOldSchema && isSensitive || variable.includes(canonicalName);
if (isFunction ? canonicalName && isFunc : isVar) {
this.setAttribute('name', canonicalName || lcName.replace(/^#|:$/u, ''));
this.#type = 'magic-word';
if (fullWidth) {
this.#colon = ':';
}
/^\s*uc\s*$/iu; // eslint-disable-line @typescript-eslint/no-unused-expressions
const token = new syntax_1.SyntaxToken(magicWord, new RegExp(String.raw `^\s*${name}\s*$`, isSensitive ? 'u' : 'iu'), 'magic-word-name', config, accum, { 'Stage-1': ':', '!ExtToken': '' });
super.insertAt(token);
if (arg !== false) {
parts.unshift([arg]);
}
if (this.name === 'invoke') {
/* NOT FOR BROWSER */
this.setAttribute('acceptable', { SyntaxToken: 0, AtomToken: '1:3', ParameterToken: '3:' });
this.protectChildren('1:3');
/* NOT FOR BROWSER END */
for (let i = 0; i < 2; i++) {
const part = parts.shift();
if (!part) {
break;
}
const invoke = new atom_1.AtomToken(part.join('='), `invoke-${i ? 'function' : 'module'}`, config, accum, { 'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '' });
super.insertAt(invoke);
}
}
}
}
if (this.type === 'template') {
const name = (0, string_1.removeComment)(title).trim();
if (!this.normalizeTitle(name, 10, { halfParsed: true, temporary: true }).valid) {
accum.pop();
/* NOT FOR BROWSER */
index_1.default.debug(`Invalid template name: ${(0, string_1.noWrap)(name)}`);
/* NOT FOR BROWSER END */
throw new SyntaxError('Invalid template name');
}
const token = new atom_1.AtomToken(title, 'template-name', config, accum, {
'Stage-2': ':', '!ExtToken': '', '!HeadingToken': '',
});
super.insertAt(token);
}
if (typeof heading === 'number') {
// @ts-expect-error sparse array
accum[heading] = undefined;
}
const templateLike = this.isTemplate();
let i = 1;
for (let j = 0; j < parts.length; j++) {
const part = parts[j];
if (!(templateLike || this.name === 'switch' && j > 0 || this.name === 'tag' && j > 1)) {
part[0] = part.join('=');
part.length = 1;
}
if (part.length === 1) {
part.unshift(i);
i++;
}
// @ts-expect-error abstract class
this.insertAt(new parameter_1.ParameterToken(...part, config, accum));
}
/* PRINT ONLY */
this.seal('modifier');
/* PRINT ONLY END */
/* NOT FOR BROWSER */
this.protectChildren(0);
}
/**
* Set the transclusion modifier
*
* 设置引用修饰符
* @param modifier transclusion modifier / 引用修饰符
*/
setModifier(modifier) {
const { parserFunction: [, , raw, subst] } = this.getAttribute('config'), lcModifier = (0, string_1.removeComment)(modifier).trim();
if (modifier && !lcModifier.endsWith(':')) {
return false;
}
const magicWord = lcModifier.slice(0, -1).toLowerCase(), isRaw = raw.includes(magicWord), isSubst = subst.includes(magicWord);
if (this.#raw && isRaw
|| !this.#raw && (isSubst || !modifier)
|| (debug_1.Shadow.running || this.length > 1) && (isRaw || isSubst || !modifier)) {
this.setAttribute('modifier', modifier);
this.#raw = isRaw;
return Boolean(modifier);
}
return false;
}
/**
* Check if it is a template or a module
*
* 是否是模板或模块
*/
isTemplate() {
return this.type === 'template' || this.name === 'invoke';
}
/** 获取模板或模块名 */
#getTitle() {
const isTemplate = this.type === 'template', title = this.normalizeTitle(this.childNodes[isTemplate ? 0 : 1].text(), isTemplate ? 10 : 828, { temporary: true });
/* NOT FOR BROWSER */
title.fragment = undefined;
/* NOT FOR BROWSER END */
return title;
}
/** @private */
afterBuild() {
if (this.modifier.includes('\0')) {
this.setAttribute('modifier', this.buildFromStr(this.modifier, constants_1.BuildMethod.String));
}
super.afterBuild();
if (this.isTemplate()) {
const isTemplate = this.type === 'template';
if (isTemplate) {
this.#title = this.#getTitle();
this.setAttribute('name', this.#title.title);
}
/* NOT FOR BROWSER */
/**
* 当事件bubble到`parameter`时,将`oldKey`和`newKey`保存进AstEventData。
* 当继续bubble到`template`时,处理并删除`oldKey`和`newKey`。
* @implements
*/
const transcludeListener = (e, data) => {
const { prevTarget } = e, { oldKey, newKey } = data;
if (typeof oldKey === 'string') {
delete data.oldKey;
delete data.newKey;
}
if (prevTarget === this.firstChild && isTemplate) {
this.#title = this.#getTitle();
this.setAttribute('name', this.#title.title);
}
else if (oldKey !== newKey && prevTarget instanceof parameter_1.ParameterToken) {
const oldArgs = this.getArgs(oldKey, false, false);
oldArgs.delete(prevTarget);
this.getArgs(newKey, false, false).add(prevTarget);
this.#keys.add(newKey);
if (oldArgs.size === 0) {
this.#keys.delete(oldKey);
}
}
};
this.addEventListener(['remove', 'insert', 'replace', 'text'], transcludeListener);
}
}
/** @private */
toString(skip) {
const { childNodes, length, firstChild, modifier, type } = this;
return `{{${modifier}${type === 'magic-word'
? firstChild.toString(skip)
+ (length === 1 ? '' : this.#colon)
+ childNodes.slice(1).map(child => child.toString(skip)).join('|')
: super.toString(skip, '|')}}}`;
}
/** @private */
text() {
const { childNodes, length, firstChild, modifier, type, name } = this;
return type === 'magic-word' && name === 'vardefine'
? ''
: `{{${modifier}${type === 'magic-word'
? firstChild.text()
+ (length === 1 ? '' : this.#colon)
+ (0, string_1.text)(childNodes.slice(1), '|')
: super.text('|')}}}`;
}
/** @private */
getAttribute(key) {
switch (key) {
case 'padding':
return this.modifier.length + 2;
case 'title':
return this.#title;
case 'colon':
return this.#colon;
/* PRINT ONLY */
case 'invalid':
return (this.type === 'magic-word' && this.name === 'invoke'
&& (this.length === 2 || !this.#getTitle().valid));
/* PRINT ONLY END */
/* NOT FOR BROWSER */
case 'keys':
return this.#keys;
/* NOT FOR BROWSER END */
default:
return super.getAttribute(key);
}
}
/** @private */
lint(start = this.getAbsoluteIndex(), re) {
LINT: { // eslint-disable-line no-unused-labels
const errors = super.lint(start, re);
if (!this.isTemplate()) {
return errors;
}
const { type, childNodes, length } = this, rect = new rect_1.BoundingRect(this, start), { lintConfig } = index_1.default, { computeEditInfo } = lintConfig, invoke = type === 'magic-word';
let rule = 'no-ignored', s = lintConfig.getSeverity(rule, 'fragment');
if (invoke && !this.#getTitle().valid) {
rule = 'invalid-invoke';
s = lintConfig.getSeverity(rule, 'name');
if (s) {
errors.push((0, lint_1.generateForChild)(childNodes[1], rect, rule, 'illegal-module', s));
}
}
else if (s) {
const child = childNodes[invoke ? 1 : 0], i = child.childNodes
.findIndex(c => c.type === 'text' && (0, string_1.decodeHtml)(c.data).includes('#')), textNode = child.childNodes[i];
if (textNode) {
const e = (0, lint_1.generateForChild)(child, rect, rule, 'useless-fragment', s);
if (computeEditInfo) {
e.suggestions = [
(0, lint_1.fixByRemove)(e, child.getRelativeIndex(i) + textNode.data.indexOf('#')),
];
}
errors.push(e);
}
}
rule = 'invalid-invoke';
s = lintConfig.getSeverity(rule, 'function');
if (s && invoke && length === 2) {
errors.push((0, lint_1.generateForSelf)(this, rect, rule, 'missing-function', s));
return errors;
}
rule = 'no-duplicate';
s = lintConfig.getSeverity(rule, 'parameter');
if (s) {
const duplicatedArgs = this.getDuplicatedArgs()
.filter(([, parameter]) => !parameter[0].querySelector('ext')), msg = 'duplicate-parameter';
for (const [, args] of duplicatedArgs) {
errors.push(...args.map(arg => {
const e = (0, lint_1.generateForChild)(arg, rect, rule, msg, s);
if (computeEditInfo) {
e.suggestions = [(0, lint_1.fixByRemove)(e, -1)];
}
return e;
}));
}
}
return errors;
}
}
/**
* 处理匿名参数更改
* @param addedToken 新增的参数
*/
#handleAnonArgChange(addedToken) {
const args = this.getAnonArgs(), added = typeof addedToken !== 'number';
/* NOT FOR BROWSER */
const maxAnon = String(args.length + (added ? 0 : 1));
if (added) {
this.#keys.add(maxAnon);
}
else if (!this.hasArg(maxAnon, true)) {
this.#keys.delete(maxAnon);
}
/* NOT FOR BROWSER END */
for (let i = added ? args.indexOf(addedToken) : addedToken - 1; i < args.length; i++) {
const token = args[i], { name } = token, newName = String(i + 1);
if (name !== newName || token === addedToken) {
token.setAttribute('name', newName);
this.getArgs(newName, false, false).add(token);
/* NOT FOR BROWSER */
if (name && token !== addedToken) {
this.getArgs(name, false, false).delete(token);
}
}
}
}
/**
* @override
* @param token node to be inserted / 待插入的子节点
* @param i position to be inserted at / 插入位置
*/
insertAt(token, i = this.length) {
super.insertAt(token, i);
if (token.anon) {
this.#handleAnonArgChange(token);
}
else if (token.name) {
this.getArgs(token.name, false, false).add(token);
/* NOT FOR BROWSER */
this.#keys.add(token.name);
}
return token;
}
/**
* Get all parameters
*
* 获取所有参数
*/
getAllArgs() {
return this.childNodes.filter((0, debug_1.isToken)('parameter'));
}
/**
* Get all anonymous parameters
*
* 获取所有匿名参数
*/
getAnonArgs() {
return this.getAllArgs().filter(({ anon }) => anon);
}
/**
* Get parameters with the specified name
*
* 获取指定参数
* @param key parameter name / 参数名
* @param exact whether to match anonymosity / 是否匹配匿名性
* @param copy whether to return a copy / 是否返回一个备份
*/
getArgs(key, exact, copy = true) {
const keyStr = String(key)
.replace(/^[ \t\n\0\v]+|([^ \t\n\0\v])[ \t\n\0\v]+$/gu, '$1');
let args;
if (this.#args.has(keyStr)) {
args = this.#args.get(keyStr);
}
else {
args = new Set(this.getAllArgs().filter(({ name }) => keyStr === name));
this.#args.set(keyStr, args);
}
/* NOT FOR BROWSER */
if (exact && keyStr.trim() && Number.isInteger(Number(keyStr))) {
args = new Set([...args].filter(({ anon }) => typeof key === 'number' === anon));
}
else if (copy) {
args = new Set(args);
}
/* NOT FOR BROWSER END */
return args;
}
/**
* Get duplicated parameters
*
* 获取重名参数
* @throws `Error` 仅用于模板
*/
getDuplicatedArgs() {
if (this.isTemplate()) {
return [...this.#args].filter(([, { size }]) => size > 1).map(([key, args]) => [key, [...args]]);
}
throw new Error('TranscludeToken.getDuplicatedArgs method is only for template!');
}
/**
* Get possible values of some magic words
*
* 对特定魔术字获取可能的取值
* @throws `Error` 不是可接受的魔术字
*/
getPossibleValues() {
const { type, name, childNodes } = this;
if (type === 'template') {
throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
}
let start, queue;
switch (name) {
case 'if':
case 'ifexist':
case 'ifexpr':
case 'iferror':
start = 2;
break;
case 'ifeq':
start = 3;
break;
case 'switch': {
const parameters = childNodes.slice(2), last = parameters[parameters.length - 1];
queue = [
...parameters.filter(({ anon }) => !anon),
...last?.anon ? [last] : [],
].map(({ lastChild }) => lastChild);
break;
}
default:
throw new Error('TranscludeToken.getPossibleValues method is only for specific magic words!');
}
queue ??= childNodes.slice(start, start + 2).map(({ lastChild }) => lastChild);
for (let i = 0; i < queue.length;) {
const { length, 0: first } = queue[i].childNodes.filter(child => child.text().trim());
if (length === 0) {
queue.splice(i, 1);
}
else if (length > 1 || first.type !== 'magic-word') {
i++;
}
else {
try {
const possibleValues = first.getPossibleValues();
queue.splice(i, 1, ...possibleValues);
i += possibleValues.length;
}
catch {
i++;
}
}
}
return queue;
}
/** @private */
print() {
const { childNodes, length, firstChild, modifier, type } = this;
return `<span class="wpb-${type}${this.getAttribute('invalid') ? ' wpb-invalid' : ''}">{{${type === 'magic-word'
? (0, string_1.escape)(modifier)
+ firstChild.print()
+ (length === 1 ? '' : this.#colon)
+ (0, string_1.print)(childNodes.slice(1), { sep: '|' })
: (modifier ? `<span class="wpb-magic-word">${(0, string_1.escape)(modifier)}</span>` : '')
+ (0, string_1.print)(childNodes, { sep: '|' })}}}</span>`;
}
/* NOT FOR BROWSER */
cloneNode() {
const [first, ...cloned] = this.cloneChildNodes(), config = this.getAttribute('config');
return debug_1.Shadow.run(() => {
// @ts-expect-error abstract class
const token = new TranscludeToken(this.type === 'template' ? 'T' : first.text() + (cloned.length === 0 ? '' : this.#colon), [], config);
if (this.#raw) {
token.setModifier(this.modifier);
}
else {
token.setAttribute('modifier', this.modifier);
}
token.firstChild.safeReplaceWith(first);
if (token.length > 1) {
token.removeAt(1);
}
token.safeAppend(cloned);
return token;
});
}
/**
* Convert to substitution
*
* 替换引用
*/
subst() {
this.setModifier('subst:');
}
/**
* Convert to safe substitution
*
* 安全的替换引用
*/
safesubst() {
this.setModifier('safesubst:');
}
/**
* @override
* @param i position of the child node / 移除位置
*/
removeAt(i) {
const token = super.removeAt(i);
if (token.anon) {
this.#handleAnonArgChange(Number(token.name));
}
else {
const args = this.getArgs(token.name, false, false);
args.delete(token);
if (args.size === 0) {
this.#keys.delete(token.name);
}
}
return token;
}
/**
* Check if there is a parameter with the specified name
*
* 是否具有某参数
* @param key parameter name / 参数名
* @param exact whether to match anonymosity / 是否匹配匿名性
*/
hasArg(key, exact) {
return this.getArgs(key, exact, false).size > 0;
}
/**
* Get the effective parameter with the specified name
*
* 获取生效的指定参数
* @param key parameter name / 参数名
* @param exact whether to match anonymosity / 是否匹配匿名性
*/
getArg(key, exact) {
return [...this.getArgs(key, exact, false)].sort((a, b) => b.compareDocumentPosition(a))[0];
}
/**
* Remove parameters with the specified name
*
* 移除指定参数
* @param key parameter name / 参数名
* @param exact whether to match anonymosity / 是否匹配匿名性
*/
removeArg(key, exact) {
debug_1.Shadow.run(() => {
for (const token of this.getArgs(key, exact, false)) {
this.removeChild(token);
}
});
}
/**
* Get all parameter names
*
* 获取所有参数名
*/
getKeys() {
const args = this.getAllArgs();
if (this.#keys.size === 0 && args.length > 0) {
for (const { name } of args) {
this.#keys.add(name);
}
}
return [...this.#keys];
}
/**
* Get parameter values
*
* 获取参数值
* @param key parameter name / 参数名
*/
getValues(key) {
return [...this.getArgs(key, false, false)].map(token => token.getValue());
}
getValue(key) {
return key === undefined
? Object.fromEntries(this.getKeys().map(k => [k, this.getValue(k)]))
: this.getArg(key)?.getValue();
}
/**
* Insert an anonymous parameter
*
* 插入匿名参数
* @param val parameter value / 参数值
*/
newAnonArg(val) {
require('../addon/transclude');
return this.newAnonArg(val);
}
/**
* Set the parameter value
*
* 设置参数值
* @param key parameter name / 参数名
* @param value parameter value / 参数值
* @throws `Error` 仅用于模板
*/
setValue(key, value) {
require('../addon/transclude');
this.setValue(key, value);
}
/**
* Convert all anonymous parameters to named ones
*
* 将匿名参数改写为命名参数
* @throws `Error` 仅用于模板
*/
anonToNamed() {
if (!this.isTemplate()) {
throw new Error('TranscludeToken.anonToNamed method is only for template!');
}
for (const token of this.getAnonArgs()) {
token.firstChild.replaceChildren(token.name);
}
}
/**
* Replace the template name
*
* 替换模板名
* @param title template name / 模板名
* @throws `Error` 仅用于模板
*/
replaceTemplate(title) {
require('../addon/transclude');
this.replaceTemplate(title);
}
/**
* Replace the module name
*
* 替换模块名
* @param title module name / 模块名
* @throws `Error` 仅用于模块
*/
replaceModule(title) {
require('../addon/transclude');
this.replaceModule(title);
}
/**
* Replace the module function
*
* 替换模块函数
* @param func module function name / 模块函数名
* @throws `Error` 仅用于模块
* @throws `Error` 尚未指定模块名称
*/
replaceFunction(func) {
require('../addon/transclude');
this.replaceFunction(func);
}
/**
* Count duplicated parameters
*
* 重复参数计数
* @throws `Error` 仅用于模板
*/
hasDuplicatedArgs() {
if (this.isTemplate()) {
return this.getAllArgs().length - this.getKeys().length;
}
throw new Error('TranscludeToken.hasDuplicatedArgs method is only for template!');
}
/**
* Fix duplicated parameters
* @description
* - Only empty parameters and identical parameters are removed with `aggressive = false`.
* Anonymous parameters have a higher precedence, otherwise all anonymous parameters are converted to named ones.
* - Additionally, consecutive numbered parameters are treated with `aggressive = true`.
*
* 修复重名参数
* @description
* - `aggressive = false`时只移除空参数和全同参数,优先保留匿名参数,否则将所有匿名参数更改为命名。
* - `aggressive = true`时还会尝试处理连续的以数字编号的参数。
* @param aggressive whether to use a more risky approach / 是否使用有更大风险的修复手段
*/
fixDuplication(aggressive) {
require('../addon/transclude');
return this.fixDuplication(aggressive);
}
/**
* Escape tables inside the template
*
* 转义模板内的表格
* @throws `Error` 转义失败
*/
escapeTables() {
require('../addon/transclude');
return this.escapeTables();
}
/**
* Get the module name and module function name
*
* 获取模块名和模块函数名
* @throws `Error` 仅用于模块
* @since v1.16.4
*/
getModule() {
/* istanbul ignore if */
if (this.type !== 'magic-word' || this.name !== 'invoke') {
throw new Error('TranscludeToken.getModule method is only for modules!');
}
return [this.module, this.function];
}
/**
* Get the [frame object](https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#frame-object)
*
* 获取 [frame 对象](https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#frame-object)
* @param context template calling this module / 调用该模块的模板
* @throws `Error` 仅用于模块
* @since v1.22.0
*/
getFrame(context) {
/* istanbul ignore if */
if (this.type === 'magic-word' && this.name !== 'invoke' || this.type === 'template' && context) {
throw new Error('TranscludeToken.getFrame method is only for modules!');
}
return {
args: this.getValue(),
parent: context?.getFrame(),
title: this.#getTitle().toString(true),
};
}
/** @private */
toHtmlInternal(opt) {
const { type, name } = this;
if (type === 'template' && !name.startsWith('Special:')) {
if (this.normalizeTitle(name, 0, { halfParsed: true, temporary: true }).valid) {
const title = name.replaceAll('_', ' ');
return `<a href="${this.#title.getUrl()}?action=edit&redlink=1" class="new" title="${title} (page does not exist)">${title}</a>`;
}
const str = this.text();
return opt?.nowrap ? str.replaceAll('\n', ' ') : str;
}
return basicMagicWords.has(name) ? basicMagicWords.get(name) : '';
}
};
return TranscludeToken = _classThis;
})();
exports.TranscludeToken = TranscludeToken;
constants_1.classes['TranscludeToken'] = __filename;