模組:ZhConversion/sandbox
外观
| 这是Module:ZhConversion(差异)的沙盒。 |
| 此模块沙盒被引用於許多頁面。 為了避免造成大規模的影響,所有對此模块沙盒的編輯應先於沙盒或測試樣例上測試。实施修改前,建议先在討論頁發起討論。 模板引用數量會自動更新。 |
本模組可以在Lua階段實現文字的繁簡轉換,但不支援地區詞轉換和轉換組。(ZhConversion.php的Lua實現)
原始文字 本文为测试文字。这段文字用于说明格式,请勿删除。这段文字用于说明格式,请勿删除。海纳百川,有容乃大。维基百科,自由的百科全书。
- 輸入到to_hant()函數變為(轉為繁體):
本文為測試文字。這段文字用於說明格式,請勿刪除。這段文字用於說明格式,請勿刪除。海納百川,有容乃大。維基百科,自由的百科全書。
原始文字 本文為測試文字。這段文字用於說明格式,請勿刪除。這段文字用於說明格式,請勿刪除。海納百川,有容乃大。維基百科,自由的百科全書。
- 輸入到to_hans()函數變為(轉為簡體):
本文为测试文字。这段文字用于说明格式,请勿删除。这段文字用于说明格式,请勿删除。海纳百川,有容乃大。维基百科,自由的百科全书。
原始文字繁體简体(繁簡混用)
- 輸入到to_hant()函數變為(轉為繁體):
繁體簡體
原始文字繁體简体(繁簡混用)
- 輸入到to_hans()函數變為(轉為簡體):
繁体简体
用途
[编辑]標題轉換
[编辑]在魔術字和Lua的場合中,頁面標題的繁簡差異是無法被識別的,例如{{PAGESIZE:}}。以光泽 (矿物)為例,頁面光泽 (矿物)存在而頁面光澤 (礦物)不存在由系統自動轉換標題差異,這時:
{{PAGESIZE:光泽 (矿物)}}→「10,163」{{PAGESIZE:光澤 (礦物)}}→「0」
對於輸入的標題同時,-{}-和<langconvert></langconvert>等轉換語法在模板及模組階段是不工作的:
<langconvert from="zh-hans" to="zh-hant">光泽 (矿物)</langconvert>→「光澤 (礦物)」{{PAGESIZE:<langconvert from="zh-hans" to="zh-hant">光泽 (矿物)</langconvert>}}→「0」
<langconvert from="zh-hant" to="zh-hans">光澤 (礦物)</langconvert>→「光泽 (矿物)」{{PAGESIZE:<langconvert from="zh-hant" to="zh-hans">光澤 (礦物)</langconvert>}}→「0」
這意味著,如果存在的頁面是光泽 (矿物),輸入光澤 (礦物)到模板或模組中有關功能是會失效的。
所以如果輸入的值是光澤 (礦物)就有在Lua運算階段需使用繁簡轉換的需求。
{{PAGESIZE:{{#invoke:ZhConversion|zh_title|光泽 (矿物)}}}}→「10,163」{{PAGESIZE:{{#invoke:ZhConversion|zh_title|光澤 (礦物)}}}}→「10,163」
函數說明
[编辑]- to_hant(字串)
- 輸入一個字串,轉換為繁體中文(可模板調用)
- to_hans(字串)
- 輸入一個字串,轉換為簡體中文(可模板調用)
- zh_convert(字串)
- 輸入一個字串,進行繁簡轉換,若輸入是簡體,轉換為繁體;若輸入是繁體,轉換為簡體;若繁簡混用,以繁體優先。(可模板調用)
- zh_title(頁面標題)
- 輸入一個頁面標題,若該頁面標題頁面不存在,但繁簡轉換後存在,則返回存在的標題。(可模板調用)
- equals(字串1,字串2)
- 忽略繁簡差異的文字比對,例如「光泽」與「光澤」視為相同。(可模板調用)
- _language_cvt(字串, 轉換表, 子字串搜尋最大長度)
- 使用指定的轉換表進行轉換。(不支援模板調用)
參見
[编辑]- Module:ZhConversion/data:本模組的轉換表
local p={}
-- Manual CPU profiling. Timings accumulate until resetProfile() is called.
local profile = {}
local profileOrder = {}
local function recordTime(name, started, bytes, candidates, matches)
local elapsed = os.clock() - started
local entry = profile[name]
if not entry then
entry = { calls = 0, seconds = 0, bytes = 0, candidates = 0, matches = 0 }
profile[name] = entry
profileOrder[#profileOrder + 1] = name
end
entry.calls = entry.calls + 1
entry.seconds = entry.seconds + elapsed
entry.bytes = entry.bytes + (bytes or 0)
entry.candidates = entry.candidates + (candidates or 0)
entry.matches = entry.matches + (matches or 0)
end
function p.resetProfile()
profile = {}
profileOrder = {}
end
function p.dumpProfile()
local lines = {
'ZhConversion CPU profile (inclusive totals; rows may overlap):',
'stage | calls | total ms | mean ms | input bytes | candidates | matches'
}
for _, name in ipairs(profileOrder) do
local entry = profile[name]
lines[#lines + 1] = string.format(
'%s | %d | %.3f | %.3f | %d | %d | %d', name, entry.calls,
entry.seconds * 1000, entry.seconds * 1000 / entry.calls,
entry.bytes, entry.candidates, entry.matches
)
end
local report = table.concat(lines, '\n')
mw.log(report)
return report
end
-- Template callers can add |profile=1 to append the timings after the result.
local function finish(input, result)
if type(input) == 'table' and input.args and input.args.profile == '1' then
return tostring(result) .. '\n<pre>' .. mw.text.encode(p.dumpProfile()) .. '</pre>'
end
return result
end
local loadStarted = os.clock()
local zhcvt = mw.loadData('Module:ZhConversion/data')
recordTime('loadData', loadStarted)
-- Build this only for long inputs, and only for the read-only loadData tables.
-- Custom tables passed to _language_cvt may be mutated, so they use the normal scan.
local multiStartCache = setmetatable({}, { __mode = 'k' })
local function getMultiStarts(cvt_table)
local starts = multiStartCache[cvt_table]
if starts then return starts end
local started = os.clock()
starts = {}
for key in pairs(cvt_table) do
local first = string.byte(key)
local width = first < 0x80 and 1 or first < 0xe0 and 2 or
first < 0xf0 and 3 or 4
if #key > width then
starts[string.sub(key, 1, width)] = true
end
end
multiStartCache[cvt_table] = starts
recordTime('build multi-start index', started)
return starts
end
function p.to_hant(str)
local input_str = str
if type(str) == type({table}) then
input_str = (str.args or {})[1] or str[1] or ''
elseif type(str) ~= type(string) then
input_str = tostring(str)
end
return finish(str, p._language_cvt(input_str, zhcvt.to_hant, zhcvt.max_length))
end
function p.to_hans(str)
local input_str = str
if type(str) == type({table}) then
input_str = (str.args or {})[1] or str[1] or ''
elseif type(str) ~= type(string) then
input_str = tostring(str)
end
return finish(str, p._language_cvt(input_str, zhcvt.to_hans, zhcvt.max_length))
end
function p.zh_title(str)
local input_str = str
if type(str) == type({table}) then
input_str = (str.args or {})[1] or str[1] or ''
elseif type(str) ~= type(string) then
input_str = tostring(str)
end
local checked_title = input_str
local titleStarted = os.clock()
xpcall(function()
local function getTitle(...)
local started = os.clock()
local success, titleObj = pcall(mw.title.new, ...)
recordTime('create title', started)
if success then return titleObj else return nil end
end
local function getContent(titleObj)
if not titleObj then return nil end
local started = os.clock()
local content = titleObj:getContent()
recordTime('get title content', started)
return content
end
local titleObj = getTitle(input_str)
local ori_code = getContent(titleObj)
if ori_code then checked_title = input_str else
local zh_hant = p.to_hant(input_str)
local titleHantObj = getTitle(zh_hant)
local hant_code = getContent(titleHantObj)
if hant_code then checked_title = zh_hant else
local zh_hans = p.to_hans(input_str)
local titleHansObj = getTitle(zh_hans)
local hans_code = getContent(titleHansObj)
if hans_code then checked_title = zh_hans end
end
end
end,function()end)
recordTime('zh_title total', titleStarted, #input_str)
return finish(str, checked_title)
end
function p.equals(str1, str2)
local input_str1 = str1
local input_str2 = str2
if type(str1) == type({table}) then
input_str1 = (str1.args or {})[1] or str1[1] or ''
input_str2 = (str1.args or {})[2] or str1[2] or ''
elseif type(str1) ~= type(string) then
input_str1 = tostring(str1)
input_str2 = tostring(str2)
end
local result = false
if input_str1 == input_str2 then
result = true
else
local str1_hans = p._language_cvt(input_str1, zhcvt.to_hans, zhcvt.max_length)
local str2_hans = p._language_cvt(input_str2, zhcvt.to_hans, zhcvt.max_length)
if str1_hans == str2_hans or input_str1 == str2_hans or str1_hans == input_str2 then
result = true
else
local str1_hant = p._language_cvt(input_str1, zhcvt.to_hant, zhcvt.max_length)
local str2_hant = p._language_cvt(input_str2, zhcvt.to_hant, zhcvt.max_length)
if str1_hant == str2_hant or input_str1 == str2_hant or str1_hant == input_str2 then
result = true
end
end
end
if str1 == mw.getCurrentFrame() or (type(str1)==type({}) and type(str1.getParent)==type(function()end)) then
return finish(str1, result and '1' or '')
end
return finish(str1, result)
end
function p.zh_convert(str)
local input_str = str
if type(str) == type({table}) then
input_str = (str.args or {})[1] or str[1] or ''
elseif type(str) ~= type(string) then
input_str = tostring(str)
end
local result = p._language_cvt(input_str, zhcvt.to_hant, zhcvt.max_length)
if result == input_str then
result = p._language_cvt(input_str, zhcvt.to_hans, zhcvt.max_length)
end
return finish(str, result)
end
function p._language_cvt(str, cvt_table, max_length)
local totalStarted = os.clock()
local started = os.clock()
-- Build UTF-8 character boundaries without creating a PHP code-point array.
-- Validate continuations, overlong forms, surrogates and out-of-range values.
local byteStarts = {}
local bytePos = 1
local byteLength = #str
if mw.ustring.maxStringLength and byteLength > mw.ustring.maxStringLength then
error("bad argument #1 to 'gcodepoint' (string is longer than " ..
mw.ustring.maxStringLength .. ' bytes)', 2)
end
while bytePos <= byteLength do
local first = string.byte(str, bytePos)
local width = 1
if first >= 0xc2 and first <= 0xdf then
width = 2
elseif first >= 0xe0 and first <= 0xef then
width = 3
elseif first >= 0xf0 and first <= 0xf4 then
width = 4
elseif first >= 0x80 then
error("bad argument #1 to 'gcodepoint' (string is not UTF-8)", 2)
end
if width > 1 then
local second, third, fourth = string.byte(str, bytePos + 1, bytePos + width - 1)
if not second or second < 0x80 or second > 0xbf or
width >= 3 and (not third or third < 0x80 or third > 0xbf) or
width == 4 and (not fourth or fourth < 0x80 or fourth > 0xbf) or
first == 0xe0 and second < 0xa0 or
first == 0xed and second > 0x9f or
first == 0xf0 and second < 0x90 or
first == 0xf4 and second > 0x8f then
error("bad argument #1 to 'gcodepoint' (string is not UTF-8)", 2)
end
end
byteStarts[#byteStarts + 1] = bytePos
bytePos = bytePos + width
end
byteStarts[#byteStarts + 1] = bytePos
recordTime('index UTF-8', started, #str)
local multiStarts = multiStartCache[cvt_table]
if not multiStarts and byteLength >= 4096 and max_length > 1 and
(cvt_table == zhcvt.to_hant or cvt_table == zhcvt.to_hans) then
multiStarts = getMultiStarts(cvt_table)
end
started = os.clock()
local result = {}
local strlen = #byteStarts - 1
local i=1
local candidates = 0
local matches = 0
while i <= strlen do
local firstChar = string.sub(str, byteStarts[i], byteStarts[i + 1] - 1)
local replacement, matchedLength
if multiStarts and not multiStarts[firstChar] then
candidates = candidates + 1
replacement = cvt_table[firstChar]
if replacement then matchedLength = 1 end
else
for length = max_length, 1, -1 do
if i + length - 1 <= strlen then
candidates = candidates + 1
local check_str = string.sub(str, byteStarts[i], byteStarts[i + length] - 1)
replacement = cvt_table[check_str]
if replacement then
matchedLength = length
break
end
end
end
end
if matchedLength then
matches = matches + 1
result[#result + 1] = replacement
i = i + matchedLength
else
result[#result + 1] = firstChar
i = i + 1
end
end
recordTime('scan dictionary', started, #str, candidates, matches)
started = os.clock()
local converted = table.concat(result)
recordTime('join result', started, #str)
recordTime('convert total', totalStarted, #str)
return converted
end
return p