Módulo:JSONutil

Fonte: Enciclopédia de conhecimento da Igreja de Deus
Saltar para a navegação Saltar para a pesquisa

A documentação para este módulo pode ser criada na página Módulo:JSONutil/doc

local JSONutil = { suite  = "JSONutil",
																			serial = "2020-11-08",
																			item   = 63869449 }
--[=[
preprocess or generate JSON data
]=]
local Failsafe = JSONutil
JSONutil.Encoder  = { stab   = string.char( 9 ),
																						sep    = string.char( 10 ),
																						scream = "@error@JSONencoder@" }
JSONutil.more = 50    -- length of trailing context
local Fallback = function ()
				-- Retrieve current default language code
				--     Returns  string
				return mw.language.getContentLanguage():getCode()
																																											:lower()
end -- Fallback()
local flat = function ( adjust )
				-- Clean template argument string
				-- Parameter:
				--     adjust  -- string, or not
				-- Returns:
				--     string
				local r
				if adjust then
								r = mw.text.trim( mw.text.unstripNoWiki( adjust ) )
				else
								r = ""
				end
				return r
end -- flat()
local flip = function ( frame )
				-- Retrieve template argument indent
				-- Parameter:
				--     frame  -- object
				-- Returns:
				--     number, of indentation level, or not
				local r
				if frame.args.indent  and  frame.args.indent:match( "^%d+$" ) then
								r = tonumber( frame.args.indent )
				end
				return r
end -- flip()
JSONutil.Encoder.Array = function ( apply, adapt, alert )
				-- Convert table to JSON Array
				-- Parameter:
				--     apply  -- table, with sequence of raw elements, or
				--               string, with formatted Array, or empty
				--     adapt  -- string, with requested type, or not
				--     alert  -- true, if non-numeric elements shall trigger errors
				-- Returns:
				--     string, with JSON Array
				local r = type( apply )
				if r == "string" then
								r = mw.text.trim( apply )
								if r == "" then
												r = "[]"
								elseif r:byte( 1, 1 ) ~= 0x5B  or
															r:byte( -1, -1 ) ~= 0x5D then
												r = false
								end
				elseif r == "table" then
								local n = 0
								local strange
								for k, v in pairs( apply ) do
												if type( k ) == "number" then
																if k > n then
																				n = k
																end
												elseif alert then
																if strange then
																				strange = strange .. " "
																else
																				strange = ""
																end
																strange = strange .. tostring( k )
												end
								end -- for k, v
								if strange then
												r = string.format( "{ \"%s\": \"%s\" }",
																															JSONutil.Encoder.scream,
																															JSONutil.Encoder.string( strange ) )
								elseif n > 0 then
												local sep   = ""
												local scope = adapt or "string"
												local s
												if type( JSONutil.Encoder[ scope ] ) ~= "function" then
																scope = "string"
												end
												r = " ]"
												for i = n, 1, -1 do
																s = JSONutil.Encoder[ scope ]( apply[ i ] )
																r = string.format( "%s%s%s", s, sep, r )
																sep = ",\n  "
												end -- for i = n, 1, -1
												r = "[ " .. r
								else
												r = "[]"
								end
				else
								r = false
				end
				if not r then
								r = string.format( "[ \"%s * %s\" ]",
																											JSONutil.Encoder.scream,
																											"Bad Array" )
				end
				return r
end -- JSONutil.Encoder.Array()
JSONutil.Encoder.boolean = function ( apply )
				-- Convert string to JSON boolean
				-- Parameter:
				--     apply  -- string, with value
				-- Returns:
				--     boolean as string
				local r = mw.text.trim( apply )
				if r == ""  or  r == "null"  or  r == "false"
							or  r == "0"  or  r == "-" then
								r = "false"
				else
								r = "true"
				end
				return r
end -- JSONutil.Encoder.boolean()
JSONutil.Encoder.Component = function ( access, apply,
																																								adapt, align, alert )
				-- Create single entry for mapping object
				-- Parameter:
				--     access  -- string, with component name
				--     apply   -- component value
				--     adapt   -- string, with value type, or not
				--     align   -- number, of indentation level, or not
				--     alert   --
				-- Returns:
				--     string, with JSON fragment, and comma
				local v     = apply
				local types = adapt
				local indent, liner, scope, sep, sign
				if type( access ) == "string" then
								sign = mw.text.trim( access )
								if sign == "" then
												sign = false
								end
				end
				if type( types ) == "string" then
								types = mw.text.split( mw.text.trim( types ),  "%s+" )
				end
				if type( types ) ~= "table" then
								types = { }
								table.insert( types, "string" )
				end
				if #types == 1 then
								scope = types[ 1 ]
				else
								for i = 1, #types do
												if types[ i ] == "boolean" then
																if v == "1"  or  v == 1  or  v == true then
																				v = "true"
																				scope = "boolean"
																elseif v == "0"  or  v == 0  or  v == false then
																				v = "false"
																				scope = "boolean"
																end
																if scope then
																				types = { }
																				break    -- for i
																else
																				table.remove( types, i )
																end
												end
								end   -- for i
								for i = 1, #types do
												if types[ i ] == "number" then
																if tonumber( v ) then
																				v     = tostring( v )
																				scope = "number"
																				types = { }
																				break    -- for i
																else
																				table.remove( types, i )
																end
												end
								end    -- for i
				end
				scope = scope or "string"
				if type( JSONutil.Encoder[ scope ] ) ~= "function" then
								scope = "string"
				elseif scope == "I18N" then
								scope = "Polyglott"
				end
				if scope == "string" then
								v = v or ""
				end
				if type( align ) == "number"  and  align > 0 then
								indent = math.floor( align )
								if indent == 0 then
												indent = false
								end
				end
				if scope == "object"  or  not sign then
								liner = true
				elseif scope == "string" then
								local k = mw.ustring.len( sign ) + mw.ustring.len( v )
								if k > 60 then
												liner = true
								end
				end
				if liner then
								if indent then
												sep = "\n" .. string.rep( "  ", indent )
								else
												sep = "\n"
								end
				else
								sep = " "
				end
				if indent then
								indent = indent + 1
				end
				return  string.format( " \"%s\":%s%s,\n",
																											sign or "???",
																											sep,
																											JSONutil.Encoder[ scope ]( v, indent ) )
end -- JSONutil.Encoder.Component()
JSONutil.Encoder.Hash = function ( apply, adapt, alert )
				-- Create entries for mapping object
				-- Parameter:
				--     apply  -- table, with element value assignments
				--     adapt  -- table, with value types assignment, or not
				-- Returns:
				--     string, with JSON fragment, and comma
				local r = ""
				local s
				for k, v in pairs( apply ) do
								if type( adapt ) == "table" then
												s = adapt[ k ]
								end
								r = r .. JSONutil.Encoder.Component( tostring( k ), v, s )
				end -- for k, v
				return
end -- JSONutil.Encoder.Hash()
JSONutil.Encoder.I18N = function ( apply, align )
				-- Convert multilingual string table to JSON
				-- Parameter:
				--     apply  -- table, with mapping object
				--     align  -- number, of indentation level, or not
				-- Returns:
				--     string, with JSON object
				local r = type( apply )
				if r == "table" then
								local strange
								local fault = function ( a )
																		if strange then
																						strange = strange .. " *\n "
																		else
																						strange = ""
																		end
																		strange = strange .. a
														end
								local got, sep, indent
								for k, v in pairs( apply ) do
												if type( k ) == "string" then
																k = mw.text.trim( k )
																if type( v ) == "string" then
																				v = mw.text.trim( v )
																				if v == "" then
																								fault( string.format( "%s %s=",
																																														"Empty text", k ) )
																				end
																				if not ( k:match( "%l%l%l?" )  or
																													k:match( "%l%l%l?-%u%u" )  or
																													k:match( "%l%l%l?-%u%l%l%l+" ) ) then
																								fault( string.format( "%s %s=",
																																														"Strange language code",
																																														k ) )
																				end
																else
																				v = tostring( v )
																				fault( string.format( "%s %s=%s",
																																										"Bad type for text",
																																										k,
																																										type( v ) ) )
																end
																got = got  or  { }
																got[ k ] = v
												else
																fault( string.format( "%s %s: %s",
																																						"Bad language code type",
																																						type( k ),
																																						tostring( k ) ) )
												end
								end -- for k, v
								if not got then
												fault( "No language codes" )
												got = { }
								end
								if strange then
												got[ JSONutil.Encoder.scream ] = strange
								end
								r = false
								if type( align ) == "number"  and  align > 0 then
												indent = math.floor( align ) 
								else
												indent = 0
								end
								sep = string.rep( "  ",  indent + 1 ) 
								for k, v in pairs( got ) do
												if r then
																r = r .. ",\n"
												else
																r = ""
												end
												r = string.format( "%s  %s%s: %s",
																															r,
																															sep,
																															JSONutil.Encoder.string( k ),
																															JSONutil.Encoder.string( v ) )
								end -- for k, v
								r = string.format( "{\n%s\n%s}", r, sep )
				elseif r == "string" then
								r = JSONutil.Encoder.string( apply )
				else
								r = string.format( "{ \"%s\": \"%s: %s\" }",
																											JSONutil.Encoder.scream,
																											"Bad Lua type",
																											r )
				end
				return r
end -- JSONutil.Encoder.I18N()
JSONutil.Encoder.number = function ( apply )
				-- Convert string to JSON number
				-- Parameter:
				--     apply  -- string, with presumable number
				-- Returns:
				--     number, or "NaN"
				local s = mw.text.trim( apply )
				JSONutil.Encoder.minus = JSONutil.Encoder.minus  or
																													mw.ustring.char( 0x2212 )
				s = s:gsub( JSONutil.Encoder.minus, "-" )
				return tonumber( s:lower() )  or  "NaN"
end -- JSONutil.Encoder.number()
JSONutil.Encoder.object = function ( apply, align )
				-- Create mapping object
				-- Parameter:
				--     apply  -- string, with components, may end with comma
				--     align  -- number, of indentation level, or not
				-- Returns:
				--     string, with JSON fragment
				local story = mw.text.trim( apply )
				local start = ""
				if story:sub( -1 ) == "," then
								story = story:sub( 1, -2 )
				end
				if type( align ) == "number"  and  align > 0 then
								local indent = math.floor( align )
								if indent > 0 then
												start = string.rep( "  ", indent )
								end
				end
				return  string.format( "%s{ %s\n%s}", start, story, start )
end -- JSONutil.Encoder.object()
JSONutil.Encoder.Polyglott = function ( apply, align )
				-- Convert string or multilingual string table to JSON
				-- Parameter:
				--     apply  -- string, with string or object
				--     align  -- number, of indentation level, or not
				-- Returns:
				--     string
				local r = type( apply )
				if r == "string" then
								r = mw.text.trim( apply )
								if not r:match( "^{%s*\"" )  or
											not r:match( "\"%s*}$" ) then
												r = JSONutil.Encoder.string( r )
								end
				else
								r = string.format( "{ \"%s\": \"%s: %s\" }",
																											JSONutil.Encoder.scream,
																											"Bad Lua type",
																											r )
				end
				return r
end -- JSONutil.Encoder.Polyglott()
JSONutil.Encoder.string = function ( apply )
				-- Convert plain string to strict JSON string
				-- Parameter:
				--     apply  -- string, with plain string
				-- Returns:
				--     string, with quoted trimmed JSON string
				return  string.format( "\"%s\"",
																											mw.text.trim( apply )
																																		:gsub( "\\",  "\\\\" )
																																		:gsub( "\"",  "\\\"" )
																																		:gsub( JSONutil.Encoder.sep,  "\\n" )
																																		:gsub( JSONutil.Encoder.stab, "\\t" ) )
end -- JSONutil.Encoder.string()
JSONutil.fair = function ( apply )
				-- Reduce enhanced JSON data to strict JSON
				-- Parameter:
				--     apply  -- string, with enhanced JSON
				-- Returns:
				--     1    -- string|nil|false, with error keyword
				--     2    -- string, with JSON or context
				local m   = 0
				local n   = 0
				local s   = mw.text.trim( apply )
				local i, j, last, r, scan, sep0, sep1, start, stub, suffix
				local framework = function ( a )
																														-- syntax analysis outside strings
																														local k = 1
																														local c
																														while k do
																																		k = a:find( "[{%[%]}]", k )
																																		if k then
																																						c = a:byte( k, k )
																																						if c == 0x7B then    -- {
																																										m = m + 1
																																						elseif c == 0x7D then    -- }
																																										m = m - 1
																																						elseif c == 0x5B then    -- [
																																										n = n + 1
																																						else    -- ]
																																										n = n - 1
																																						end
																																						k = k + 1
																																		end
																														end   -- while k
																						end    -- framework()
				local free = function ( a, at, f )
																					-- Throws: error if /* is not matched by */
																					local s = a
																					local i = s:find( "//", at, true )
																					local k = s:find( "/*", at, true )
																					if i or k then
																									local m = s:find( sep0, at )
																									if i   and   ( not m  or  i < m ) then
																													k = s:find( "\n",  i + 2,  true )
																													if k then
																																	if i == 1 then
																																					s = s:sub( k + 1 )
																																	else
																																					s = s:sub( 1,  i - 1 )   ..
																																									s:sub( k + 1 )
																																	end
																													elseif i > 1 then
																																	s = s:sub( 1,  i - 1 )
																													else
																																	s = ""
																													end
																									elseif k   and   ( not m  or  k < m ) then
																													i = s:find( "*/",  k + 2,  true )
																													if i then
																																	if k == 1 then
																																					s = s:sub( i + 2 )
																																	else
																																					s = s:sub( 1,  k - 1 )   ..
																																									s:sub( i + 2 )
																																	end
																													else
																																	error( s:sub( k + 2 ), 0 )
																													end
																													i = k
																									else
																													i = false
																									end
																									if i then
																													s = mw.text.trim( s )
																													if s:find( "/", 1, true ) then
																																	s = f( s, i, f )
																													end
																									end
																					end
																					return s
																	end    -- free()
				if s:sub( 1, 1 ) == '{' then
								s    = s:gsub( string.char( 13, 10 ),  JSONutil.Encoder.sep )
																:gsub( string.char( 13 ),  JSONutil.Encoder.sep )
								stub = s:gsub( JSONutil.Encoder.sep, "" )
																:gsub( JSONutil.Encoder.stab, "" )
								scan = string.char( 0x5B, 0x01, 0x2D, 0x1F, 0x5D )    -- [ \-\ ]
								j    = stub:find( scan )
								if j then
												r = "ControlChar"
												s = mw.text.trim( s:sub( j + 1 ) )
												s = mw.ustring.sub( s, 1, JSONutil.more )
								else
												i    = true
												j    = 1
												last = ( stub:sub( -1 ) == "}" )
												sep0 = string.char( 0x5B, 0x22, 0x27, 0x5D )    -- [ " ' ]
												sep1 = string.char( 0x5B, 0x5C, 0x22, 0x5D )    -- [ \ " ]
								end
				else
								r = "Bracket0"
								s = mw.ustring.sub( s, 1, JSONutil.more )
				end
				while i do
								i, s = pcall( free, s, j, free )
								if i then
												i = s:find( sep0, j )
								else
												r = "CommentEnd"
												s = mw.text.trim( s )
												s = mw.ustring.sub( s, 1, JSONutil.more )
								end
								if i then
												if j == 1 then
																framework( s:sub( 1, i - 1 ) )
												end
												if s:sub( i, i ) == '"' then
																stub = s:sub( j,  i - 1 )
																if stub:find( '[^"]*,%s*[%]}]' ) then
																				r = "CommaEnd"
																				s = mw.text.trim( stub )
																				s = mw.ustring.sub( s, 1, JSONutil.more )
																				i = false
																				j = false
																else
																				if j > 1 then
																								framework( stub )
																				end
																				i = i + 1
																				j = i
																end
																while j do
																				j = s:find( sep1, j )
																				if j then
																								if s:sub( j, j ) == '"' then
																												start  = s:sub( 1,  i - 1 )
																												suffix = s:sub( j )
																												if j > i then
																																stub = s:sub( i,  j - 1 )
																																								:gsub( JSONutil.Encoder.sep,
																																															"\\n" )
																																								:gsub( JSONutil.Encoder.stab,
																																															"\\t" )
																																j = i + stub:len()
																																s = string.format( "%s%s%s",
																																																			start, stub, suffix )
																												else
																																s = start .. suffix
																												end
																												j = j + 1
																												break   -- while j
																								else
																												j = j + 2
																								end
																				else
																								r = "QouteEnd"
																								s = mw.text.trim( s:sub( i ) )
																								s = mw.ustring.sub( s, 1, JSONutil.more )
																								i = false
																				end
																end   -- while j
												else
																r = "Qoute"
																s = mw.text.trim( s:sub( i ) )
																s = mw.ustring.sub( s, 1, JSONutil.more )
																i = false
												end
								elseif not r then
												stub = s:sub( j )
												if stub:find( '[^"]*,%s*[%]}]' ) then
																r = "CommaEnd"
																s = mw.text.trim( stub )
																s = mw.ustring.sub( s, 1, JSONutil.more )
												else
																framework( stub )
												end
								end
				end   -- while i
				if not r   and   ( m ~= 0  or  n ~= 0 ) then
								if m ~= 0 then
												s = "}"
												if m > 0 then
																r = "BracketCloseLack"
																j = m
												elseif m < 0 then
																r = "BracketClosePlus"
																j = -m
												end
								else
												s = "]"
												if n > 0 then
																r = "BracketCloseLack"
																j = n
												else
																r = "BracketClosePlus"
																j = -n
												end
								end
								if j > 1 then
												s =  string.format( "%d %s", j, s )
								end
				elseif not ( r or last ) then
								stub = suffix or apply or ""
								j    = stub:find( "/", 1, true )
								if j then
												i, stub = pcall( free, stub, j, free )
								else
												i = true
								end
								stub = mw.text.trim( stub )
								if i then
												if stub:sub( - 1 ) ~= "}" then
																r = "Trailing"
																s = stub:match( "%}%s*(%S[^%}]*)$" )
																if s then
																				s = mw.ustring.sub( s, 1, JSONutil.more )
																else
																				s = mw.ustring.sub( stub,  - JSONutil.more )
																end
												end
								else
												r = "CommentEnd"
												s = mw.ustring.sub( stub, 1, JSONutil.more )
								end
				end
				if r and s then
								s = s:gsub( JSONutil.Encoder.sep,  " " )
								s = mw.text.encode( s ):gsub( "|", "&#124;" )
				end
				return r, s
end -- JSONutil.fair()
JSONutil.fault = function ( alert, add, adapt )
				-- Retrieve formatted message
				-- Parameter:
				--     alert  -- string, with error keyword, or other text
				--     add    -- string|nil|false, with context
				--     adapt  -- function|string|table|nil|false, for I18N
				-- Returns string, with HTML span
				local e = mw.html.create( "span" )
																					:addClass( "error" )
				local s = alert
				if type( s ) == "string" then
								s = mw.text.trim( s )
								if s == "" then
												s = "EMPTY JSONutil.fault key"
								end
								if not s:find( " ", 1, true ) then
												local storage = string.format( "I18n/Module:%s.tab",
																																											JSONutil.suite )
												local lucky, t = pcall( mw.ext.data.get, storage, "_" )
												if type( t ) == "table" then
																t = t.data
																if type( t ) == "table" then
																				local e
																				s = "err_" .. s
																				for i = 1, #t do
																								e = t[ i ]
																								if type( e ) == "table" then
																												if e[ 1 ] == s then
																																e = e[ 2 ]
																																if type( e ) == "table" then
																																				local q = type( adapt )
																																				if q == "function" then
																																								s = adapt( e, s )
																																								t = false
																																				elseif q == "string" then
																																								t = mw.text.split( adapt, "%s+" )
																																				elseif q == "table" then
																																								t = adapt
																																				else
																																								t = { }
																																				end
																																				if t then
																																								table.insert( t, Fallback() )
																																								table.insert( t, "en" )
																																								for k = 1, #t do
																																												q = e[ t[ k ] ]
																																												if type( q ) == "string" then
																																																s = q
																																																break   -- for k
																																												end
																																								end   -- for k
																																				end
																																else
																																				s = "JSONutil.fault I18N bad #" ..
																																								tostring( i )
																																end
																																break   -- for i
																												end
																								else
																												break   -- for i
																								end
																				end   -- for i
																else
																				s = "INVALID JSONutil.fault I18N corrupted"
																end
												else
																s = "INVALID JSONutil.fault commons:Data: " .. type( t )
												end
								end
				else
								s = "INVALID JSONutil.fault key: " .. tostring( s )
				end
				if type( add ) == "string" then
								s = string.format( "%s &#8211; %s", s, add )
				end
				e:wikitext( s )
				return tostring( e )
end -- JSONutil.fault()
JSONutil.fetch = function ( apply, always, adapt )
				-- Retrieve JSON data, or error message
				-- Parameter:
				--     apply   -- string, with presumable JSON text
				--     always  -- true, if apply is expected to need preprocessing
				--     adapt   -- function|string|table|nil|false, for I18N
				-- Returns table, with data, or string, with error as HTML span
				local lucky, r
				if not always then
								lucky, r = pcall( mw.text.jsonDecode, apply )
				end
				if not lucky then
								lucky, r = JSONutil.fair( apply )
								if lucky then
												r = JSONutil.fault( lucky, r, adapt )
								else
												lucky, r = pcall( mw.text.jsonDecode, r )
												if not lucky then
																r = JSONutil.fault( r, false, adapt )
												end
								end
				end
				return r
end -- JSONutil.fetch()
Failsafe.failsafe = function ( atleast )
				-- Retrieve versioning and check for compliance
				-- Precondition:
				--     atleast  -- string, with required version
				--                         or "wikidata" or "~" or "@" or false
				-- Postcondition:
				--     Returns  string  -- with queried version/item, also if problem
				--              false   -- if appropriate
				-- 2020-08-17
				local since = atleast
				local last    = ( since == "~" )
				local linked  = ( since == "@" )
				local link    = ( since == "item" )
				local r
				if last  or  link  or  linked  or  since == "wikidata" then
								local item = Failsafe.item
								since = false
								if type( item ) == "number"  and  item > 0 then
												local suited = string.format( "Q%d", item )
												if link then
																r = suited
												else
																local entity = mw.wikibase.getEntity( suited )
																if type( entity ) == "table" then
																				local seek = Failsafe.serialProperty or "P348"
																				local vsn  = entity:formatPropertyValues( seek )
																				if type( vsn ) == "table"  and
																							type( vsn.value ) == "string"  and
																							vsn.value ~= "" then
																								if last  and  vsn.value == Failsafe.serial then
																												r = false
																								elseif linked then
																												if mw.title.getCurrentTitle().prefixedText
																															==  mw.wikibase.getSitelink( suited ) then
																																r = false
																												else
																																r = suited
																												end
																								else
																												r = vsn.value
																								end
																				end
																end
												end
								end
				end
				if type( r ) == "nil" then
								if not since  or  since <= Failsafe.serial then
												r = Failsafe.serial
								else
												r = false
								end
				end
				return r
end -- Failsafe.failsafe()
-- Export
local p = { }
p.failsafe = function ( frame )
				-- Versioning interface
				local s = type( frame )
				local since
				if s == "table" then
								since = frame.args[ 1 ]
				elseif s == "string" then
								since = frame
				end
				if since then
								since = mw.text.trim( since )
								if since == "" then
												since = false
								end
				end
				return Failsafe.failsafe( since )  or  ""
end -- p.failsafe
p.encodeArray = function ( frame )
				return JSONutil.Encoder.Array( frame:getParent().args,
																																			frame.args.type,
																																			frame.args.error == "1" )
end -- p.encodeArray
p.encodeComponent = function ( frame )
				return JSONutil.Encoder.Component( frame.args.sign,
																																							frame.args.value,
																																							frame.args.type,
																																							flip( frame ),
																																							frame.args.error == "1" )
end -- p.encodeComponent
p.encodeHash = function ( frame )
				return JSONutil.Encoder.Hash( frame:getParent().args,
																																		frame.args )
end -- p.encodeHash
p.encodeI18N = function ( frame )
				return JSONutil.Encoder.I18N( frame:getParent().args,
																																		flip( frame ) )
end -- p.encodeI18N
p.encodeObject = function ( frame )
				return JSONutil.Encoder.object( flat( frame.args[ 1 ] ),
																																				flip( frame ) )
end -- p.encodeObject
p.encodePolyglott = function ( frame )
				return JSONutil.Encoder.Polyglott( flat( frame.args[ 1 ] ),
																																							flip( frame ) )
end -- p.encodePolyglott
p.JSONutil = function ()
				-- Module interface
				return JSONutil
end
return p