cejs
Version:
A JavaScript module framework that is simple to use.
1,573 lines (1,413 loc) • 171 kB
JavaScript
/**
* @name CeL function for MediaWiki (Wikipedia / 維基百科): parse wikitext /
* wikicode 解析維基語法
*
* @fileoverview 本檔案包含了 MediaWiki 自動化作業用程式庫的子程式庫。
*
* TODO:<code>
parser [[WP:維基化]] [[w:en:Wikipedia:AutoWikiBrowser/General fixes]] [[w:en:Wikipedia:WikiProject Check Wikipedia]]
https://www.mediawiki.org/wiki/API:Edit_-_Set_user_preferences
</code>
*
* @since 2021/12/14 18:53:43 拆分自 CeL.application.net.wiki.parser
*/
// More examples: see /_test suite/test.js
// Wikipedia bots demo: https://github.com/kanasimi/wikibot
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'application.net.wiki.parser.wikitext',
// for_each_subelement
require : 'application.net.wiki.parser.',
// 設定不匯出的子函式。
no_extend : 'this,*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring
var wiki_API = library_namespace.application.net.wiki;
// @inner
var PATTERN_wikilink = wiki_API.PATTERN_wikilink, PATTERN_wikilink_global = wiki_API.PATTERN_wikilink_global, PATTERN_URL_WITH_PROTOCOL_GLOBAL = wiki_API.PATTERN_URL_WITH_PROTOCOL_GLOBAL, PATTERN_invalid_page_name_characters = wiki_API.PATTERN_invalid_page_name_characters;
var
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
NOT_FOUND = ''.indexOf('_');
// --------------------------------------------------------------------------------------------
// CeL.wiki.HTML_to_wikitext(HTML)
// Please use CeL.wiki.wikitext_to_plain_text() instead!
// TODO: 應該 parse HTML。
// @see [[Module:Plain text]],
// https://www.mediawiki.org/w/api.php?action=help&modules=flow-parsoid-utils
// https://www.mediawiki.org/w/api.php?action=help&modules=parse
// https://www.mediawiki.org/w/api.php?action=help&modules=expandtemplates
function HTML_to_wikitext(HTML, options) {
return HTML
//
.replace(/<\/i><i>/g, '').replace(/<\/b><b>/g, '').replace(
/<\/strong><strong>/g, '')
//
.replace(/<i>([\s\S]+?)<\/i>/g, "''$1''").replace(
/<b>([\s\S]+?)<\/b>/g, "'''$1'''").replace(
/<strong>([\s\S]+?)<\/strong>/g, "'''$1'''")
//
.replace_till_stable(/<span(?: [^<>]*)?>([^<>]*?)<\/span>/g, "$1")
//
.replace(/<a ([^<>]+)>([\s\S]+?)<\/a>/g,
//
function(all, attributes, innerHTML) {
var href = attributes.match(/href="([^"]+)"/);
return '[' + (href ? href[1] : '#') + ' ' + innerHTML + ']';
})
//
.replace(/\s*<br(?:[^\w<>][^<>]*)?>[\r\n]*/ig, '\n').replace(
/<p ?\/>\n*/ig, '\n\n')
// ignore style, remove <p style="...">...</p>
// .replace(/<p[^<>]*>([^<>]*)<\/p>[\s\n]*/g, '$1\n\n')
.replace(/<p>([\s\S]+?)<\/p>\n*/g, '$1\n\n')
//
.replace(/\r?\n/g, '\n').replace(/\n{3,}/g, '\n\n');
}
// decode page title / section title
// @see [[mw:Manual:PAGENAMEE encoding#Encodings compared]]
function decode_title(title, original_title) {
if (!/%[\dA-F]{2}/i.test(title)) {
return original_title;
}
try {
// @see HTML_to_Unicode()
title = decodeURIComponent(title.replace_till_stable(
// MediaWiki 採用了非標準的 decodeURIComponent(),
// 可能是只 replace(/%([\s\S]{2}|$)/g)?
// [[#1%]] 不會像 decodeURIComponent() 一般 throw。
/%([\s\S]{2}|$)/g, function(all, code) {
return /^[\dA-F]{2}$/i.test(code) ? all
: encodeURIComponent('%') + code;
}));
} catch (e) {
// e.g., error after convert /\.([\dA-F]{2})/g
return original_title;
// 可能是非 UTF-8 編碼?
// TODO: 無法解碼可能會被辨識為普通文字而非 wikilink。
// e.g., "[[Francisco_Hern%E1ndez_de_C%F3"... @
// [[w:en:Talk:Francisco_Hernández_de_Córdoba_(Yucatán_conquistador)]]
}
if (/[\x00-\x1F\x7F]/.test(title)) {
// e.g. [[w:ja:エヴァンゲリオン (架空の兵器)#Mark.09]]
return original_title;
}
return title;
}
// --------------------------------------------------------------------------------------------
/**
* excluding the disambiguator, and remove diacritics of page_title
*
* @param {String}page_title
* 頁面標題。
* @param {Boolean}to_lower_case
* for case-insensitive compare
*
* @returns {String} sort key
*/
function page_title_to_sort_key(page_title, to_lower_case) {
if (!page_title)
return;
if (page_title.title) {
// input page_data
page_title = page_title.title;
}
// excluding the disambiguator
// e.g., [[Abdoul Karim Sylla (footballer, born 1981)]]
// → "Abdoul Karim Sylla"
var sort_key = page_title.toString().replace(/ \([^()]+\)$/, '');
if (sort_key.normalize) {
// with diacritics removed. to Latin alphabet
// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
sort_key = sort_key.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
}
if (to_lower_case)
sort_key = sort_key.toLowerCase();
return sort_key;
}
// TODO: check if the sort_key is the same as page title or DEFAULTSORT
function set_sort_key_of_category(sort_key) {
if (typeof sort_key === 'undefined' || sort_key === null)
return;
var category_token = this;
// const
var old_sort_key = category_token.sort_key
&& page_title_to_sort_key(category_token.sort_key);
if (old_sort_key) {
if (old_sort_key === sort_key) {
// Nothing changed
return;
}
if (old_sort_key.length > sort_key
|| !old_sort_key.startsWith(sort_key)) {
// <syntaxhighlight lang="wikitext" inline>...</syntaxhighlight>
library_namespace.debug('The sort key of <code><nowiki>'
+ category_token + '</nowiki></code> will be set to '
+ JSON.stringify(sort_key) + '!', 1,
'set_sort_key_of_category');
}
}
category_token[2] = category_token.sort_key = sort_key;
return true;
}
// --------------------------------------------------------------------------------------------
// parse wikitext.
/**
* 不包含可 parse 之要素,不包含 text 之 type。<br />
* 不應包含 section title,因可能有 "==[[]]==" 的情況。
*
* @type {Object}
*/
var atom_type = {
namespace : true,
// https://phabricator.wikimedia.org/T173889
page_title : true,
// external_link : true,
url : true,
style : true,
tag_single : true,
comment : true
};
// tree level
var KEY_DEPTH = 'depth';
/**
* 設定 token 為指定 type。將 token 轉為指定 type。
*
* @param {Array}token
* parse_wikitext() 解析 wikitext 所得之,以 {Array} 組成之結構。
* @param {String}type
* 欲指定之類型。 e.g., 'transclusion'.
*
* @returns {Array}token
*
* @see wiki_element_toString
*/
function set_wiki_type(token, type, parent) {
// console.trace(token);
if (typeof token === 'string') {
token = [ token ];
} else if (!Array.isArray(token)) {
library_namespace.warn('set_wiki_type: The token is not Array!');
} else if (token.type && token.type !== 'plain') {
// 就算 token.type !== type,可能是 <span> 中嵌套 <span> 的形式,
// 不該直接 `return token` 。
// 預防token本來就已經有設定類型。
token = [ token ];
}
// assert: Array.isArray(token)
token.type = type;
if (type in atom_type) {
token.is_atom = true;
}
// check
if (false && !wiki_element_toString[type]) {
throw new Error('.toString() not exists for type [' + type + ']!');
}
token.toString = wiki_element_toString[type];
if (false) {
Object.defineProperty(token, 'toString',
wiki_element_toString[type]);
}
if (false) {
var depth;
if (parent >= 0) {
// 當作直接輸入 parent depth。
depth = parent + 1;
} else if (parent && parent[KEY_DEPTH] >= 0) {
depth = parent[KEY_DEPTH] + 1;
}
// root 的 depth 為 (undefined|0)===0
token[KEY_DEPTH] = depth | 0;
}
return token;
}
// --------------------------------------------------------------------------------------------
/**
* 將特殊標記解譯/還原成 {Array} 組成之結構。
*
* @param {Array}queue
* temporary queue.
* @param {String}include_mark
* 解析用之起始特殊標記。
* @param {String}end_mark
* 結束之特殊標記。
*
* @see parse_wikitext()
*/
function resolve_escaped(queue, include_mark, end_mark, options) {
if (false) {
library_namespace.debug('queue: ' + queue.join('\n--- '), 4,
'resolve_escaped');
console.log('resolve_escaped: ' + JSON.stringify(queue));
}
var resolve_filter = options && options.resolve_filter;
function resolving_item(item) {
// result queue
var result = [];
item.split(include_mark).forEach(function(piece, index) {
if (index === 0) {
if (piece) {
result.push(piece);
}
return;
}
index = piece.indexOf(end_mark);
var token;
if (index === NOT_FOUND || index === 0
//
|| !((token = +piece.slice(0, index)) in queue)) {
result.push(include_mark + piece);
return;
}
token = queue[token];
if (resolve_filter && !resolve_filter(token)) {
result.push(include_mark + piece);
return;
}
result.push(token);
if (piece = piece.slice(index + end_mark.length))
result.push(piece);
});
if (result.length > 1) {
// console.log(result);
set_wiki_type(result, 'plain');
} else {
// console.trace(result);
result = result[0];
}
if (!resolve_filter && result.includes(include_mark)) {
// Error.stackTraceLimit = Infinity;
throw new Error('resolve_escaped: 仍有 include mark 殘留!');
}
return result;
}
if (options && ('resolve_item' in options)) {
return resolving_item(options.resolve_item);
}
var length = queue.length;
for (var index = queue.last_resolved_length | 0; index < length; index++) {
var item = queue[index];
if (false)
library_namespace.debug([ 'item', index, item ], 4,
'resolve_escaped');
if (typeof item !== 'string') {
// already resolved
// assert: Array.isArray(item)
continue;
}
var result = resolving_item(item);
queue[index] = result;
}
if (!resolve_filter)
queue.last_resolved_length = length;
// console.log('resolve_escaped end: '+JSON.stringify(queue));
}
// 經測試發現 {{...}} 名稱中不可有 [{}<>\[\]]
// while(/{{{[^{}\[\]]+}}}/g.exec(wikitext));
// [|{}] or [|{}=]
// 但允許 "{{\n name}}"
// 模板名#後的內容會忽略。
/** {RegExp}模板的匹配模式。 */
// var PATTERN_transclusion =
// /{{[\s\n]*([^\s\n#\|{}<>\[\]][^#\|{}<>\[\]]*)(?:#[^\|{}]*)?((?:(\||{{\s*!\s*}})[^<>\[\]]*)*?)}}/g;
/**
* {RegExp}wikilink內部連結的匹配模式。
*
* @see PATTERN_wikilink
*/
// var PATTERN_link =
// /\[\[[\s\n]*([^\s\n\|{}<>\[\]�][^\|{}<>\[\]�]*)((?:(\||{{\s*!\s*}})[^\|{}<>\[\]]*)*)\]\]/g;
/**
* Wikimedia projects 的 external link 匹配模式。
*
* matched: [ all external link wikitext, URL, protocol with "://",
* URL_others, delimiter, link name ]
*
* 2016/2/23: 經測試,若為結尾 /$/ 不會 parse 成 external link。<br />
* 2016/2/23: "[ http...]" 中間有空白不會被判別成 external link。
*
* @type {RegExp}
*
* @see PATTERN_URL_GLOBAL, PATTERN_URL_WITH_PROTOCOL_GLOBAL,
* PATTERN_URL_prefix, PATTERN_WIKI_URL, PATTERN_wiki_project_URL,
* PATTERN_external_link_global
*
* @see https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=protocols&utf8&format=json
*/
var PATTERN_external_link_global = new RegExp(
PATTERN_URL_WITH_PROTOCOL_GLOBAL.source
// 允許 [//example.com/]
.replace('):)', '):|\\/\\/)').replace(/^\([^()]+\)/, /\[/.source)
+ /(?:([^\S\r\n]+)([^\]]*))?\]/.source, 'ig'),
// 若包含 br|hr| 會導致 "aa<br>\nbb</br>\ncc" 解析錯誤!
/** {String}以"|"分開之 wiki tag name。 [[Help:Wiki markup]], HTML tags. 不包含 <a>! */
markup_tags = 'bdi|b|del|ins|i|u|font|big|small|sub|sup|h[1-6]|cite|code|em|strike|strong|s|tt|var|div|center|blockquote|[oud]l|table|caption|thead|tbody|tr|th|td|pre|ruby|r[tbp]|p|span|abbr|dfn|kbd|samp|data|time|mark'
// [[Help:Parser tag]], [[Help:Extension tag]]
+ '|includeonly|noinclude|onlyinclude'
// https://phabricator.wikimedia.org/T263082
// 會讀取目標語言的 MediaWiki 轉換表
// [[w:zh:Wikipedia:互助客栈/技术#新的语言转换语法已经启用]]
// 使用 <langconvert> 的頁面,優先級順序大概是:-{}- 頁面語言切換 > <langconvert> > 轉換組?
+ '|langconvert'
// [[Special:Version#mw-version-parser-extensiontags]]
// <ce> is deprecated, using <chem>
// Replace all usages of <ce> with <chem> on wiki
// https://phabricator.wikimedia.org/T155125
+ '|categorytree|ce|chem|charinsert|gallery|graph|hiero|imagemap|indicator|inputbox|nowiki|mapframe|maplink|math|poem|quiz|ref|references|score|section|source|syntaxhighlight|templatedata|templatestyles|timeline'
// https://www.mediawiki.org/wiki/Extension:DynamicPageList_(Wikimedia)
// + '|DynamicPageList'
// [[w:en:Template:Term]]
+ '|li|dt|dd',
// @see function get_PATTERN_full_tag()
PATTERN_invalid_end_tag = new RegExp('<(/)(' + markup_tags
+ ')([\\s/][^<>]*)?>', 'ig'),
// "<nowiki />", "<nowiki>...<nowiki>" are valid,
// but "<nowiki> without end tag" is invalid.
// 必須要寫成 <nowiki/>
// Parser extension tags @ [[Special:Version]]
// For {{#lst}}, {{#section:}}
// [[w:en:Help:Labeled section transclusion]]
// TODO: 標籤(tag)現在可以本地化 [[mw:Extension:Labeled_Section_Transclusion/zh]]
// templatestyles: https://www.mediawiki.org/wiki/Extension:TemplateStyles
// https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=extensiontags&utf8&format=json
// 優先權高低: <onlyinclude> → <nowiki> → <noinclude>, <includeonly>
// [[mw:Transclusion#Partial transclusion markup]]
// <noinclude>, <includeonly> 在解析模板時優先權必須高於其他 tags。
wiki_extensiontags = 'includeonly|noinclude|onlyinclude|'
// 在其內部的 wikitext 不會被 parse。允許內部採用 table 語法的 tags。例如
// [[mw:Manual:Extensions]]
// configurations.extensiontags
+ 'pre|nowiki|gallery|indicator|langconvert|timeline|hiero|imagemap|source|syntaxhighlight|poem|quiz|score|templatestyles|templatedata|graph|maplink|mapframe|charinsert|ref|references|inputbox|categorytree|section|math|ce|chem',
// <nowiki>不允許內部再解析,但這幾個的內部得再解析。
wiki_extensiontags_must_parse_inner = {
onlyinclude : true,
includeonly : true,
noinclude : true
},
/**
* {RegExp}HTML tags 的匹配模式 of <nowiki>。這些 tag 就算中間置入 "<!--" 也不會被當作
* comments,必須在 "<!--" 之前解析。 PATTERN_WIKI_TAG_of_wiki_extensiontags
*/
PATTERN_wiki_extensiontags = wiki_API.get_PATTERN_full_tag(
wiki_extensiontags, true),
// MediaWiki 可接受的 HTML void elements 標籤. self-closed HTML tags
// NO b|span|sub|sup|li|dt|dd|center|small
// 包含可使用,亦可不使用 self-closing 的 tags。
// self-closing: void elements + foreign elements
// https://www.w3.org/TR/html5/syntax.html#void-elements
// @see [[phab:T134423]]
// https://www.mediawiki.org/wiki/Manual:OutputPage.php
// https://www.mediawiki.org/wiki/Help:Lint_errors/self-closed-tag
self_closed_tags = 'area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr',
/** {RegExp}HTML self closed tags 的匹配模式。 */
PATTERN_WIKI_TAG_VOID = new RegExp('<(\/)?(' + self_closed_tags
// [[w:en:Help:Labeled section transclusion]]
// Allow `<section begin=chapter1 />`
+ '|' + wiki_extensiontags
// allow "<br/>"
+ ')(\/|\\s[^<>]*)?>', 'ig');
/** {RegExp}HTML tags 的匹配模式。 */
// var PATTERN_WIKI_TAG = wiki_API.get_PATTERN_full_tag(markup_tags);
wiki_extensiontags = wiki_extensiontags.split('|').to_hash();
markup_tags = markup_tags.split('|');
/** {RegExp}HTML tags 的匹配模式 without <nowiki>。 */
var PATTERN_non_wiki_extensiontags = wiki_API
.get_PATTERN_full_tag(markup_tags.filter(function(tag) {
return !(tag in wiki_extensiontags);
}));
var PATTERN_HTML_tag = wiki_API
.get_PATTERN_full_tag('[^<>\\s]+', null, 'i');
/**
* Test if the section title is old dot-encoded.
*
* e.g.,<code>
[[臺灣話#.E5.8F.97.E6.97.A5.E6.9C.AC.E8.AA.9E.E5.BD.B1.E9.9F.BF.E8.80.85|(其他參考資料)]]
[[Kingdom of Italy#Fascist regime .281922.E2.80.931943.29|Fascist Italy]]
</code>
*
* @see [[w:en:Help:Link#Section linking (anchors)]], [[w:en:WP:ANCHOR]]
* https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters
*/
var PATTERN_is_dot_encoded = /^([\w\s\-~!*'();:@&=+$,/?#\[\]]|\.[\dA-F]{2})+$/;
// [[MediaWiki:Converter-manual-rule-error]]: 在手动语言转换规则中检测到错误
var VALUE_converter_rule_error = 'converter-manual-rule-error';
/**
* .toString() of wiki elements: wiki_element_toString[token.type]<br />
* parse_wikitext() 將把 wikitext 解析為各 {Array} 組成之結構。當以 .toString() 結合時,將呼叫
* .join() 組合各次元素。此處即為各 .toString() 之定義。<br />
* 所有的 key (type) 皆為小寫。
*
* CeL.wiki.parse.wiki_element_toString.*
*
* @type {Object}
*
* @see parse_wikitext()
*/
var wiki_element_toString = {
// internal/interwiki link : language links : category links, file,
// subst 替換引用, ... : title
// e.g., [[m:en:Help:Parser function]], [[m:Help:Interwiki linking]],
// [[:File:image.png]], [[wikt:en:Wiktionary:A]],
// [[:en:Template:Editnotices/Group/Wikipedia:Miscellany for deletion]]
// [[:en:Marvel vs. Capcom 3: Fate of Two Worlds]]
// [[w:en:Help:Link#Http: and https:]]
//
// 應當使用 [[w:zh:維基百科:編輯提示|編輯提示]] 而非 [[:zh:w:維基百科:編輯提示|編輯提示]],
// 見 [[User:Cewbot/Stop]]。
//
// @see [[Wikipedia:Namespace]]
// https://www.mediawiki.org/wiki/Markup_spec#Namespaces
// [[ m : abc ]] is OK, as "m : abc".
// [[: en : abc ]] is OK, as "en : abc".
// [[ :en:abc]] is NOT OK.
namespaced_title : function() {
return this.join(this.oddly ? '' : ':');
},
// page title, template name
page_title : function() {
return this.join(':');
},
// link 的變體。但可採用 .name 取得 file name。
file : function() {
var wikitext = '[[' + this[0]
// anchor 網頁錨點
+ this[1];
if (this.length > 2) {
var pipe = this.pipe;
for (var index = 2; index < this.length; index++) {
// `pipe &&`: for .file.call([])
wikitext += (pipe && pipe[index - 2] || '|') + this[index];
}
}
return wikitext + ']]';
},
// link 的變體。但可採用 .name 取得 category name。
category : function() {
return '[[' + this[0]
// anchor 網頁錨點
+ this[1]
//
+ (this.length > 2 ? (this.pipe || '|')
//
+ this[2] : '') + ']]';
},
// 內部連結 (wikilink / internal link) + interwiki link
// TODO: [ start_quote (e.g., [<!----><!---->[), page_title, #anchor,
// pipe (e.g., {{!}}), display_text, end_quote (e.g., ]<!---->]) ]
// TODO: .normalize(): [[A|A]] → [[A]]
link : function() {
// 警告: for_each_subelement() 會忽略 .pipe, start_quote, end_quote 中的元素
// ({{!}}, <!---->)!
return '[[' + this[0]
// this: [ page_title, #anchor, display_text ]
+ (this[1] || '') + (this.length > 2
// && this[2] !== undefined && this[2] !== null
? (this.pipe || '|')
// + (this[2] || '')
+ this[2] : '') + ']]';
},
// 外部連結 external link, external web link
external_link : function() {
// [ url, delimiter, display_text ]
// assert: this.length === 1 or 3
// assert: this.length === 3
// && this[1].trim() === '' && this[2] === this[2].trimStart()
return '[' + this.join('') + ']';
},
url : function() {
return this.join('');
},
// template parameter
parameter : function() {
return '{{{' + this.join('|') + '}}}';
},
// template_parameter
parameter_unit : function() {
// [ parameter_name, " = ", parameter_value
// , [optional tail spaces] ]
return this.join('');
// assert: this[1] && this[1].includes('=')
// assert: this.parent && this.parent.type === 'transclusion'
},
// e.g., template
transclusion : function() {
return '{{' + this.join('|') + '}}';
},
magic_word_function : function() {
return '{{' + this[0] + this.slice(1).join('|') + '}}';
},
// [[Help:Table]]
table : function() {
// this: [ table style, row, row, ... ]
return (this.indentation || '') + '{|' + this.join('')
+ ('ending' in this ? this.ending : '\n|}');
},
// table attributes / styles, old name before 2021/1/24: table_style
table_attributes : function() {
return this.join('') + (this.suffix || '');
},
// table caption
caption : function() {
// this: [ main caption, invalid caption, ... ]
return (this.delimiter || '') + this.join('');
},
table_row : function() {
// this: [ row style, cell, cell, ... ]
return (this.delimiter || '') + this.join('');
},
table_cell : function() {
// this: [ contents ]
// this.delimiter:
// /\n[!|]|!!|\|\|/ or undefined (在 style/第一區間就已當作 cell)
return (this.delimiter || '') + this.join('');
},
// 手工字詞轉換 language conversion -{}-
convert : function(language, lang_fallbacks, force_show) {
if (!language) {
return '-{'
//
+ ('flag' in this ? (this._flag || this.flag) + '|' : '')
+ this.join(';') + '}-';
}
if (language === 'rule') {
// gets the rule of conversion only
return this.join(';');
}
if (language === 'normalized rule') {
// @see function item_to_conversion(item) @
// CeL.application.net.wiki
var rule = parse_wikitext('-{A|'
// .toString('rule')
+ this.join(';') + '}-', {
normalize : true,
})
// .toString('rule')
.join(';');
if (lang_fallbacks) {
// as 通用的轉換式
// function normalized_general_rule(token)
// const rule = token.toString('normalized rule', true);
// 通用的轉換式不該為連結。
rule = rule.replace(/:\[\[([^\[\]]+)\]\]($|;)/g, ':$1$2')
.replace(/^\[\[([^\[\]]+)\]\]$/g, '$1');
}
return rule;
}
var flag = this.flag;
if (!force_show && (flag in {
// hidden: add rule for convert code
// (but no display in placed code)
H : true,
// title
T : true,
// delete 去掉此 rule
'-' : true
})) {
return '';
}
if (flag in {
// raw content
R : true,
// description
D : true
}) {
return this.join(';');
}
language = language.trim().toLowerCase();
if (Array.isArray(flag)) {
if (!flag.includes(language)) {
// 單純顯示不繁簡轉換的文字。
return this.join(';');
}
// TODO: 顯示繁簡轉換後的文字。
return this.join(';');
}
// TODO: 後援語種 fallback language variant。
// https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=general%7Cnamespaces%7Cnamespacealiases%7Cstatistics
// language fallbacks: [[mw:Localisation statistics]]
// (zh-tw, zh-hk, zh-mo) → zh-hant (→ zh?)
// (zh-cn, zh-sg, zh-my) → zh-hans (→ zh?)
// [[Wikipedia_talk:地区词处理#zh-my|馬來西亞簡體華語]]
// [[MediaWiki:Variantname-zh-tw]]
if (!this.conversion[language]) {
if (/^zh-(?:tw|hk|mo)/.test(language)) {
language = 'zh-hant';
} else if (/^zh/.test(language)) {
language = 'zh-hans';
}
}
var convert_to = this.conversion[language];
if (Array.isArray(convert_to)) {
// e.g., -{H|zh-tw:a-{b}-c}-
var not_all_string;
convert_to = convert_to.map(function(token) {
if (typeof token === 'string')
return token;
if (token.type === 'convert'
&& typeof token.converted === 'string')
return token.converted;
not_all_string = true;
});
if (!not_all_string)
convert_to = convert_to.join('');
else
convert_to = this.conversion[language];
}
if (convert_to)
return convert_to;
if (typeof this.converted === 'string' && this.converted)
return this.converted;
// e.g., "-{zh-cn:下划线; zh-tw:底線}-" .toString('zh')
// console.trace([ this, arguments ]);
return VALUE_converter_rule_error;
},
// Behavior switches
'switch' : function() {
// assert: this.length === 1
return '__' + this[0] + '__';
},
// italic type
italic : function() {
// this: [ start_mark(''), content, end_mark('') ]
// @see KEY_apostrophe_element_content
// Warning: (0 in this), (2 in this) 可能為 false!
// Warning: this[0], this[2] 可能為 {Array}!
// assert: this.length === 3 || this.length === 2
return this.join('');
},
// emphasis
bold : function() {
// this: [ start_mark('''), content, end_mark(''') ]
// @see KEY_apostrophe_element_content
// Warning: (0 in this), (2 in this) 可能為 false!
// Warning: this[0], this[2] 可能為 {Array}!
// assert: this.length === 3 || this.length === 2
return this.join('');
},
// section title / section name
// show all section titles:
// parser=CeL.wiki.parser(page_data);parser.each('section_title',function(token,index){console.log('['+index+']'+token.title);},false,1);
// @see for_each_subelement()
// parser.each('plain',function(token){},{slice:[1,2]});
section_title : function(get_inner) {
// this.join(''): 必須與 wikitext 相同。見 parse_wikitext.title。
var inner = this.join('');
if (get_inner) {
// section_title.toString(true): get inner
// Must .trim() yourself.
return inner;
}
var level = '='.repeat(this.level);
return level + inner + level
// this.postfix maybe undefined, string, {Array}
+ (this.postfix || '');
},
// [[Help:Wiki markup]], HTML tags
tag : function() {
// this: [ {String}attributes, {Array}inner nodes ].tag
// 欲取得 .tagName,請用 this.tag.toLowerCase();
// 欲取得 .inner nodes,請用 this[1];
// 欲取得 .innerHTML,請用 this[1].toString();
return '<' + this.tag + (this[0] || '') + '>' + this[1]
+ ('ending' in this ? this.ending : '</' + this.tag + '>');
},
tag_attributes : function() {
return this.join('');
},
tag_inner : function() {
// console.trace(this);
return this.join('');
},
tag_single : function() {
// this: [ {String}attributes ].tag
// 欲取得 .tagName,請用 this.tag.toLowerCase();
// 有 .slash 代表 invalid tag。
return '<' + (this.slash || '') + this.tag + this.join('') + '>';
},
// comments: <!-- ... -->
comment : function() {
// "<\": for Eclipse JSDoc.
return '<\!--' + this.join('') + (this.no_end ? '' : '-->');
},
line : function() {
// https://www.mediawiki.org/wiki/Markup_spec/BNF/Article
// NewLine = ? carriage return and line feed ? ;
return this.join('\n');
},
list : function() {
return this.join('');
},
list_item : function() {
return (this.list_prefix || '') + this.join('');
},
pre : function() {
return ' ' + this.join('\n ');
},
hr : function() {
return this[0];
},
paragraph : function() {
return this.join('\n') + (this.separator || '');
},
// plain text 或尚未 parse 的 wikitext.
plain : function() {
return this.join('');
}
};
// const , for <dl>
var DEFINITION_LIST = 'd';
// !!default_magic_words_hash[magic_word] === 必須指定數值,採用 {{#MW:value}}
// else 可單用 {{MW}}
var default_magic_words_hash = Object.create(null);
// https://www.mediawiki.org/wiki/Help:Magic_words
// https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=functionhooks&utf8&format=json
('DISPLAYTITLE|DEFAULTSORT|DEFAULTSORTKEY|DEFAULTCATEGORYSORT|デフォルトソート'
+ '|ns|nse|lc|lcfirst|uc|ucfirst' + '|padleft|padright|bidi'
+ '|formatnum' + '|urlencode|anchorencode'
+ '|localurl|fullurl|filepath'
// https://www.mediawiki.org/wiki/Help:Magic_words#Transclusion_modifiers
// https://en.wikipedia.org/wiki/Help:Transclusion#Transclusion_modifiers
// https://www.mediawiki.org/wiki/Help:Magic_words#Localization
+ '|PLURAL|GRAMMAR|GENDER|int|msg|raw|msgnw|subst|safesubst'
// 這些需要指定數值。 e.g., {{NS:1}}: OK, {{NS}} will get " ", {{NS:}} will get ""
).split('|').forEach(function name(magic_word) {
default_magic_words_hash[magic_word.toUpperCase()] = true;
});
// https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=variables&utf8&format=json
('!|='
+ '|CURRENTYEAR|CURRENTMONTH|CURRENTDAY|CURRENTTIME|CURRENTHOUR|CURRENTWEEK|CURRENTTIMESTAMP'
+ '|NAMESPACE|NAMESPACENUMBER'
+ '|FULLPAGENAME|PAGENAME|BASEPAGENAME|SUBPAGENAME|SUBJECTPAGENAME|ARTICLEPAGENAME|TALKPAGENAME|ROOTPAGENAME'
+ '|FULLPAGENAMEE|PAGENAMEE|BASEPAGENAMEE|SUBPAGENAMEE|SUBJECTPAGENAMEE|ARTICLEPAGENAMEE|TALKPAGENAMEE|ROOTPAGENAMEE'
// 這些不用指定數值。
).split('|').forEach(function name(magic_word) {
default_magic_words_hash[magic_word.toUpperCase()] = false;
});
// matched: [ all, inner parameters ]
var PATTERN_language_conversion = /-{(|[^\n{].*?)}-/g;
// parse 手動轉換語法的轉換標籤的語法
// 經測試,":"前面與後面不可皆有空白。
// (\s{2,}): 最後的單一/\s/會被轉換為" "
// matched: [ all, 指定轉換字串, 指定轉換詞, spaces,
// this language code, colon, this language token, last spaces ]
var PATTERN_conversion_slice = /^(([\s\S]+?)=>)?(\s*)(zh(?:-(?:cn|tw|hk|mo|sg|my|hant|hans))?)(\s*:|:\s*)(\S.*?)(\s{2,})?$/;
// 狀態開關: [[mw:Help:Magic words#Behavior switches]]
var PATTERN_BEHAVIOR_SWITCH = /__([A-Z]+(?:_[A-Z]+)*)__/g;
PATTERN_BEHAVIOR_SWITCH = /__(NOTOC|FORCETOC|TOC|NOEDITSECTION|NEWSECTIONLINK|NONEWSECTIONLINK|NOGALLERY|HIDDENCAT|NOCONTENTCONVERT|NOCC|NOTITLECONVERT|NOTC|INDEX|NOINDEX|STATICREDIRECT|NOGLOBAL)__/g;
// [[w:en:Wikipedia:Extended image syntax]]
// [[mw:Help:Images]]
var file_options = {
// Type, display format, 表示形式
thumb : 'format',
thumbnail : 'format',
frame : 'format',
framed : 'format',
frameless : 'format',
// Border, 外枠, 縁取る, 境界
border : 'border',
// Location, Horizontal alignment option, 配置位置
right : 'location',
left : 'location',
// 居中, 不浮動
center : 'location',
// 不浮動
none : 'location',
// Vertical alignment option, 垂直方向の位置
baseline : 'alignment',
middle : 'alignment',
sub : 'alignment',
'super' : 'alignment',
'text-top' : 'alignment',
'text-bottom' : 'alignment',
top : 'alignment',
bottom : 'alignment',
// Link option
// link : 'link',
// alt : 'alt',
// lang : 'language',
// https://en.wikipedia.org/wiki/Wikipedia:Creation_and_usage_of_media_files#Setting_a_video_thumbnail_image
// thumbtime : 'video_thumbtime',
// start : 'video_start',
// end : 'video_end',
// page : 'book_page',
// 'class' : 'CSS_class',
// Size, Resizing option
// 放大倍數
upright : 'size'
};
function join_string_of_array(array) {
for (var index = 1; index < array.length;) {
if (typeof array[index] !== 'string') {
index++;
continue;
}
if (array[index] === '') {
array.splice(index, 1);
continue;
}
if (typeof array[index - 1] === 'string') {
array[index - 1] += array[index];
array.splice(index, 1);
} else {
index++;
}
}
return array;
}
// is_wiki_token(), is_token()
function is_parsed_element(value) {
return Array.isArray(value) && (value.type in wiki_element_toString);
}
/** token為無意義的token。 */
function is_meaningless_element(token) {
if (typeof token === 'string')
return !token.trim();
if (Array.isArray(token)) {
return token.type in {
comment : true
};
}
}
// 可算 function preprocess_section_link_token(token, options) 的簡化版。
// 可能保留 "\n"、不可見字符 必須自己 .trim()。
function wiki_element_to_key(token) {
if (!Array.isArray(token))
return token;
var has_no_string;
// key token must accept '\n'. e.g., "key_ \n _key = value"
var _token = token.filter(function(t) {
if (t.type === 'plain')
t = wiki_element_to_key(t);
if (!Array.isArray(t))
return t;
// 去除註解 comments。
if (t.type === 'comment') {
return;
}
has_no_string = true;
return true;
});
// console.trace([ has_no_string, _token ]);
if (token.type === 'plain' && !has_no_string)
return _token.join('');
return set_wiki_type(_token, token.type);
}
// for_each_subelement() 的超簡化版
// @inner
function for_each_non_plain_subtoken(token, for_each_token, index, parent) {
if (!Array.isArray(token) || token.type !== 'plain') {
return for_each_token(token, index, parent);
}
return token.some(function(suntoken, index, parent) {
return for_each_non_plain_subtoken(suntoken, for_each_token, index,
parent);
});
}
/**
* 將層層疊疊的 plain: [ plain[], plain[], ... ]<br />
* 轉成 [ non-plain, non-plain, non-plain, … ]
*
* @param {Array}token
* root token
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
* @returns
*/
function flat_plain_element(token, options) {
var element_list = set_wiki_type([], 'plain');
function add_subtoken(plain_token) {
for (var index = 0, length = plain_token.length; index < length; index++) {
var subtoken = plain_token[index];
if (subtoken.type === 'plain') {
add_subtoken(subtoken);
continue;
}
// element_list[element_list.length - 1];
var previous_subtoken = element_list.at(-1);
if (typeof previous_subtoken === 'string'
// e.g., "</s>" only
&& (subtoken.type === 'tag_single' && subtoken.slash
// Keep new line.
|| typeof subtoken === 'string' && !subtoken.startsWith('\n')
//
&& !previous_subtoken.endsWith('\n'))) {
// assert: element_list.length > 0
// Warning: may need re-parse!
element_list[element_list.length - 1] += subtoken;
} else {
element_list.push(subtoken);
}
}
}
add_subtoken(token.type === 'plain' ? token : [ token ]);
return element_list;
}
function generate_token_pattern(pattern_template, options) {
if (library_namespace.is_RegExp(pattern_template))
pattern_template = pattern_template.source;
// assert: typeof pattern_template === 'string'
options = library_namespace.setup_options(options);
var include_mark = library_namespace
.to_RegExp_pattern(options.include_mark || default_include_mark),
//
end_mark = library_namespace.to_RegExp_pattern(options.end_mark
|| default_end_mark),
// /\d+/
queue_index = /\d{1,5}/.source,
//
pattern = new RegExp(pattern_template.replace(
/token_mark/g,
include_mark
+ (options.with_queue_index ? '(' + queue_index + ')'
: queue_index) + end_mark).replace(
/include_mark/g, include_mark).replace(/end_mark/g, end_mark),
options.flags || 'g');
return pattern;
}
var KEY_apostrophe_element_start_quote = 0, KEY_apostrophe_element_content = 1, KEY_apostrophe_element_end_quote = 2,
//
default_include_mark = '\x00', default_end_mark = '\x01',
// \n, $ 都會截斷 italic, bold
// <tag> 不會截斷 italic, bold
template_PATTERN_text_apostrophe_unit = /([^\n]*?)('(?:(?:token_mark)*')+|\n|$)/.source,
// [ all, text, apostrophes(''+) ]
default_PATTERN_text_apostrophe_unit = generate_token_pattern(template_PATTERN_text_apostrophe_unit),
// is_dt
PATTERN_prefix_is_description_term = /;\s*$/;
/**
* parse The MediaWiki markup language (wikitext / wikicode). 解析維基語法。
* 維基語法解析器
*
* TODO:<code>
<p<!-- -->re>...</pre>
parse:
{{Template:Single chart}}
</code>
*
* 此功能之工作機制/原理:<br />
* 找出完整的最小單元,並將之 push 入 queue,並把原 string 中之單元 token 替換成:<br />
* {String}include_mark + ({ℕ⁰:Natural+0}index of queue) + end_mark<br />
* e.g.,<br />
* "a[[p]]b{{t}}" →<br />
* "a[[p]]b\00;", queue = [ ["t"].type='transclusion' ] →<br />
* "a\01;b\00;", queue = [ ["t"].type='transclusion', ["p"].type='link' ]<br />
* 最後再依 queue 與剩下的 wikitext,以 resolve_escaped() 作 resolve。
*
* @param {String}wikitext
* wikitext to parse
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
* @param {Array}[queue]
* temporary queue. 基本上僅供內部使用。
*
* @returns {Array}parsed data
*
* @see https://blog.wikimedia.org/2013/03/04/parsoid-how-wikipedia-catches-up-with-the-web/
* https://phabricator.wikimedia.org/diffusion/GPAR/
*
* @see [[w:en:Help:Wikitext]], [[Wiki標記式語言]]
* https://www.mediawiki.org/wiki/Markup_spec/BNF/Article
* https://www.mediawiki.org/wiki/Markup_spec/BNF/Inline_text
* https://www.mediawiki.org/wiki/Markup_spec
* https://www.mediawiki.org/wiki/Wikitext
* https://doc.wikimedia.org/mediawiki-core/master/php/html/Parser_8php.html
* Parser.php: PHP parser that converts wiki markup to HTML.
* https://mwparserfromhell.readthedocs.io/
*/
function parse_wikitext(wikitext, options, queue) {
if (!wikitext) {
return wikitext;
}
function _set_wiki_type(token, type) {
// 這可能性已經在下面個別處理程序中偵測並去除。
if (false && typeof token === 'string'
&& token.includes(include_mark)) {
queue.push(token);
resolve_escaped(queue, include_mark, end_mark);
token = [ queue.pop() ];
}
return set_wiki_type(token, type, wikitext);
// 因為parse_wikitext()採用的是從leaf到root的解析法,因此無法在解析leaf時就知道depth。
// 故以下廢棄。
var node = set_wiki_type(token, type);
library_namespace.debug('set depth ' + depth_of_children
+ ' to children [' + node + ']', 3, '_set_wiki_type');
node[KEY_DEPTH] = depth_of_children;
return node;
}
// 正規化並提供可隨意改變的同內容參數,以避免修改或覆蓋附加參數。
// 每個parse_wikitext()都需要新的options,需要全新的。
// options = Object.assign({}, options);
options = library_namespace.setup_options(options);
var session = wiki_API.session_of_options(options);
if (false) {
// assert: false>=0, (undefined>=0)
// assert: (NaN | 0) === 0
var depth_of_children = ((options[KEY_DEPTH]) | 0) + 1;
// assert: depth_of_children >= 1
library_namespace.debug('[' + wikitext + ']: depth_of_children: '
+ depth_of_children, 3, 'parse_wikitext');
options[KEY_DEPTH] = depth_of_children;
}
var
/**
* 解析用之起始特殊標記。<br />
* 需找出一個文件中不可包含,亦不會被解析的字串,作為解析用之起始特殊標記。<br />
* e.g., '\u0000' === '\x00' === '\0'.<br />
* include_mark + ({ℕ⁰:Natural+0}index of queue) + end_mark
*
* assert: /\s/.test(include_mark) === false
*
* @see [[mw:Strip marker]]
*
* @type {String}
*/
include_mark = options.include_mark || default_include_mark,
/**
* {String}結束之特殊標記。 end of include_mark. 不可為數字 (\d) 或
* include_mark,不包含會被解析的字元如 /;/。應為 wikitext 所不容許之字元。<br />
* e.g., '\x01' === '\u0001'.<br />
*/
end_mark = options.end_mark || default_end_mark,
//
PATTERN_text_apostrophe_unit = include_mark === default_include_mark
&& end_mark === default_end_mark ? default_PATTERN_text_apostrophe_unit
: generate_token_pattern(template_PATTERN_text_apostrophe_unit,
options),
/** {Boolean}是否順便作正規化。預設不會規範頁面內容。 */
normalize = options.normalize,
/** {Array}是否需要初始化。 [ {String}prefix added, {String}postfix added ] */
initialized_fix = !queue && [ '', '' ],
// 這項設定不應被繼承。
no_resolve = options.no_resolve;
if (no_resolve) {
delete options.no_resolve;
}
if (/\d/.test(end_mark) || include_mark.includes(end_mark))
throw new Error('Error end of include_mark!');
if (initialized_fix) {
// 初始化。
if (!wikitext.replace) {
if (is_parsed_element(wikitext)) {
library_namespace.debug('Treat [' + wikitext
+ '] as parsed token and return directly!', 3,
'parse_wikitext');
return wikitext;
}
console.trace(wikitext);
}
wikitext = wikitext
// 注意: 2004年5月早期的中文維基百科換行採用 "\r\n",因此必須保留 "\r"。
// .replace(/\r\n/g, '\n')
.replace(
// 先 escape 掉會造成問題之 characters。
new RegExp(include_mark.replace(/([\s\S])/g, '\\$1'), 'g'),
include_mark + end_mark);
if (!wikitext.startsWith('\n') &&
// /\n([*#:;]+|[= ]|{\|)/:
// https://www.mediawiki.org/wiki/Markup_spec/BNF/Article#Wiki-page
// https://www.mediawiki.org/wiki/Markup_spec#Start_of_line_only
/^(?:[*#;:=\s]|{\|)/.test(wikitext))
wikitext = (initialized_fix[0] = '\n') + wikitext;
if (!wikitext.endsWith('\n'))
wikitext += (initialized_fix[1] = '\n');
// setup temporary queue
queue = [];
}
var section_title_hierarchy = queue.section_title_hierarchy
|| (queue.section_title_hierarchy = []);
if (!section_title_hierarchy[0]) {
// As root
section_title_hierarchy[0] = options.target_array
|| Object.create(null);
section_title_hierarchy[0].child_section_titles = [];
}
if (!queue.conversion_table) {
// [[MediaWiki:Conversiontable/zh-hant]]
queue.conversion_table = Object.create(null);
}
if (typeof options.prefix === 'function') {
wikitext = options.prefix(wikitext, queue, include_mark, end_mark)
|| wikitext;
}
// 預防有特殊 elements 置入其中。此時將之當作普通 element 看待。
// cf. deep resolve_escaped()
function shallow_resolve_escaped(text) {
if (text.includes(include_mark)) {
// 經過改變,需再進一步處理。
text = parse_wikitext(text, options, queue);
}
return text;
}
// console.trace(wikitext);
// ------------------------------------------------------------------------
// parse functions
function parse_language_conversion(all, parameters) {
// -{...}- 自 end_mark 向前回溯。
var index = parameters.lastIndexOf('-{'),
// 在先的,在前的,前面的; preceding
// (previous 反義詞 following, preceding 反義詞 exceeds)
previous;
if (index > 0) {
previous = '-{' + parameters.slice(0, index);
parameters = parameters.slice(index + '}-'.length);
} else {
previous = '';
}
library_namespace.debug(previous + ' + ' + parameters, 4,
'parse_wikitext.convert');
// console.log(parameters);
var conversion = Object.create(null),
//
conversion_list = [], latest_language;
var _flag = parameters.match(/^([a-zA-Z\-;\s]*)\|(.*)$/), flag;
if (_flag) {
parameters = _flag[2];
var flag_hash = Object.create(null);
_flag = _flag[1];
flag = _flag.split(';').map(function(f) {
f = f.trim();
if (f)
flag_hash[f] = true;
return f;
}).filter(function(f) {
return !!f;
});
if (flag.length === 0) {
flag = '';
} else {
// https://doc.wikimedia.org/mediawiki-core/master/php/ConverterRule_8php_source.html
// 僅取首先符合者。
[ 'R', 'N', '-', 'T', 'H', 'A', 'D' ].some(function(f) {
if (flag_hash[f]) {
flag = f;
return true;
}
});
}
}
var conversion_table = flag && (flag in {
// '+' add rules for alltext
// '+' : true,
// these flags above are reserved for program
// remove convert (not implement)
// '-' : true,
// add rule for convert code (but no display in placed code)
H : true,
// add rule for convert code (all text convert)
A : true
}) && queue.conversion_table;
// console.log('parameters: ' + JSON.stringify(parameters));
parameters = parameters.split(';');
parameters.forEach(function(converted, index) {
if (normalize) {
// remove tail spaces
converted = converted.trim();
}
if (PATTERN_conversion_slice.test(converted)
// e.g., "-{ a; zh-tw: tw }-" 之 " a"
|| conversion_list.length === 0
// 最後一個是空白。
|| !converted.trim() && index + 1 === parameters.length) {
conversion_list.push(converted);
} else {
conversion_list[conversion_list.length - 1]
// e.g., "-{zh-tw: tw ; tw : tw2}-"
+= ';' + converted;
}
});
// console.log(conversion_list);
var convert_from_hash = conversion_table && Object.create(null);
var unidirectional = [];
/**
* [[Help:高级字词转换语法#基本语法]]
*
* <code>
-{zh-cn:cn; zh-tw:tw;}-
→
conversion_table['cn'] = conversion_table['tw'] =
{'zh-cn':'cn','zh-tw':'tw'}
-{txt=>zh-cn:cn; txt=>zh-tw:tw;}-
→
conversion_table['txt'] =
{'zh-cn':'cn','zh-tw':'tw'}
-{txt=>zh-cn:cn; zh-cn:cn; zh-tw:tw;}-
→
conversion_table['txt'] =
{'zh-cn':'cn'}
∪
conversion_table['cn'] = conversion_table['tw'] =
{'zh-cn':'cn','zh-tw':'tw'}
</code>
*/
// TODO: 剖析不出任何對應規則的話,則為 R 旗標轉換,即是停用字詞轉換,顯示原文(R stands for raw)。
conversion_list = conversion_list.map(function(token) {
var matched = token.match(PATTERN_conversion_slice);
// console.log(matched);
if (!matched
// e.g., -{A|=>zh-tw:tw}-
|| matched[1] && !(matched[2] = matched[2].trim())) {
// 經過改變,需再進一步處理。
return parse_wikitext(token, options, queue);
}
// matched.shift();
matched = matched.slice(1);
// matched: [ 指定轉換字串, 指定轉換詞, spaces,
// this language code, colon, this language token, last spaces ]
if (!matched[6])
matched.pop();
// 語言代碼 language variant 用字模式
var language_code = matched[3].trim(), convert_to
// 經過改變,需再進一步處理。
= matched[5] = parse_wikitext(matched[5], options, queue);
if (!convert_to) {
// 'converter-manual-rule-error'
return parse_wikitext(token, options, queue);
}
conversion[language_code] = convert_to;
if (!matched[2]) {
matched.splice(2, 1);
}
// 指定僅轉換某些特殊詞彙。
// unidirectional_convert_from
var uniconvert_from;
if (matched[0]) {
uniconvert_from = matched[1].trim();
if (!uniconvert_from) {
// if $from is empty, strtr() could return a wrong
// result.
}
matched.splice(1, 1);
} else {
matched.splice(0, 2);
}
token = _set_wiki_type(matched, 'plain');
token.is_conversion = true;
if (!conversion_table) {
;
} else if (uniconvert_from) {
// 單向轉換 unidirectional convert
unidirectional.push(uniconvert_from);
if (!conversion_table[uniconvert_from]) {
conversion_table[uniconvert_from]
// Initialization
= Object.create(null);
} else if (conversion_table[uniconvert_from].conversion) {
conversion_table[uniconvert_from] = Object.clone(
// assert:
// conversion_table[uniconvert_from].type==='convert'
conversion_table[uniconvert_from].conversion);
}
if (false && options.conflict_conversion
// overwrite
&& conversion_table[uniconvert_from][language_code]) {
options.conflict_conversion.call(conversion_table,
uniconvert_from, language_code,
conversion_table[uniconvert_from]
//
[language_code], convert_to);
}
conversion_table[uniconvert_from][language_code]
// settle
= convert_to;
} else if (typeof convert_to === 'string') {
// 後面的設定會覆蓋先前的設定。
convert_from_hash[language_code] = convert_to;
} else if (convert_to && convert_to.type === 'plain') {
// 雙向轉換 bidirectional convert
// -{H|zh-cn:俄-{匊}-斯;zh-tw:俄-{匊}-斯;zh-hk:俄-{匊}-斯;}-
// 當作 "俄匊斯"
var not_all_string;
convert_to = convert_to.map(function(token) {
if (typeof token === 'string')
return token;
if (token.type === 'convert'
&& typeof token.converted === 'string')
return token.converted;
not_all_string = true;
});
if (!not_all_string) {
// 後面的設定會覆蓋先前的設定。
convert_from_hash[language_code] = convert_to.join('');
}
}
// console.log(JSON.stringify(token));
return token;
});
if (normalize) {
// 正規化後可以不保留 -{...;}- 最後的 ';'
conversion_list = conversion_list.filter(function(token) {
return !!token;
});
conversion_list.sort(function(_1, _2) {
// assert: {Array} _1, _2
return _1[0] < _2[0] ? -1 : _1[0] > _2[0] ? 1 : 0;
});
}
// console.log(conversion_list);
parameters = _set_wiki_type(conversion_list, 'convert');
parameters.conversion = conversion;
if (unidirectional.length > 0)
parameters.unidirectional = unidirectional.unique();
if (typeof _flag === 'string') {
if (_flag !== flag)
parameters._flag = _flag;
parameters.flag = flag;
if (flag === 'T')
options.conversion_title = parameters;
}
// console.log(convert_from_hash);
convert_from_hash && Object.values(convert_from_hash)
//
.forEach(function(convert_from_string) {
// console.log(convert_from_string);
conversion_table[convert_from_string] = parameters;
});
// console.log(JSON.stringify(wikitext));
// console.log(conversion_table);
if (queue.switches && (queue.switches.__NOCC__
// 使用魔術字 __NOCC__ 或 __NOCONTENTCONVERT__ 可避免轉換。
|| queue.switches.__NOCONTENTCONVERT__)) {
parameters.no_convert = true;
} else if (Object.keys(conversion).length === 0) {
// assert: parameters.length === 1
// e.g., "-{ t {{T}} }-"
// NOT "-{ zh-tw: tw {{T}} }-"
parameters.converted = parameters[0];
} else if (options.language) {
// TODO: 先檢測當前使用的語言,然後轉成在當前環境下轉換過、會顯示出的結果。
var converted = parameters.toString(options.language);
if (converted !== VALUE_converter_rule_error)
parameters.converted = converted;
}
queue.push(parameters);
return previous + include_mark + (queue.length - 1) + end_mark;
}
var PATTERN_token_unit;
function setup_PATTERN_token_unit() {
if (!PATTERN_token_unit) {
PATTERN_token_unit = generate_token_pattern(
// matched: [ , string, token_mark, queue_index ]
/([\s\S]*?)(token_mark|$)/g, Object.assign(Object
.clone(options), {
with_queue_index : true
}));
} else {
// reset PATTERN index
PATTERN_token_unit.lastIndex = 0;
}
}
function has_invalid_token(original_value, invalid_token_filter) {
// assert: original_value.includes(include_mark)
setup_PATTERN_token_unit();
var invalid_token_data = [/* valid_value */'' ];
while (PATTERN_token_unit.lastIndex < original_value.length) {
var matched = PATTERN_token_unit.exec(original_value);
// matched: [ , string, token_ma