跳转到内容

模組:ZhConversion/sandbox

本页使用了标题或全文手工转换
维基百科,自由的百科全书
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