跳转到内容

模組:Special wikitext/JSON

被永久保护的模块
维基百科,自由的百科全书

这是本页的一个历史版本,由A2569875留言 | 贡献2021年5月15日 (六) 08:25编辑。这可能和当前版本存在着巨大的差异。

local p={}
function _preEsc(str)
	return mw.ustring.gsub(str,'%$',"$$")
end
function _postEsc(str)
	if not mw.ustring.find(tostring(str),'%$') then return str end
	return mw.ustring.gsub(str,'%$%$',"$")
end
function _wikiescape(str)
	local escape_list = {
		{'#','#'}, {'%{','{'}, {'%|','|'}, {'%}','}'}, {'=','='}, {':',':'}, {'%[','['}, {'%]',']'}
	}
	str = mw.text.encode( str )
	for i=1,#escape_list do str = mw.ustring.gsub(str,escape_list[i][1],escape_list[i][2]) end
	return str
end
function _handleNull(input_string)
	local JSON_syms = ",:%[%]%{%}"
	local head_patterns,tail_patterns = {'^(%s*)','(['..JSON_syms..']%s*)'},{'(%s*)$','(%s*['..JSON_syms..'])'}
	local result = input_string
	for i=1,#head_patterns do
		for j=1,#tail_patterns do
			result = mw.ustring.gsub(result,head_patterns[i].."null"..tail_patterns[j],function(head,tail)
				local p_head,p_tail = mw.text.trim(head),mw.text.trim(tail)
				local body = (p_tail==':'and''or'$').."null"
				return head..'"'..body..'"'..tail
			end)
		end
	end
	return result
end
function _renderHide(value)
	return '<span style="display:none">'..value..'</span>'
end
function _renderKeyString(value)
	return _renderHide('"').._wikiescape(value).._renderHide('"')
end
function _renderKey(value)
	return _renderKeyString(value).._renderHide(':')
end
function _rendeString(value)
	return '"'.._wikiescape(value)..'"'
end
function _rendeValue(value)
	if _isNumber(value) then return tostring(value) end
	if value=='$null' then return 'null' end
	if type(value)==type(true) then return value and 'true' or 'false' end
	return _rendeString(_postEsc(value))
end
function _renderTerminal(key, value, is_array, is_tail, sing_value)
	local body = '<tr>'
	..((key and not is_array) and ('<th><span>'.._renderKey(key)..'</span></th>') or '')
	..'<td'..(sing_value and''or' class="mw-json-value"')..'>'.._rendeValue(value)..((not is_tail) and _renderHide(',') or '')..'</td></tr>'
	return body
end

function _renderItem(key, item, is_array, is_tail)
	local body = '<tr>'
	..((key and not is_array) and ('<th><span>'.._renderKey(key)..'</span></th>') or '')
	..'<td>'..item..((not is_tail) and _renderHide(',') or '')..'</td></tr>'
	return body
end
function _isNumber(obj)
	return tonumber(obj) ~= nil and tostring(tonumber(obj)) == tostring(obj)
end
function _isArray(obj)
	local min_val, max_val
	if obj == nil or type(obj) ~= type({}) then return false end
	if (obj[0] or '') == '$array' then return true end
	for k,v in pairs(obj) do
		if _isNumber(k) then
			local key = tonumber(k)
			if min_val==nil then min_val = key end
			if max_val==nil then max_val = key end
			if min_val > key  then min_val = key end
			if max_val < key  then max_val = key end
		else return false end
	end
	return (min_val or -1) > 0 and #obj==(max_val or -1)
end
function _jsonDecode(str)
	local preJSON = _preEsc(str)
	preJSON = _handleNull(preJSON)
	preJSON = mw.ustring.gsub(preJSON,'%[','["$array",')
	preJSON = mw.ustring.gsub(preJSON,",%s*([%]%}])","%1")
	return mw.text.jsonDecode(preJSON, mw.text.JSON_PRESERVE_KEYS + mw.text.JSON_TRY_FIXING)
end
function p.JSONTable(frame)
	local json_data = mw.text.killMarkers( mw.text.unstripNoWiki( frame.args[1] or frame.args['1'] or '' ) )
	local body = ''
	xpcall(function()
		body=p._json_reader(_jsonDecode(json_data))
	end,function()
		body = mw.getCurrentFrame():preprocess(mw.text.decode(json_data))
	end)
	return body
end

function p._json_reader(json)
	local sing_value = type(json) == type('') or type(json) == type(0) or type(json) == type(true)
	--見lua說明,遞迴的變數行為可能會導致遞迴失效,因此用堆疊實現
	local json_stack={{result='',obj={}}}
	json_stack[#json_stack + 1] = {obj=json,parent=json_stack[1],state='start',root=true,result='',is_last=true}
	while(#json_stack > 1)do
		local top = json_stack[#json_stack]
		if _isArray(top.obj) then
			if top.state == 'start'then
				top.state = 'process'
				top.cap = true
				local last_key
				for i=1,#top.obj do
					last_key = i
					json_stack[#json_stack + 1] = {
						obj = top.obj[i], key = i, parent=top, state='start',result='',type='array_item'
					}
				end
				if last_key ~= nil then json_stack[#json_stack].is_last = true end
			elseif top.state == 'process' then
				json_stack[#json_stack] = nil--pop
				local peek = top.parent
				local top_result = top.result
				if mw.text.trim(top_result) == '' then top_result ='<td class="mw-json-empty"><span class="mw-json-empty-array"></span></td>'end
				local result_data = _renderHide('[')..'<table class="mw-json">'..top_result..'</table>'.. _renderHide(']')
				local is_array = top.key and not (top.type=='array_item')
				result_data = _renderItem(_postEsc(top.key), result_data, top.type=='array_item', top.is_last)
				peek.result=result_data..peek.result
			end
		elseif type(top.obj) == type({}) then
			if top.state == 'start'then
				top.state = 'process'
				top.cap = true
				local last_key
				for k,v in pairs(top.obj) do
					last_key = k
					json_stack[#json_stack + 1] = {
						obj = v, key = k, parent=top, state='start',result='',type='obj_item'
					}
				end
				if last_key ~= nil then json_stack[#json_stack].is_last = true end
			elseif top.state == 'process' then
				json_stack[#json_stack] = nil--pop
				local peek = top.parent
				local top_result = top.result
				if mw.text.trim(top_result) == '' then top_result ='<td class="mw-json-empty"><span class="mw-json-empty-object"></span></td>'end
				local result_data = _renderHide('{')..'<table class="mw-json">'..top_result..'</table>'.._renderHide('}')
				local is_array = top.key and not (top.type=='array_item')
				local result_data = _renderItem(_postEsc(top.key), result_data, top.type=='array_item', top.is_last)
				peek.result=result_data..peek.result
			end
		else
			json_stack[#json_stack] = nil--pop
			local peek = top.parent
			local result_data = (type(top.obj) == type(0) or type(top.obj) == type(true) or type(top.obj) == type(''))and 
				_renderTerminal(_postEsc(top.key), top.obj, top.type=='array_item', top.is_last, sing_value) or ''
			peek.result=top.result..result_data..peek.result
		end
	end
	local class_list = {}
	if _isArray(json) or sing_value then class_list[#class_list+1]="mw-json" end
	if sing_value then class_list[#class_list+1]="mw-json-single-value" end
	class_list = table.concat(class_list, ' ')
	local outer_class = (mw.text.trim(class_list)~='') and 'class="'..class_list..'"' or ''
	return '<table '..outer_class..'>' .. json_stack[1].result .. '</table>'
end
return p