Mô đun:Mapframe

-- Note: Originally written on English Wikipedia at https://en.wikipedia.org/wiki/Module:Mapframe
------------------------------------------------------------------------------
##### Localisation (L10n) settings #####
Replace values in quotes ("") with localised values
------------------------------------------------------------------------------
local L10n = {}
-- Modue dependencies
local transcluder -- local copy of https://www.mediawiki.org/wiki/Module:Transcluder loaded lazily
-- "strict" should not be used, at least until all other modules which require this module are not using globals.
-- Template parameter names (unnumbered versions only)
--   Specify each as either a single string, or a table of strings (aliases)
--   Aliases are checked left-to-right, i.e. `{ "one", "two" }` is equivalent to using `` in a template
L10n.para = {
	display		= "display",
	type		= "type",
	id              = { "id", "ids" },
	from		= "from",
	raw		= "raw",
	title		= "title",
	description	= "description",
	strokeColor     = { "stroke-color", "stroke-colour" },
	strokeWidth	= "stroke-width",
	strokeOpacity = "stroke-opacity",
	fill        = "fill",
	fillOpacity     = "fill-opacity",
	coord		= "coord",
	marker		= "marker",
	markerColor	= { "marker-color", "marker-colour" },
	markerSize = "marker-size",
	radius      = { "radius", "radius_m" },
	radiusKm    = "radius_km",
	radiusFt    = "radius_ft",
	radiusMi    = "radius_mi",
	edges       = "edges",
	text		= "text",
	icon		= "icon",
	zoom		= "zoom",
	frame		= "frame",
	plain		= "plain",
	frameWidth	= "frame-width",
	frameHeight	= "frame-height",
	frameCoordinates = { "frame-coordinates", "frame-coord" },
	frameLatitude    = { "frame-lat", "frame-latitude" },
	frameLongitude   = { "frame-long", "frame-longitude" },
	frameAlign       = "frame-align",
	switch           = "switch",
	overlay          = "overlay",
	overlayBorder    = "overlay-border",
	overlayHorizontalAlignment = "overlay-horizontal-alignment",
	overlayVerticalAlignment = "overlay-vertical-alignment",
	overlayHorizontalOffset = "overlay-horizontal-offset",
	overlayVerticalOffset = "overlay-vertical-offset"
}
-- Names of other templates this module can extract coordinates from
L10n.template = {
	coord     = { -- The coord template, as well as templates with output that contains 
		"Coord", "Coord/sandbox",
		"NRHP row", "NRHP row/sandbox",
		"WikidataCoord", "WikidataCoord/sandbox", "Wikidatacoord", "Wikidata coord"
	}
}
-- Error messages
L10n.error = {
	badDisplayPara    = "Tham số hiển thị không hợp lệ",
	noCoords	      = "Tọa độ phải được xác định trên Wikidata hoặc trong |" .. ( type(L10n.para.coord)== 'table' and L10n.para.coord1 or L10n.para.coord ) .. "=",
	wikidataCoords    = "Tọa độ không tìm thấy trên Wikidata",
	noCircleCoords    = "Tọa độ tâm hình tròn phải được chỉ định, hoặc có sẵn thông qua Wikidata",
	negativeRadius    = "Bán kính hình tròn phải là một số dương",
	noRadius          = "Bán kính hình tròn phải được xác định",
	negativeEdges     = "Các cạnh hình tròn phải là một số dương",
	noSwitchPara      = "Chỉ có tìm thấy một giá trị chuyển đổi trong |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch1 or L10n.para.switch ) .. "=",
	oneSwitchLabel    = "Chỉ có tìm thấy một nhãn trong |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch1 or L10n.para.switch ) .. "=",
	noSwitchLists     = "Ít nhất một tham số phải có một danh sách SWITCH:",
	switchMismatches  = "Tất cả SWITCH: danh sách phải có cùng một số giá trị",
	
	 -- "%s" and "%d" tokens will be replaced with strings and numbers when used
	oneSwitchValue    = "Chỉ có tìm thấy một giá trị chuyển đổi trong |%s=",
	fewerSwitchLabels = "Tìm thấy giá trị chuyển đổi %d nhưng chỉ có nhãn %d trong |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch1 or L10n.para.switch ) .. "=",
	noNamedCoords     = "Không tìm thấy tọa độ được đặt tên nào trong %s"
}
-- Other strings
L10n.str = {
	-- valid values for display parameter, e.g. (|display=inline) or (|display=title) or (|display=inline,title) or (|display=title,inline)
	inline		= "inline",
	title		= "title",
	dsep		= ",",			-- separator between inline and title (comma in the example above)
	-- valid values for type paramter
	line		= "line",		-- geoline feature (e.g. a road)
	shape		= "shape",		-- geoshape feature (e.g. a state or province)
	shapeInverse	= "shape-inverse",	-- geomask feature (the inverse of a geoshape)
	data		= "data",		-- geoJSON data page on Commons
	point		= "point",		-- single point feature (coordinates)
	circle      = "circle",     -- circular area around a point
	named       = "named",      -- all named coordinates in an article or section
	-- Keyword to indicate a switch list. Must NOT use the special characters ^$()%.*+-?
	switch = "SWITCH",
	-- valid values for icon, frame, and plain parameters
	affirmedWords = ' '..table.concat({
		"add",
		"added",
		"affirm",
		"affirmed",
		"include",
		"included",
		"on",
		"true",
		"yes",
		"y"
	}, ' ')..' ',
	declinedWords = ' '..table.concat({
		"decline",
		"declined",
		"exclude",
		"excluded",
		"false",
		"none",
		"not",
		"no",
		"n",
		"off",
		"omit",
		"omitted",
		"remove",
		"removed"
	}, ' ')..' '
}
-- Default values for parameters
L10n.defaults = {
	display		= L10n.str.inline,
	text		= "Bản đồ",
	frameWidth	= "300",
	frameHeight	= "200",
	frameAlign  = "right",
	markerColor	= "5E74F3",
	markerSize	= nil,
	strokeColor	= "#ff0000",
	strokeWidth	= 6,
	edges = 32, -- number of edges used to approximate a circle
	overlayBorder = "1px solid white",
	overlayHorizontalAlignment = "right",
	overlayHorizontalOffset = "0",
	overlayVerticalAlignment = "bottom",
	overlayVerticalOffset = "0"
}
-- #### End of L10n settings ####
------------------------------------------------------------------------------
Utility methods
------------------------------------------------------------------------------
local util = {}
--
Looks up a parameter value based on the id (a key from the L10n.para table) and
optionally a suffix, for parameters that can be suffixed (e.g. type2 is type
with suffix 2).
@param {table} args  key-value pairs of parameter names and their values
@param {string} param_id  id for parameter name (key from the L10n.para table)
@param {string} suffix  suffix for parameter name
@returns {string|nil} parameter value if found, or nil if not found
--
function util.getParameterValue(args, param_id, suffix)
	suffix = suffix or 
	if type( L10n.paraparam_id ) ~= 'table' then
		return argsL10n.paraparam_id..suffix
	end
	for _i, paramAlias in ipairs(L10n.paraparam_id) do
		if argsparamAlias..suffix then
			return argsparamAlias..suffix
		end
	end
	return nil
end
--
Trim whitespace from args, and remove empty args. Also fix control characters.
@param {table} argsTable
@returns {table} trimmed args table
--
function util.trimArgs(argsTable)
	local cleanArgs = {}
	for key, val in pairs(argsTable) do
		if type(key) == 'string'  and type(val) == 'string' then
			val = val:match('^%s*(.-)%s*$')
			if val ~=  then
				-- control characters inside json need to be escaped, but stripping them is simpler
				-- See also T214984
				-- However, *don't* strip control characters from wikitext (text or description parameters) or you'll break strip markers
				-- Alternatively it might be better to only strip control char from raw parameter content
				if util.matchesParam('text', key) or util.matchesParam('description', key, key:gsub('^%D+(%d+)$', '%1') ) then
					cleanArgskey = val
				else
					cleanArgskey = val:gsub('%c',' ')
				end
			end
		else
			cleanArgskey = val
		end
	end
	return cleanArgs
end
--nil} - an optional suffix to apply to the value(s) from the localization key
@returns {boolean} true if the name matches the parameter, false otherwise
--
function util.matchesParam(key, name, suffix)
	local param = L10n.parakey
	suffix = suffix or 
	if type(param) == 'table' then
		for _, v in pairs(param) do
			if (v .. suffix) == name then return true end
		end
		return false
	end
	return ((param .. suffix) == name)
end
--
Check if a value is affirmed (one of the values in L10n.str.affirmedWords)
@param {string} val  Value to be checked
@returns {boolean} true if affirmed, false otherwise
--
function util.isAffirmed(val)
	if not(val) then return false end
	return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false
end
--
Check if a value is declined (one of the values in L10n.str.declinedWords)
@param {string} val  Value to be checked
@returns {boolean} true if declined, false otherwise
--
function util.isDeclined(val)
	if not(val) then return false end
	return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false
end
--
Check if the name of a template matches the known coord templates or wrappers
(in L10n.template.coord). The name is normalised when checked, so e.g. the names
"Coord", "coord", and "  Coord" all return true.
@param {string} name
@returns {boolean} true if it is a coord template or wrapper, false otherwise
--
function util.isCoordTemplateOrWrapper(name)
	name = mw.text.trim(name)
	local inputTitle = mw.title.new(name, 'Template')
	if not inputTitle then
		return false
	end
	-- Create (or reuse) mw.title objects for each known coord template/wrapper.
	-- Stored in L10n.template.title so that they don't need to be recreated
	-- each time this function is called
	if not L10n.template.titles then
		L10n.template.titles = {}
		for _, v in pairs(L10n.template.coord) do
			table.insert(L10n.template.titles, mw.title.new(v, 'Template'))
		end
	end
	for _, templateTitle in pairs(L10n.template.titles) do
		if mw.title.equals(inputTitle, templateTitle) then
			return true
		end
	end
	return false
end
--
Recursively extract coord templates which have a name parameter.
@param {string} wikitext
@returns {table} table sequence of coord templates
--
function util.extractCoordTemplates(wikitext)
	local output = {}
	local templates = mw.ustring.gmatch(wikitext, '{%b{}}')
	local subtemplates = {}
	for template in templates do
		local templateName = mw.ustring.match(template, '|+")
		if util.isCoordTemplateOrWrapper(templateName) then
			if nameParam then table.insert(output, template) end
		elseif mw.ustring.find(mw.ustring.sub(template, 2), "