wikiparser-node
Version:
A Node.js parser for MediaWiki markup with AST
310 lines (309 loc) • 11.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ImageParameterToken = exports.galleryParams = void 0;
const common_1 = require("@bhsd/common");
const string_1 = require("../util/string");
const lint_1 = require("../util/lint");
const constants_1 = require("../util/constants");
const index_1 = __importDefault(require("../index"));
const index_2 = require("./index");
/* NOT FOR BROWSER */
const debug_1 = require("../util/debug");
/* NOT FOR BROWSER END */
/^(?:ftp:\/\/|\/\/|\0\d+m\x7F)/iu; // eslint-disable-line @typescript-eslint/no-unused-expressions
const getUrlLikeRegex = (0, common_1.getRegex)(protocol => new RegExp(String.raw `^(?:${protocol}|//|\0\d+m\x7F)`, 'iu'));
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
/^(?:(?:ftp:\/\/|\/\/)(?:\[[\da-f:.]+\]|[^[\]<>"\t\n\p{Zs}])|\0\d+m\x7F)[^[\]<>"\0\t\n\p{Zs}]*$/iu;
const getUrlRegex = (0, common_1.getRegex)(protocol => new RegExp(String.raw `^(?:(?:${protocol}|//)${string_1.extUrlCharFirst}|\0\d+m\x7F)${string_1.extUrlChar}$`, 'iu'));
/* eslint-disable @typescript-eslint/no-unused-expressions */
/^(\s*)link=(.*)(?=$|\n)(\s*)$/u;
/^(\s*(?!\s))(.*)px(\s*)$/u;
/* eslint-enable @typescript-eslint/no-unused-expressions */
const getSyntaxRegex = (0, common_1.getRegex)(syntax => new RegExp(String.raw `^(\s*(?!\s))${syntax.replace('$1', '(.*)')}${syntax.endsWith('$1') ? '(?=$|\n)' : ''}(\s*)$`, 'u'));
exports.galleryParams = new Set(['alt', 'link', 'lang', 'page', 'caption']);
function validate(key, val, config, halfParsed, ext) {
val = (0, string_1.removeComment)(val).trim();
let value = val.replace(key === 'link' ? /\0\d+[tq]\x7F/gu : /\0\d+t\x7F/gu, '').trim();
switch (key) {
case 'width':
return !value && Boolean(val) || /^(?:\d+x?|\d*x\d+)(?:\s*px)?$/u.test(value);
case 'link': {
if (!value) {
return val;
}
else if (getUrlLikeRegex(config.protocol).test(value)) {
return getUrlRegex(config.protocol).test(value) && val;
}
else if (value.startsWith('[[') && value.endsWith(']]')) {
value = value.slice(2, -2);
}
const title = index_1.default.normalizeTitle(value, 0, false, config, { halfParsed, decode: true, selfLink: true });
return title.valid && title;
}
case 'lang':
return (ext === 'svg' || ext === 'svgz') && !/[^a-z\d-]/u.test(value);
case 'alt':
case 'class':
case 'manualthumb':
return true;
case 'page':
return (ext === 'djvu' || ext === 'djv' || ext === 'pdf') && Number(value) > 0;
default:
return Boolean(value) && !isNaN(value);
}
}
/* eslint-enable jsdoc/check-param-names */
/**
* image parameter
*
* 图片参数
*/
class ImageParameterToken extends index_2.Token {
#syntax = '';
#extension;
/* NOT FOR BROWSER END */
get type() {
return 'image-parameter';
}
/** image link / 图片链接 */
get link() {
return this.name === 'link' ? validate('link', super.text(), this.getAttribute('config')) : undefined;
}
/* NOT FOR BROWSER */
set link(value) {
if (this.name === 'link') {
this.setValue(value);
}
}
/** parameter value / 参数值 */
get value() {
return this.getValue();
}
set value(value) {
this.setValue(value);
}
/** iamge size / 图片大小 */
get size() {
if (this.name === 'width') {
const size = this.getValue().trim().replace(/px$/u, '').trim();
if (!size.includes('{{')) {
const [width, height = ''] = size.split('x');
return { width, height };
}
const token = index_1.default.parse(size, false, 2, this.getAttribute('config')), i = token.childNodes.findIndex(({ type, data }) => type === 'text' && data.includes('x'));
if (i === -1) {
return { width: size, height: '' };
}
const str = token.childNodes[i];
str.splitText(str.data.indexOf('x')).splitText(1);
return { width: (0, string_1.text)(token.childNodes.slice(0, i + 1)), height: (0, string_1.text)(token.childNodes.slice(i + 2)) };
}
return undefined;
}
set size(size) {
if (this.name === 'width') {
this.setValue(size && size.width + (size.height && 'x') + size.height);
}
}
/** image width / 图片宽度 */
get width() {
return this.size?.width;
}
set width(width) {
if (this.name === 'width') {
const { height } = this;
this.setValue((width || '') + (height && 'x') + height);
}
}
/** image height / 图片高度 */
get height() {
return this.size?.height;
}
set height(height) {
if (this.name === 'width') {
this.setValue(this.width + (height ? `x${height}` : ''));
}
}
/* NOT FOR BROWSER END */
/** @param str 图片参数 */
constructor(str, extension, config, accum) {
let mt;
const regexes = Object.entries(config.img)
.map(([syntax, param]) => [syntax, param, getSyntaxRegex(syntax)]), param = regexes.find(([, key, regex]) => {
mt = regex.exec(str);
return mt
&& (mt.length !== 4
|| validate(key, mt[2], config, true, extension) !== false);
});
// @ts-expect-error mt already assigned
if (param && mt) {
if (mt.length === 3) {
super(undefined, config, accum);
this.#syntax = str;
}
else {
super(mt[2], config, accum, {
'Stage-2': ':', '!HeadingToken': ':',
});
this.#syntax = mt[1] + param[0] + mt[3];
}
this.setAttribute('name', param[1]);
if (param[1] === 'alt') {
this.setAttribute('stage', constants_1.MAX_STAGE - 1);
}
return;
}
super(str, config.excludes.includes('list')
? config
: {
...config,
excludes: [...config.excludes, 'list'],
}, accum);
this.setAttribute('name', 'caption');
this.setAttribute('stage', 7);
/* NOT FOR BROWSER */
this.#extension = extension;
}
/** @private */
afterBuild() {
if (this.parentNode?.is('gallery-image') && !exports.galleryParams.has(this.name)) {
this.setAttribute('name', 'invalid');
}
super.afterBuild();
}
/** @private */
toString(skip) {
return this.#syntax ? this.#syntax.replace('$1', super.toString(skip)) : super.toString(skip);
}
/** @private */
text() {
return this.#syntax ? this.#syntax.replace('$1', super.text()).trim() : super.text().trim();
}
/** @private */
isPlain() {
return this.name === 'caption' || this.name === 'alt';
}
/** @private */
getAttribute(key) {
/* PRINT ONLY */
if (key === 'invalid') {
return (this.name === 'invalid');
}
/** PRINT ONLY END */
return key === 'padding'
? Math.max(0, this.#syntax.indexOf('$1'))
: super.getAttribute(key);
}
/** @private */
lint(start = this.getAbsoluteIndex(), re) {
LINT: { // eslint-disable-line no-unused-labels
const errors = super.lint(start, re), { lintConfig } = index_1.default, { computeEditInfo, fix } = lintConfig, { link, name } = this;
if (name === 'invalid') {
const rule = 'invalid-gallery', s = lintConfig.getSeverity(rule, 'parameter');
if (s) {
const e = (0, lint_1.generateForSelf)(this, { start }, rule, 'invalid-image-parameter', s);
if (computeEditInfo || fix) {
e.fix = (0, lint_1.fixByRemove)(e, -1);
}
errors.push(e);
}
}
else if (typeof link === 'object' && link.encoded) {
const rule = 'url-encoding', s = lintConfig.getSeverity(rule, 'file');
if (s) {
const e = (0, lint_1.generateForSelf)(this, { start }, rule, 'unnecessary-encoding', s);
if (computeEditInfo || fix) {
e.fix = (0, lint_1.fixByDecode)(e, this);
}
errors.push(e);
}
}
return errors;
}
}
/** 是否是不可变参数 */
#isVoid() {
return this.#syntax && !this.#syntax.includes('$1');
}
/**
* Get the parameter value
*
* 获取参数值
*/
getValue() {
return this.name === 'invalid' ? this.text() : this.#isVoid() || super.text();
}
/** @private */
print() {
if (this.#syntax) {
return `<span class="wpb-image-parameter${this.name === 'invalid' ? ' wpb-invalid' : ''}">${this.#syntax.replace('$1', `<span class="wpb-image-caption">${(0, string_1.print)(this.childNodes)}</span>`)}</span>`;
}
return super.print({ class: 'image-caption' });
}
/* NOT FOR BROWSER */
cloneNode() {
const cloned = this.cloneChildNodes(), config = this.getAttribute('config');
return debug_1.Shadow.run(() => {
// @ts-expect-error abstract class
const token = new ImageParameterToken(this.#syntax.replace('$1', '1'), this.#extension, config);
token.safeReplaceChildren(cloned);
return token;
});
}
insertAt(token, i) {
if (!debug_1.Shadow.running && this.#isVoid()) {
throw new Error(`Image parameter ${this.name} does not accept custom input!`);
}
return super.insertAt(token, i);
}
/**
* Set the parameter value
*
* 设置参数值
* @param value parameter value / 参数值
* @throws `Error` 无效参数
*/
setValue(value = false) {
const { name } = this;
if (value === false) {
this.remove();
return;
}
else if (name === 'invalid') {
throw new Error('Invalid image parameter!');
}
const type = this.#isVoid() ? 'Boolean' : 'String';
if (typeof value !== type.toLowerCase()) { // eslint-disable-line valid-typeof
this.typeError('setValue', type);
}
else if (value !== true) {
const include = this.getAttribute('include'), config = this.getAttribute('config'), { childNodes } = index_1.default.parse(value, include, name === 'caption' ? undefined : 5, config);
this.safeReplaceChildren(childNodes);
}
}
/**
* Get the URL
*
* 获取网址
* @param articlePath article path / 条目路径
* @since v1.11.0
*/
getUrl(articlePath) {
let { link } = this;
if (!link) {
return link;
}
else if (typeof link !== 'string') {
return link.getUrl(articlePath);
}
else if (link.startsWith('//')) {
link = `https:${link}`;
}
return new URL(link).href;
}
}
exports.ImageParameterToken = ImageParameterToken;
constants_1.classes['ImageParameterToken'] = __filename;