Skip to content

Commit 28d26df

Browse files
committed
fix function parse_bare_url(), function parse_external_link()
1 parent 9baf66e commit 28d26df

4 files changed

Lines changed: 148 additions & 56 deletions

File tree

_test suite/test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3599,6 +3599,10 @@ function test_wiki() {
35993599
wikitext = '[https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/ text]'; parsed = CeL.wiki.parse(wikitext);
36003600
assert([wikitext, parsed.toString()], 'wiki.parse: external link #11');
36013601
assert(['external_link', parsed.type], 'wiki.parse: external link #11-1');
3602+
assert(['url', parsed[0].type], 'wiki.parse: external link #11-2');
3603+
assert(['https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/', parsed[0].toString()], 'wiki.parse: external link #11-3');
3604+
assert([' ', parsed[1]], 'wiki.parse: external link #11-4');
3605+
assert(['text', parsed[2].toString()], 'wiki.parse: external link #11-5');
36023606
wikitext = "[http://example.com/foo{{!}}bar''foo bar'']"; parsed = CeL.wiki.parse(wikitext);
36033607
assert([wikitext, parsed.toString()], 'wiki.parse: external link #12');
36043608
assert(['external_link', parsed.type], 'wiki.parse: external link #12-1');
@@ -3642,6 +3646,13 @@ function test_wiki() {
36423646
wikitext = '[https://a.b{{t}}]'; parsed = CeL.wiki.parse(wikitext);
36433647
assert([wikitext, parsed.toString()], 'wiki.parse: external link #19');
36443648
assert(['external_link', parsed.type], 'wiki.parse: external link #19-1');
3649+
wikitext = '[https://www.example.com/path[title]'; parsed = CeL.wiki.parse(wikitext);
3650+
assert([wikitext, parsed.toString()], 'wiki.parse: external link #20');
3651+
assert(['external_link', parsed.type], 'wiki.parse: external link #20-1');
3652+
wikitext = '[https://www.example.com/path[title]]'; parsed = CeL.wiki.parse(wikitext);
3653+
assert([wikitext, parsed.toString()], 'wiki.parse: external link #21');
3654+
assert(['external_link', parsed[0].type], 'wiki.parse: external link #21-1');
3655+
assert([']', parsed[1]], 'wiki.parse: external link #21-2');
36453656

36463657
wikitext = 't<!--='; parsed = CeL.wiki.parse(wikitext);
36473658
assert([wikitext, parsed.toString()]);

application/net/wiki/namespace.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function module_code(library_namespace) {
8989
}
9090

9191
// https://meta.wikimedia.org/wiki/Help:Page_name#Special_characters
92+
// https://en.wikipedia.org/wiki/Wikipedia:Page_name#Technical_restrictions_and_limitations
9293
// @see $wgLegalTitleChars
9394
var PATTERN_invalid_page_name_characters = /[{}\[\]\|<>\t\n#]/,
9495
// https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(technical_restrictions)#Forbidden_characters
@@ -146,7 +147,8 @@ function module_code(library_namespace) {
146147
*/
147148
PATTERN_URL_WITH_PROTOCOL_GLOBAL
148149
// 警告: PATTERN_external_link_global 會用到 '):)'
149-
= /(^|[^a-z\d_])(((?:https?|ssh|telnet|ftps?|sftp|gopher|ircs?|news|nntp|worldwind|svn|git|mms):?\/\/|(?:mailto|urn):)((?:\[[a-f\d:]+\]|[^\s\|<>\[\]\/])[^\s\|<>\[\]]*))/ig;
150+
= /(^|[^a-z\d_])(((?:https?|ssh|telnet|ftps?|sftp|gopher|ircs?|news|nntp|worldwind|svn|git|mms):?\/\/|(?:mailto|urn):)((?:\[[a-f\d:]+\]|[^\s\|<>\]\/])[^\s\|<>\]]*))/ig;
151+
var PATTERN_IPv6_prefix = /^\[[a-f\d:]+\]/i;
150152

151153
/**
152154
* 匹配以URL網址起始。
@@ -4920,6 +4922,7 @@ function module_code(library_namespace) {
49204922
// PATTERN_page_name : PATTERN_page_name,
49214923
PATTERN_file_prefix : PATTERN_file_prefix,
49224924
PATTERN_URL_WITH_PROTOCOL_GLOBAL : PATTERN_URL_WITH_PROTOCOL_GLOBAL,
4925+
PATTERN_IPv6_prefix : PATTERN_IPv6_prefix,
49234926
PATTERN_category_prefix : PATTERN_category_prefix,
49244927

49254928
PATTERN_PROJECT_CODE_i : PATTERN_PROJECT_CODE_i

application/net/wiki/parser/misc.js

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function module_code(library_namespace) {
4141
// requiring
4242
var wiki_API = library_namespace.application.net.wiki;
4343
// @inner
44-
var PATTERN_URL_prefix = wiki_API.PATTERN_URL_prefix;
44+
var PATTERN_URL_prefix = wiki_API.PATTERN_URL_prefix, PATTERN_invalid_page_name_characters = wiki_API.PATTERN_invalid_page_name_characters;
4545

4646
// --------------------------------------------------------------------------------------------
4747

@@ -196,6 +196,8 @@ function module_code(library_namespace) {
196196
if (!url)
197197
return;
198198

199+
options = library_namespace.setup_options(options);
200+
199201
var session = wiki_API.session_of_options(options);
200202
var configurations = session && session.latest_site_configurations;
201203

@@ -208,6 +210,7 @@ function module_code(library_namespace) {
208210
}
209211
if (url.url)
210212
url = url.url;
213+
211214
if (!url) {
212215
// e.g., '[{{fullurl:Special:Purge/{{PAGENAME}}}} 清除頁面緩存]'
213216
return;
@@ -216,6 +219,14 @@ function module_code(library_namespace) {
216219
url = String(url);
217220
url = library_namespace.HTML_to_Unicode(url);
218221

222+
if (typeof options.postfix_url === 'function') {
223+
url = options.postfix_url(url);
224+
if (!url) {
225+
return;
226+
}
227+
// assert: typeof url === 'string'
228+
}
229+
219230
/**
220231
* Wikimedia 網域名轉換 <code>
221232
@@ -340,50 +351,63 @@ function module_code(library_namespace) {
340351

341352
// ------------------------------------------------
342353

343-
var link_title = [];
344-
if (prefix) {
345-
if (prefix.includes(':')) {
346-
link_title.push(prefix, ':');
347-
} else if (prefix !== session.language) {
348-
link_title.push(':', prefix, ':');
349-
}
350-
351-
} else if (!('localinterwiki' in interwikimap_data)) {
352-
// ↑ localinterwiki: 跨維基連結是否指向目前維基。
353-
// https://www.mediawiki.org/wiki/API:Siteinfo#Interwikimap
354-
if (interwikimap_data.language) {
355-
link_title.push(
356-
// 跨語言連結不加 ':' prefix 會被當成維基數據中的跨語言連結。
357-
':',
358-
// 在 adapt_site_configurations() 中已確保
359-
// interwikimap_data_list[0].prefix 與 url 一致,
360-
// 不必使用 interwiki_data.shortcut_prefix。
361-
interwiki_data.prefix);
362-
} else {
363-
link_title.push(interwiki_data.shortcut_prefix
364-
|| interwiki_data.prefix);
365-
}
366-
link_title.push(':');
367-
}
368-
link_title.push(name);
354+
// display_text 別包含 prefix。
355+
interwiki_data.display_text = external_link && external_link[2] || name;
369356

370-
interwiki_data.link_title = link_title.join('');
357+
if (PATTERN_invalid_page_name_characters.test(name)) {
358+
interwiki_data.is_invalid_page_title = true;
359+
library_namespace.error('parse_interwiki_url: '
360+
+ (options.page_data ? wiki_API
361+
.title_link_of(options.page_data)
362+
+ ': ' : '') + 'Invalid page title '
363+
+ JSON.stringify(name) + ' in interwiki link: '
364+
+ (external_link || url));
371365

372-
// Release memory. 釋放被占用的記憶體。
373-
link_title = null;
366+
} else {
367+
var link_title = [];
368+
if (prefix) {
369+
if (prefix.includes(':')) {
370+
link_title.push(prefix, ':');
371+
} else if (prefix !== session.language) {
372+
link_title.push(':', prefix, ':');
373+
}
374374

375-
// ------------------------------------------------
375+
} else if (!('localinterwiki' in interwikimap_data)) {
376+
// ↑ localinterwiki: 跨維基連結是否指向目前維基。
377+
// https://www.mediawiki.org/wiki/API:Siteinfo#Interwikimap
378+
if (interwikimap_data.language) {
379+
link_title.push(
380+
// 跨語言連結不加 ':' prefix 會被當成維基數據中的跨語言連結。
381+
':',
382+
// 在 adapt_site_configurations() 中已確保
383+
// interwikimap_data_list[0].prefix 與 url 一致,
384+
// 不必使用 interwiki_data.shortcut_prefix。
385+
interwiki_data.prefix);
386+
} else {
387+
link_title.push(interwiki_data.shortcut_prefix
388+
|| interwiki_data.prefix);
389+
}
390+
link_title.push(':');
391+
}
392+
link_title.push(name);
376393

377-
// display_text 別包含 prefix。
378-
interwiki_data.display_text = external_link && external_link[2] ? external_link[2]
379-
: name;
394+
link_title = link_title.join('');
395+
interwiki_data.link_title = link_title;
396+
}
397+
398+
// ------------------------------------------------
380399

381400
var search;
382401
if (!interwikimap_data.extract_url_data
383-
|| Object.keys(_url.search_params).join() !== 'title') {
402+
// 假如有其他參數,就不能轉換為純 wikilink。
403+
|| Object.keys(_url.search_params).join() !== 'title') {
384404
search = _url.search.replace(/^\?/, '');
385405
}
386-
if (search) {
406+
407+
if (!interwiki_data.link_title) {
408+
;
409+
410+
} else if (search) {
387411
// https://www.mediawiki.org/wiki/Help:Magic_words#URL_data
388412
// https://en.wikipedia.org/wiki/Help:Magic_words#Paths
389413
// TODO: use {{fullurl}}, e.g., [[w:ja:Template:利用者の投稿記録リンク]]
@@ -393,12 +417,11 @@ function module_code(library_namespace) {
393417
'}}' ].join('');
394418

395419
} else {
396-
interwiki_data.wikilink = [
397-
'[[',
398-
interwiki_data.link_title
399-
+ url_hash_to_section_title(_url.hash, {
400-
to_hash : true
401-
}) ];
420+
interwiki_data.wikilink = [ '[[', interwiki_data.link_title
421+
//
422+
+ url_hash_to_section_title(_url.hash, {
423+
to_hash : true
424+
}) ];
402425

403426
if (interwiki_data[1] !== interwiki_data.display_text.toString()) {
404427
interwiki_data.wikilink.push('|', interwiki_data.display_text);

application/net/wiki/parser/wikitext.js

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,14 @@ function module_code(library_namespace) {
381381
*
382382
* @see https://zh.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=protocols&utf8&format=json
383383
*/
384-
var PATTERN_external_link_global = new RegExp(
384+
var PATTERN_external_link = new RegExp(
385385
PATTERN_URL_WITH_PROTOCOL_GLOBAL.source
386386
// 允許 [//example.com/]
387387
.replace('):)', '):|\\/\\/)').replace(/^\([^()]+\)/, /\[/.source)
388-
+ /(?:([^\S\n]+)([^\]]*))?\]/.source, 'ig'),
388+
+ /(?:([^\S\n]+)([^\]]*))?\]/.source, 'i'),
389+
390+
PATTERN_external_link_global = new RegExp(PATTERN_external_link.source,
391+
'ig'),
389392

390393
// 若包含 br|hr| 會導致 "aa<br>\nbb</br>\ncc" 解析錯誤!
391394
/** {String}以"|"分開之 wiki tag name。 [[Help:Wiki markup]], HTML tags. 不包含 <a>! */
@@ -1362,7 +1365,7 @@ function module_code(library_namespace) {
13621365
previous = '';
13631366
}
13641367
library_namespace.debug(previous + ' + ' + parameters, 4,
1365-
'parse_wikitext.convert');
1368+
'parse_wikitext.language_conversion');
13661369

13671370
// console.log(parameters);
13681371

@@ -1658,10 +1661,28 @@ function module_code(library_namespace) {
16581661

16591662
}
16601663

1661-
// bare external link
1664+
// bare external link, parse_url()
16621665
// [[w:en:Wikipedia:Bare URLs]]
1663-
function parse_url(all, previous, URL) {
1666+
function parse_bare_url(all, previous, URL, protocol, URL_others) {
16641667
var following = '';
1668+
// protocol.length: 排除 IPv6 之類正規 URL。
1669+
var index = protocol.length;
1670+
var matched = URL_others.match(wiki_API.PATTERN_IPv6_prefix);
1671+
if (matched) {
1672+
index += matched[0].length;
1673+
}
1674+
index = URL.indexOf('[', index);
1675+
if (index !== NOT_FOUND) {
1676+
following = URL.slice(index)
1677+
// assert: following === ''
1678+
// + (following || '')
1679+
;
1680+
URL = URL.slice(0, index);
1681+
}
1682+
if (URL === protocol) {
1683+
return all;
1684+
}
1685+
16651686
if (URL.includes(include_mark)) {
16661687
// 內部包含{{!}}之類,需再進一步處理。
16671688

@@ -1682,9 +1703,7 @@ function module_code(library_namespace) {
16821703
return all;
16831704

16841705
following = URL.slice(invalid_token_data[0].length)
1685-
// assert: following === ''
1686-
// + (following || '')
1687-
;
1706+
+ following;
16881707
URL = invalid_token_data[0];
16891708
}
16901709

@@ -1708,10 +1727,46 @@ function module_code(library_namespace) {
17081727
function parse_external_link(all, URL, protocol, URL_others, delimiter,
17091728
parameters) {
17101729
// assert: all === URL + (delimiter || '') + (parameters || '')
1730+
// URL === protocol + URL_others
17111731

1712-
// including "'''". e.g., [http://a.b/''disply text'']
1713-
var matched = URL.match(/^(.+?)(''.*)$/);
1714-
if (matched) {
1732+
// [...] 自 end_mark 向前回溯。
1733+
var index = all.length,
1734+
// 在先的,在前的,前面的; preceding
1735+
// (previous 反義詞 following, preceding 反義詞 exceeds)
1736+
previous = '';
1737+
var matched;
1738+
// 1 + protocol.length: ('[' + protocol).length - 排除 IPv6 之類正規 URL。
1739+
while (index > 1 + protocol.length
1740+
&& (index = all.lastIndexOf('[', index - 1)) > 1 + protocol.length) {
1741+
matched = all.slice(index);
1742+
matched = matched.match(PATTERN_external_link);
1743+
if (!matched) {
1744+
continue;
1745+
}
1746+
1747+
// shift arguments
1748+
URL = matched[1];
1749+
protocol = matched[2];
1750+
URL_others = matched[3];
1751+
delimiter = matched[4];
1752+
parameters = matched[5];
1753+
previous = all.slice(0, matched.index);
1754+
// assert: previous.startsWith('[')
1755+
break;
1756+
}
1757+
library_namespace.debug(previous + ' + ' + URL + ' + '
1758+
+ (delimiter || '') + ' + ' + (parameters || ''), 4,
1759+
'parse_wikitext.external_link');
1760+
1761+
// 往後追溯,從不合理的字元開始算作 display text。
1762+
// "''" including "'''". e.g., [http://a.b/''disply text'']
1763+
1764+
// 排除 IPv6 之類正規 URL。
1765+
if (wiki_API.PATTERN_IPv6_prefix.test(URL_others)) {
1766+
;
1767+
}
1768+
matched = URL.match(/^(.+?)((?:\[|'').*)$/);
1769+
if (matched && !wiki_API.PATTERN_IPv6_prefix.test(URL_others)) {
17151770
// assert: 本階段尚未執行過 split_text_apostrophe_unit(wikitext)
17161771
URL = matched[1];
17171772
if (delimiter) {
@@ -1883,7 +1938,7 @@ function module_code(library_namespace) {
18831938

18841939
_set_wiki_type(URL, 'external_link');
18851940
queue.push(URL);
1886-
return include_mark + (queue.length - 1) + end_mark;
1941+
return previous + include_mark + (queue.length - 1) + end_mark;
18871942
}
18881943

18891944
function parse_odd_external_link(all, URL, delimiter, parameters) {
@@ -5046,7 +5101,7 @@ function module_code(library_namespace) {
50465101
if (!options.inside_url) {
50475102
// 不可跨行。
50485103
wikitext = wikitext.replace_till_stable(
5049-
PATTERN_URL_WITH_PROTOCOL_GLOBAL, parse_url);
5104+
PATTERN_URL_WITH_PROTOCOL_GLOBAL, parse_bare_url);
50505105
}
50515106

50525107
// ----------------------------------------------------

0 commit comments

Comments
 (0)