UNPKG

wikiparser-node

Version:

A Node.js parser for MediaWiki markup with AST

310 lines (309 loc) 11.7 kB
"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;