wikiparser-node
Version:
A Node.js parser for MediaWiki markup with AST
506 lines (505 loc) • 22.1 kB
JavaScript
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.AttributesToken = void 0;
const lint_1 = require("../util/lint");
const string_1 = require("../util/string");
const rect_1 = require("../lib/rect");
const index_1 = __importDefault(require("../index"));
const index_2 = require("./index");
const atom_1 = require("./atom");
const attribute_1 = require("./attribute");
/* NOT FOR BROWSER */
const html_1 = require("../util/html");
const debug_1 = require("../util/debug");
const constants_1 = require("../util/constants");
const clone_1 = require("../mixin/clone");
const cached_1 = require("../mixin/cached");
const stages = { 'ext-attrs': 0, 'html-attrs': 2, 'table-attrs': 3 };
/**
* 将属性类型转换为单属性类型
* @param type 属性类型
*/
const toAttributeType = (type) => type.slice(0, -1);
/**
* 将属性类型转换为无效属性类型
* @param type 属性类型
*/
const toDirty = (type) => `${toAttributeType(type)}-dirty`;
const wordRegex = /* #__PURE__ */ (() => {
try {
// eslint-disable-next-line prefer-regex-literals
return new RegExp(String.raw `[\p{L}\p{N}]`, 'u');
}
catch /* istanbul ignore next */ {
return /[^\W_]/u;
}
})();
/**
* attributes of extension and HTML tags
*
* 扩展和HTML标签属性
* @classdesc `{childNodes: (AtomToken|AttributeToken)[]}`
*/
let AttributesToken = (() => {
let _classSuper = index_2.Token;
let _instanceExtraInitializers = [];
let _cloneNode_decorators;
let _toHtmlInternal_decorators;
return class AttributesToken extends _classSuper {
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_cloneNode_decorators = [clone_1.clone];
_toHtmlInternal_decorators = [(0, cached_1.cached)()];
__esDecorate(this, null, _cloneNode_decorators, { kind: "method", name: "cloneNode", static: false, private: false, access: { has: obj => "cloneNode" in obj, get: obj => obj.cloneNode }, metadata: _metadata }, null, _instanceExtraInitializers);
__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);
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
#type = __runInitializers(this, _instanceExtraInitializers);
#classList;
/* NOT FOR BROWSER END */
get type() {
return this.#type;
}
/* NOT FOR BROWSER */
/** all attributes / 全部属性 */
get attributes() {
return this.getAttrs();
}
set attributes(attrs) {
this.replaceChildren();
this.setAttr(attrs);
}
/** class attribute in string / 以字符串表示的class属性 */
get className() {
const attr = this.getAttr('class');
return typeof attr === 'string' ? attr : '';
}
set className(className) {
this.setAttr('class', className || false);
}
/** class attribute in Set / 以Set表示的class属性 */
get classList() {
if (!this.#classList) {
this.#classList = new Set(this.className.split(/\s/u));
/**
* 更新classList
* @param prop 方法名
*/
const factory = (prop) => ({
value: /** @ignore */ (...args) => {
const result = Set.prototype[prop].apply(this.#classList, args);
this.setAttr('class', [...this.#classList].join(' '));
return result;
},
});
Object.defineProperties(this.#classList, {
add: factory('add'),
delete: factory('delete'),
clear: factory('clear'),
});
}
return this.#classList;
}
/** id attribute / id属性 */
get id() {
const attr = this.getAttr('id');
return typeof attr === 'string' ? attr : '';
}
set id(id) {
this.setAttr('id', id || false);
}
/** whether to contain invalid attributes / 是否含有无效属性 */
get sanitized() {
return this.childNodes.filter(child => child instanceof atom_1.AtomToken && child.text().trim()).length === 0;
}
set sanitized(sanitized) {
if (sanitized) {
this.sanitize();
}
}
/* NOT FOR BROWSER END */
/**
* @param attr 标签属性
* @param type 标签类型
* @param name 标签名
*/
constructor(attr, type, name, config, accum = []) {
super(undefined, config, accum, {
AtomToken: ':', AttributeToken: ':',
});
this.#type = type;
this.setAttribute('name', name);
if (attr) {
const regex = /([^\s/](?:(?!\0\d+~\x7F)[^\s/=])*)(?:((?:\s(?:\s|\0\d+[cn]\x7F)*)?(?:=|\0\d+~\x7F)(?:\s|\0\d+[cn]\x7F)*)(?:(["'])([\s\S]*?)(\3|$)|(\S*)))?/gu;
let out = '', mt = regex.exec(attr), lastIndex = 0;
const insertDirty = /** 插入无效属性 */ () => {
if (out) {
super.insertAt(new atom_1.AtomToken(out, toDirty(type), config, accum, {
[`Stage-${stages[type]}`]: ':',
}));
out = '';
}
};
while (mt) {
const { index, 0: full, 1: key, 2: equal, 3: quoteStart, 4: quoted, 5: quoteEnd, 6: unquoted } = mt;
out += attr.slice(lastIndex, index);
if (/^(?:[\w:]|\0\d+t\x7F)(?:[\w:.-]|\0\d+t\x7F)*$/u.test((0, string_1.removeComment)(key).trim())) {
const value = quoted ?? unquoted, quotes = [quoteStart, quoteEnd],
// @ts-expect-error abstract class
token = new attribute_1.AttributeToken(toAttributeType(type), name, key, equal, value, quotes, config, accum);
insertDirty();
super.insertAt(token);
}
else {
out += full;
}
({ lastIndex } = regex);
mt = regex.exec(attr);
}
out += attr.slice(lastIndex);
insertDirty();
}
}
/** @private */
afterBuild() {
const { parentNode } = this;
if (parentNode?.is('td')) {
this.setAttribute('name', parentNode.subtype);
}
super.afterBuild();
}
/**
* Get all AttributeTokens with the specified attribute name
*
* 所有指定属性名的AttributeToken
* @param key attribute name / 属性名
*/
getAttrTokens(key) {
return this.childNodes.filter((child) => child instanceof attribute_1.AttributeToken && (!key || child.name === (0, string_1.trimLc)(key)));
}
/**
* Check if the token has a certain attribute
*
* 是否具有某属性
* @param key attribute name / 属性键
*/
hasAttr(key) {
return this.getAttrTokens(key).length > 0;
}
/**
* Get the last AttributeToken with the specified attribute name
*
* 指定属性名的最后一个AttributeToken
* @param key attribute name / 属性名
*/
getAttrToken(key) {
const tokens = this.getAttrTokens(key);
return tokens[tokens.length - 1];
}
/**
* Get the attribute
*
* 获取指定属性
* @param key attribute name / 属性键
*/
getAttr(key) {
return this.getAttrToken(key)?.getValue();
}
/** 是否位于闭合标签内 */
#lint() {
const { parentNode } = this;
return parentNode?.type === 'html' && parentNode.closing && this.text().trim() !== '';
}
/** @private */
lint(start = this.getAbsoluteIndex(), re) {
LINT: { // eslint-disable-line no-unused-labels
const errors = super.lint(start, re), { parentNode, childNodes } = this, attrs = new Map(), duplicated = new Set(), rect = new rect_1.BoundingRect(this, start), rules = ['no-ignored', 'no-duplicate'], { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, s = ['closingTag', 'invalidAttributes', 'nonWordAttributes']
.map(k => lintConfig.getSeverity(rules[0], k));
if (s[0] && this.#lint()) {
const e = (0, lint_1.generateForSelf)(this, rect, rules[0], 'attributes-of-closing-tag', s[0]);
if (computeEditInfo) {
const index = parentNode.getAbsoluteIndex();
e.suggestions = [
(0, lint_1.fixByRemove)(e),
(0, lint_1.fixByOpen)(index),
];
}
errors.push(e);
}
for (const attr of childNodes) {
if (attr instanceof attribute_1.AttributeToken) {
const { name } = attr;
if (attrs.has(name)) {
duplicated.add(name);
attrs.get(name).push(attr);
}
else {
attrs.set(name, [attr]);
}
}
else {
const str = attr.text().trim(), severity = s[wordRegex.test(str) ? 1 : 2];
if (str && severity) {
const e = (0, lint_1.generateForChild)(attr, rect, rules[0], 'invalid-attribute', severity);
if (computeEditInfo) {
e.suggestions = [(0, lint_1.fixByRemove)(e, 0, ' ')];
}
errors.push(e);
}
}
}
const severity = lintConfig.getSeverity(rules[1], 'attribute');
if (severity && duplicated.size > 0) {
for (const key of duplicated) {
const pairs = attrs.get(key).map(attr => {
const value = attr.getValue();
return [attr, value === true ? '' : value];
});
errors.push(...pairs.map(([attr, value], i) => {
const e = (0, lint_1.generateForChild)(attr, rect, rules[1], index_1.default.msg('duplicate-attribute', key), severity);
if (computeEditInfo || fix) {
const remove = (0, lint_1.fixByRemove)(e);
if (!value || pairs.slice(0, i).some(([, v]) => v === value)) {
e.fix = remove;
}
else if (computeEditInfo) {
e.suggestions = [remove];
}
}
return e;
}));
}
}
return errors;
}
}
/* PRINT ONLY */
/** @private */
getAttribute(key) {
/* NOT FOR BROWSER */
if (key === 'padding') {
return this.#leadingSpace(super.toString()).length;
}
/* NOT FOR BROWSER END */
return key === 'invalid' ? this.#lint() : super.getAttribute(key);
}
/** @private */
print() {
return this.toString()
? `<span class="wpb-${this.type}${this.#lint() ? ' wpb-invalid' : ''}">${this.childNodes.map(child => child.print(child instanceof atom_1.AtomToken ? { class: child.toString().trim() && 'attr-dirty' } : undefined)).join('')}</span>`
: '';
}
/* PRINT ONLY END */
/* NOT FOR BROWSER */
/**
* Sanitize invalid attributes
*
* 清理无效属性
*/
sanitize() {
let dirty = false;
for (let i = this.length - 1; i >= 0; i--) {
const child = this.childNodes[i];
if (child instanceof atom_1.AtomToken && child.text().trim()) {
dirty = true;
this.removeAt(i);
}
}
if (!debug_1.Shadow.running && dirty) {
index_1.default.warn('AttributesToken.sanitize will remove invalid attributes!');
}
}
cloneNode() {
// @ts-expect-error abstract class
return new AttributesToken(undefined, this.type, this.name, this.getAttribute('config'));
}
/**
* @override
* @param token node to be inserted / 待插入的子节点
* @param i position to be inserted at / 插入位置
* @throws `RangeError` 标签不匹配
*/
insertAt(token, i = this.length) {
if (!(token instanceof attribute_1.AttributeToken)) {
if (!debug_1.Shadow.running && token.toString().trim()) {
this.constructorError('can only insert AttributeToken');
}
return super.insertAt(token, i);
}
const { type, name, length } = this;
if (token.type !== type.slice(0, -1) || token.tag !== name) {
throw new RangeError(`The AttributeToken to be inserted can only be used for <${token.tag}> tag!`);
}
else if (i === length) {
const { lastChild } = this;
if (lastChild instanceof attribute_1.AttributeToken) {
lastChild.close();
}
}
else {
token.close();
}
if (this.closest('parameter')) {
token.escape();
}
super.insertAt(token, i);
const { previousVisibleSibling, nextVisibleSibling } = token, dirtyType = toDirty(type), config = this.getAttribute('config'), acceptable = { [`Stage-${stages[type]}`]: ':' };
if (nextVisibleSibling && !/^\s/u.test(nextVisibleSibling.toString())) {
super.insertAt(debug_1.Shadow.run(() => new atom_1.AtomToken(' ', dirtyType, config, [], acceptable)), i + 1);
}
if (previousVisibleSibling && !/\s$/u.test(previousVisibleSibling.toString())) {
super.insertAt(debug_1.Shadow.run(() => new atom_1.AtomToken(' ', dirtyType, config, [], acceptable)), i);
}
return token;
}
setAttr(keyOrProp, value) {
if (typeof keyOrProp === 'object') {
for (const [key, val] of Object.entries(keyOrProp)) {
this.setAttr(key, val);
}
return;
}
const { type, name } = this;
if (type === 'ext-attrs' && typeof value === 'string' && value.includes('>')) {
throw new RangeError('Attributes of an extension tag cannot contain ">"!');
}
const key = (0, string_1.trimLc)(keyOrProp), attr = this.getAttrToken(key);
if (attr) {
attr.setValue(value);
return;
}
else if (value === false) {
return;
}
// @ts-expect-error abstract class
const token = debug_1.Shadow.run(() => new attribute_1.AttributeToken(toAttributeType(type), name, key, value === true ? '' : '=', value === true ? '' : value, ['"', '"'], this.getAttribute('config')));
this.insertAt(token);
}
/**
* Get all attribute names
*
* 获取全部的属性名
*/
getAttrNames() {
return new Set(this.getAttrTokens().map(({ name }) => name));
}
/**
* Get all attributes
*
* 获取全部属性
*/
getAttrs() {
return Object.fromEntries(this.getAttrTokens().map(({ name, value }) => [name, value]));
}
/**
* Remove an attribute
*
* 移除指定属性
* @param key attribute name / 属性键
*/
removeAttr(key) {
for (const attr of this.getAttrTokens(key)) {
attr.remove();
}
}
/**
* Toggle the specified attribute
*
* 开关指定属性
* @param key attribute name / 属性键
* @param force whether to force enabling or disabling / 强制开启或关闭
* @throws `RangeError` 不为Boolean类型的属性值
*/
toggleAttr(key, force) {
key = (0, string_1.trimLc)(key);
const attr = this.getAttrToken(key);
if (attr && attr.getValue() !== true) {
throw new RangeError(`${key} attribute is not Boolean!`);
}
else if (attr) {
attr.setValue(force === true);
}
else if (force !== false) {
this.setAttr(key, true);
}
}
/**
* 生成引导空格
* @param str 属性字符串
*/
#leadingSpace(str) {
const { type } = this, leadingRegex = { 'ext-attrs': /^\s/u, 'html-attrs': /^[/\s]/u };
return str && type !== 'table-attrs' && !leadingRegex[type].test(str) ? ' ' : '';
}
/** @private */
toString(skip) {
if (this.type === 'table-attrs') {
(0, string_1.normalizeSpace)(this);
}
const str = super.toString(skip);
return this.#leadingSpace(str) + str;
}
/** @private */
text() {
if (this.type === 'table-attrs') {
(0, string_1.normalizeSpace)(this);
}
const str = (0, string_1.text)(this.childNodes.filter(child => child instanceof attribute_1.AttributeToken), ' ');
return this.#leadingSpace(str) + str;
}
/** @private */
toHtmlInternal() {
const map = new Map(this.childNodes.filter(child => child instanceof attribute_1.AttributeToken).map(child => [child.name, child]));
return map.size === 0 ? '' : ` ${(0, html_1.html)([...map.values()], ' ')}`;
}
/**
* Get the value of a style property
*
* 获取某一样式属性的值
* @param key style property / 样式属性
* @param value style property value / 样式属性值
*/
css(key, value) {
return this.getAttrToken('style')?.css(key, value);
}
};
})();
exports.AttributesToken = AttributesToken;
constants_1.classes['AttributesToken'] = __filename;
;