Bước tới nội dung

Mô đun:Convert/wikidata/sandbox

Bách khoa toàn thư mở Wikipedia
-- Các hàm để truy cập vào Wikidata cho Mô đun:Convert.local Collection = {}Collection.__index = Collectiondo	function Collection:add(item)		if item ~= nil then			self.n = self.n + 1			self[self.n] = item		end	end	function Collection:join(sep)		return table.concat(self, sep)	end	function Collection:remove(pos)		if self.n > 0 and (pos == nil or (0 < pos and pos <= self.n)) then			self.n = self.n - 1			return table.remove(self, pos)		end	end	function Collection:sort(comp)		table.sort(self, comp)	end	function Collection.new()		return setmetatable({n = 0}, Collection)	endendlocal function strip_to_nil(text)	-- If text is a non-empty string, return its trimmed content,	-- otherwise return nothing (empty string or not a string).	if type(text) == 'string' then		return text:match('(%S.-)%s*$')	endendlocal function frequency_unit(value, unit_table)	-- For use when converting m to Hz.	-- Return true, s where s = name of unit's default output unit,	-- or return false, t where t is an error message table.	-- However, for simplicity a valid result is always returned.	local unit	if unit_table._symbol == 'm' then		-- c = speed of light in a vacuum = 299792458 m/s		-- frequency = c / wavelength		local w = value * (unit_table.scale or 1)		local f = 299792458 / w  -- if w == 0, f = math.huge which works here		if f >= 1e12 then			unit = 'THz'		elseif f >= 1e9 then			unit = 'GHz'		elseif f >= 1e6 then			unit = 'MHz'		elseif f >= 1e3 then			unit = 'kHz'		else			unit = 'Hz'		end	end	return true, unit or 'Hz'endlocal function wavelength_unit(value, unit_table)	-- Like frequency_unit but for use when converting Hz to m.	local unit	if unit_table._symbol == 'Hz' then		-- Using 0.9993 rather than 1 avoids rounding which would give results		-- like converting 300 MHz to 100 cm instead of 1 m.		local w = 1 / (value * (unit_table.scale or 1))  -- Hz scale is inverted		if w >= 0.9993e6 then			unit = 'Mm'		elseif w >= 0.9993e3 then			unit = 'km'		elseif w >= 0.9993 then			unit = 'm'		elseif w >= 0.9993e-2 then			unit = 'cm'		elseif w >= 0.9993e-3 then			unit = 'mm'		else			unit = 'um'		end	end	return true, unit or 'm'endlocal specials = {	frequency = { frequency_unit },	wavelength = { wavelength_unit },	--------------------------------------------------------------------------------	-- Following is a removed experiment to show two values as a range	-- using '-' as the separator.	-- frequencyrange = { frequency_unit, '-' },	-- wavelengthrange = { wavelength_unit, '-' },}local function make_unit(units, parms, uid)	-- Return a unit code for convert or nil if unit unknown.	-- If necessary, add a dummy unit to parms so convert will use it	-- for the input without attempting a conversion since nothing	-- useful is available (for example, with unit volt).	local unit = units[uid]	if type(unit) ~= 'table' then		return nil	end	local ucode = unit.ucode	if ucode and not unit.si then		return ucode                -- a unit known to convert	end	parms.opt_ignore_error = true	ucode = ucode or unit._ucode    -- must be a non-empty string	local ukey, utable	if unit.si then		local base = units[unit.si]		ukey = base.symbol          -- must be a non-empty string		local n1 = base.name1		local n2 = base.name2		if not n1 then			n1 = ukey			n2 = n2 or n1           -- do not append 's'		end		utable = {			_symbol = ukey,			_name1 = n1,			_name2 = n2,			link = unit.link or base.link,			utype = n1,			prefixes = 1,		}	else		ukey = ucode		utable = {			symbol = ucode,         -- must be a non-empty string			name1 = unit.name1,     -- if nil, uses symbol			name2 = unit.name2,     -- if nil, uses name1..'s'			link = unit.link,       -- if nil, uses name1			utype = unit.name1 or ucode,		}	end	utable.scale = 1	utable.default = ''	utable.defkey = ''	utable.linkey = ''	utable.bad_mcode = ''	parms.unittable = { [ukey] = utable }	return ucodeendlocal function matches_qualifier(statement, qual)	-- Return:	--   false, nil : if statement does not match specification	--   true, nil  : if matches, and statement has no qualifier	--   true, sq   : if matches, where sq is the statement's qualifier	-- A match means that no qualifier was specified (qual == nil), or that	-- the statement has a qualifier matching the specification.	-- If a match occurs, the caller needs the statement's qualifier (if any)	-- so statements that duplicate the qualifier are not used, after the first.	-- Then, if convert is showing all values for a property such as the diameter	-- of a telescope's mirror (diameters of primary and secondary mirrors), it	-- will not show alternative values that could in principle be present for the	-- same item (telescope) and property (diameter) and qualifier (primary/secondary).	local target = (statement.qualifiers or {}).P518  -- P518 is "applies to part"	if type(target) == 'table' then		for _, q in ipairs(target) do			if type(q) == 'table' then				local value = (q.datavalue or {}).value				if value then					if qual == nil or qual == value.id then						return true, value.id					end				end			end		end	end	if qual == nil then		return true, nil  -- only occurs if statement has no qualifier	end	return false, nil  -- statement's qualifier is not relevant because statement will be skippedendlocal function get_statements(parms, pid)	-- Get specified item and return a list of tables with each statement for property pid.	-- Each table is of form {statqual=sq, stmt=statement} where sq = statement qualifier (nil if none).	-- Statements are in Wikidata's order except that those with preferred rank	-- are first, then normal rank. Any other rank is ignored.	local stored = {}  -- qualifiers of statements that are first for the qualifier, and will be returned	local qid = strip_to_nil(parms.qid)  -- nil for current page's item, or an item id (expensive)	local qual = strip_to_nil(parms.qual)  -- nil or id of wanted P518 (applies to part) item in qualifiers	local result = Collection.new()	local entity = mw.wikibase.getEntity(qid)	if type(entity) == 'table' then		local statements = (entity.claims or {})[pid]		if type(statements) == 'table' then			for _, rank in ipairs({ 'preferred', 'normal' }) do				for _, statement in ipairs(statements) do					if type(statement) == 'table' and rank == statement.rank then						local is_match, statqual = matches_qualifier(statement, qual)						if is_match then							result:add({ statqual = statqual, stmt = statement })						end					end				end			end		end	end	return resultendlocal function input_from_property(tdata, parms, pid)	-- Given that pid is a Wikidata property identifier like 'P123',	-- return a collection of {amount, ucode} pairs (two strings)	-- for each matching item/property, or return nothing.	--------------------------------------------------------------------------------	-- There appear to be few restrictions on how Wikidata is organized so it is	-- very likely that any decision a module makes about how to handle data	-- will be wrong for some cases at some time. This meets current requirements.	-- For each qualifier (or if no qualifier), if there are any preferred	-- statements, use them and ignore any normal statements.	-- For each qualifier, for the preferred statements if any, or for	-- the normal statements (but not both):	-- * Accept each statement if it has no qualifier (this will not occur	--   if qual=x is specified because other code already ensures that in that	--   case, only statements with a qualifier matching x are considered).	-- * Ignore any statements after the first if it has a qualifier.	-- The rationale is that for the diameter at [[South Pole Telescope]], want	-- convert to show the diameters for both the primary and secondary mirrors	-- if the convert does not specify which diameter is wanted.	-- However, if convert is given the wanted qualifier, only one value	-- (_the_ diameter) is wanted. For simplicity/consistency, that is also done	-- even if no qual=x is specified. Unclear what should happen.	-- For the wavelength at [[Nançay Radio Telescope]], want to show all three	-- values, and the values have no qualifiers.	--------------------------------------------------------------------------------	local result = Collection.new()	local done = {}	local skip_normal	for _, t in ipairs(get_statements(parms, pid)) do		local statement = t.stmt		if statement.mainsnak and statement.mainsnak.datatype == 'quantity' then			local value = (statement.mainsnak.datavalue or {}).value			if value then				local amount = value.amount				if amount then					amount = tostring(amount)  -- in case amount is ever a number					if amount:sub(1, 1) == '+' then						amount = amount:sub(2)					end					local unit = value.unit					if type(unit) == 'string' then						unit = unit:match('Q%d+$')  -- unit item id is at end of URL						local ucode = make_unit(tdata.wikidata_units, parms, unit)						if ucode then							local skip							if t.statqual then								if done[t.statqual] then									skip = true								else									done[t.statqual] = true								end							else								if statement.rank == 'preferred' then									skip_normal = true								elseif skip_normal then									skip = true								end							end							if not skip then								result:add({ amount, ucode })							end						end					end				end			end		end	end	return resultendlocal function input_from_text(tdata, parms, text, insert2)	-- Given string should be of form "<value><space><unit>" or	-- "<value1><space>ft<space><value2><space>in" for a special case (feet and inches).	-- Return true if values/units were extracted and inserted, or return nothing.	text = text:gsub('&nbsp;', ' '):gsub('%s+', ' ')	local pos = text:find(' ', 1, true)	if pos then		-- Leave checking of value to convert which can handle fractions.		local value = text:sub(1, pos - 1)		local uid = text:sub(pos + 1)		if uid:sub(1, 3) == 'ft ' and uid:sub(-3) == ' in' then			-- Special case for enwiki to allow {{convert|input=5 ft 10+1/2 in}}			insert2(uid:sub(4, -4), 'in')			insert2(value, 'ft')		else			insert2(value, make_unit(tdata.wikidata_units, parms, uid) or uid)		end		return true	endendlocal function adjustparameters(tdata, parms, index)	-- For Mô đun:Convert, adjust parms (a table of {{convert}} parameters).	-- Return true if successful or return false, t where t is an error message table.	-- This is intended mainly for use in infoboxes where the input might be	--    <value><space><unit>    or	--    <wikidata-property-id>	-- If successful, insert values and units in parms, before given index.	local text = parms.input  -- should be a trimmed, non-empty string	local pid = text:match('^P%d+$')	local sep = ','	local special = specials[parms[index]]	if special then		parms.out_unit = special[1]		sep = special[2] or sep		table.remove(parms, index)	end	local function quit()		return false, pid and { 'cvt_no_output' } or { 'cvt_bad_input', text }	end	local function insert2(first, second)		table.insert(parms, index, second)		table.insert(parms, index, first)	end	if pid then		parms.input_text = ''  -- output an empty string if an error occurs		local result = input_from_property(tdata, parms, pid)		if result.n == 0 then			return quit()		end		local ucode		for i, t in ipairs(result) do			-- Convert requires each input unit to be identical.			if i == 1 then				ucode = t[2]			elseif ucode ~= t[2] then				return quit()			end		end		local item = ucode		if item == parms[index] then			-- Remove specified output unit if it is the same as the Wikidata unit.			-- For example, {{convert|input=P2044|km}} with property "12 km".			table.remove(parms, index)		end		for i = result.n, 1, -1 do			insert2(result[i][1], item)			item = sep		end		return true	else		if input_from_text(tdata, parms, text, insert2) then			return true		end	end	return quit()end----------------------------------------------------------------------------------- List units and check syntax of definitions -----------------------------------------------------------------------------------------------------------------local specifications = {	-- seq = sequence in which fields are displayed	base = {		title = 'SI base units',		fields = {			symbol = { seq = 2, mandatory = true },			name1  = { seq = 3, mandatory = true },			name2  = { seq = 4 },			link   = { seq = 5 },		},		noteseq = 6,		header = '{| class="wikitable"\n!si !!biểu tượng !!tên 1 !!tên 2 !!liên kết !!ghi chú',		item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s',		footer = '|}',	},	alias = {		title = 'Aliases for convert',		fields = {			ucode  = { seq = 2, mandatory = true },			si     = { seq = 3 },		},		noteseq = 4,		header = '{| class="wikitable"\n!alias !!ucode !!base !!ghi chú',		item = '|-\n|%s ||%s ||%s ||%s',		footer = '|}',	},	known = {		title = 'Units known to convert',		fields = {			ucode  = { seq = 2, mandatory = true },			label  = { seq = 3, mandatory = true },		},		noteseq = 4,		header = '{| class="wikitable"\n!qid !!ucode !!nhãn !!ghi chú',		item = '|-\n|%s ||%s ||%s ||%s',		footer = '|}',	},	unknown = {		title = 'Units not known to convert',		fields = {			_ucode = { seq = 2, mandatory = true },			si     = { seq = 3 },			name1  = { seq = 4 },			name2  = { seq = 5 },			link   = { seq = 6 },			label  = { seq = 7, mandatory = true },		},		noteseq = 8,		header = '{| class="wikitable"\n!qid !!_ucode !!base !!tên 1 !!tên 2 !!liên kết !!nhãn !!ghi chú',		item = '|-\n|%s ||%s ||%s ||%s ||%s ||%s ||%s ||%s',		footer = '|}',	},}local function listunits(tdata, ulookup)	-- For Mô đun:Convert, make wikitext to list the built-in Wikidata units.	-- Return true, wikitext if successful or return false, t where t is an	-- error message table. Currently, an error return never occurs.	-- The syntax of each unit definition is checked and a note is added if	-- a problem is detected.	local function safe_cells(t)		-- This is not currently needed, but in case definitions ever use wikitext		-- like '[[kilogram|kg]]', escape the text so it works in a table cell.		local result = {}		for i, v in ipairs(t) do			if v:find('|', 1, true) then				v = v:gsub('(%[%[[^%[%]]-)|(.-%]%])', '%1\0%2')  -- replace pipe in piped link with a zero byte				v = v:gsub('|', '&#124;')                        -- escape '|'				v = v:gsub('%z', '|')                            -- restore pipe in piped link			end			result[i] = v:gsub('{', '&#123;')                    -- escape '{'		end		return unpack(result)	end	local wdunits = tdata.wikidata_units	local speckeys = { 'base', 'alias', 'unknown', 'known' }	for _, sid in ipairs(speckeys) do		specifications[sid].units = Collection.new()	end	local keys = Collection.new()	for k, v in pairs(wdunits) do		keys:add(k)	end	table.sort(keys)	local note_count = 0	for _, key in ipairs(keys) do		local unit = wdunits[key]		local ktext, sid		if key:match('^Q%d+$') then			ktext = '[[d:' .. key .. '|' .. key .. ']]'			if unit.ucode then				sid = 'known'			else				sid = 'unknown'			end		elseif unit.ucode then			ktext = key			sid = 'alias'		else			ktext = key			sid = 'base'		end		local result = { ktext }		local spec = specifications[sid]		local fields = spec.fields		local note = Collection.new()		for k, v in pairs(unit) do			if fields[k] then				local seq = fields[k].seq				if result[seq] then					note:add('trùng lặp ' .. k)  -- cannot happen since keys are unique				else					result[seq] = v				end			else				note:add('không hợp lệ ' .. k)			end		end		for k, v in pairs(fields) do			local value = result[v.seq]			if value then				if k == 'si' and not wdunits[value] then					note:add('cần si ' .. value)				end				if k == 'label' then					local wdl = mw.wikibase.getLabel(key)					if wdl ~= value then						note:add('nhãn được thay đổi đến ' .. tostring(wdl))					end				end			else				result[v.seq] = ''				if v.mandatory then					note:add('thiếu ' .. k)				end			end		end		local text		if note.n > 0 then			note_count = note_count + 1			text = '*' .. note:join('<br />')		end		result[spec.noteseq] = text or ''		spec.units:add(result)	end	local results = Collection.new()	if note_count > 0 then		local text = note_count .. (note_count == 1 and ' note' or ' notes')		results:add("'''Tìm kiếm cho * để xem " .. text .. "'''\n")	end	for _, sid in ipairs(speckeys) do		local spec = specifications[sid]		results:add("'''" .. spec.title .. "'''")		results:add(spec.header)		local fmt = spec.item		for _, unit in ipairs(spec.units) do			results:add(string.format(fmt, safe_cells(unit)))		end		results:add(spec.footer)	end	return true, results:join('\n')endreturn { _adjustparameters = adjustparameters, _listunits = listunits }