Module:Mapframe: Difference between revisions
Jump to navigation
Jump to search
>Evad37 (Refactoring code: no major changes. Require no globals, add comments to methods.) |
>Evad37 (Undid revision 964739874 by Evad37 (talk): revert, see User_talk:Evad37#Mapframe_errors) |
||
Line 1: | Line 1: | ||
-- Note: Originally written on English Wikipedia at https://en.wikipedia.org/wiki/Module: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 transclude = require("Module:Transclude") -- local copy of https://www.mediawiki.org/wiki/Module:Transclude | |||
-- | |||
local L10n = {} | local L10n = {} | ||
-- Template parameter names (unnumbered versions only) | -- Template parameter names (unnumbered versions only) | ||
Line 140: | Line 134: | ||
-- #### End of L10n settings #### | -- #### End of L10n settings #### | ||
function getParameterValue(args, param_id, suffix) | |||
function | |||
suffix = suffix or '' | suffix = suffix or '' | ||
if type( L10n.para[param_id] ) ~= 'table' then | if type( L10n.para[param_id] ) ~= 'table' then | ||
Line 167: | Line 147: | ||
end | end | ||
-- | -- Trim whitespace from args, and remove empty args. Also fix control characters. | ||
Trim whitespace from args, and remove empty args. Also fix control characters. | function trimArgs(argsTable) | ||
function | |||
local cleanArgs = {} | local cleanArgs = {} | ||
for key, val in pairs(argsTable) do | for key, val in pairs(argsTable) do | ||
Line 189: | Line 165: | ||
end | end | ||
function isAffirmed(val) | |||
function | |||
if not(val) then return false end | if not(val) then return false end | ||
return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false | return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false | ||
end | end | ||
function isDeclined(val) | |||
function | |||
if not(val) then return false end | if not(val) then return false end | ||
return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false | return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false | ||
end | end | ||
--[[ | local coordsDerivedFromFeatures = false; | ||
function makeContent(args) | |||
if getParameterValue(args, 'raw') then | |||
coordsDerivedFromFeatures = true -- Kartographer should be able to automatically calculate coords from raw geoJSON | |||
]]-- | return getParameterValue(args, 'raw') | ||
function | end | ||
local content = {} | |||
local argsExpanded = {} | |||
for k, v in pairs(args) do | |||
local index = string.match( k, '^[^0-9]+([0-9]*)$' ) | |||
if index ~= nil then | |||
local indexNumber = '' | |||
if index ~= '' then | |||
indexNumber = tonumber(index) | |||
else | |||
indexNumber = 1 | |||
end | |||
if argsExpanded[indexNumber] == nil then | |||
argsExpanded[indexNumber] = {} | |||
end | |||
argsExpanded[indexNumber][ string.gsub(k, index, '') ] = v | |||
end | |||
end | |||
for contentIndex, contentArgs in pairs(argsExpanded) do | |||
local argType = getParameterValue(contentArgs, "type") | |||
-- Kartographer automatically calculates coords if geolines/shapes are used (T227402) | |||
if not coordsDerivedFromFeatures then | |||
coordsDerivedFromFeatures = ( argType == L10n.str.line or argType == L10n.str.shape ) and true or false | |||
end | |||
if argType == L10n.str.named then | |||
local namedCoords = getNamedCoords(getParameterValue(contentArgs, "from")) | |||
local typeKey = type(L10n.para.type) == "table" and L10n.para.type[1] or L10n.para.type | |||
local coordKey = type(L10n.para.coord) == "table" and L10n.para.coord[1] or L10n.para.coord | |||
local titleKey = type(L10n.para.title) == "table" and L10n.para.title[1] or L10n.para.title | |||
local descKey = type(L10n.para.description) == "table" and L10n.para.description[1] or L10n.para.description | |||
for _, namedCoord in pairs(namedCoords) do | |||
contentArgs[typeKey] = "point" | |||
contentArgs[coordKey] = namedCoord.coord | |||
contentArgs[titleKey] = namedCoord.name | |||
contentArgs[descKey] = namedCoord.description | |||
content[#content+1] = makeContentJson(contentArgs) | |||
end | |||
else | |||
content[#content + 1] = makeContentJson(contentArgs) | |||
end | |||
end | |||
--Single item, no array needed | |||
if #content==1 then return content[1] end | |||
--Multiple items get placed in a FeatureCollection | |||
local contentArray = '[\n' .. table.concat( content, ',\n') .. '\n]' | |||
return contentArray | |||
end | |||
-- recursively extract coord templates | |||
function extractCoordTemplates(wikitext) | |||
local output = {} | local output = {} | ||
local templates = mw.ustring.gmatch(wikitext, '{%b{}}') | local templates = mw.ustring.gmatch(wikitext, '{%b{}}') | ||
Line 224: | Line 245: | ||
if nameParam then table.insert(output, template) end | if nameParam then table.insert(output, template) end | ||
elseif mw.ustring.find(template, L10n.template.Coord) then | elseif mw.ustring.find(template, L10n.template.Coord) then | ||
local subOutput = | local subOutput = extractCoordTemplates(mw.ustring.sub(template, 2)) | ||
for _, t in pairs(subOutput) do | for _, t in pairs(subOutput) do | ||
table.insert(output, t) | table.insert(output, t) | ||
Line 237: | Line 258: | ||
end | end | ||
--[[ | --[[ getNamedCoords | ||
Gets all named coordiates from a page or a section of a page. | Gets all named coordiates from a page or a section of a page. | ||
@param {string|nil} page Page name, or name#section, to get named coordinates | @param {string|nil} page Page name, or name#section, to get named coordinates | ||
from. If the name is omitted, i.e. #section or nil or empty string, then | |||
the current page will be used. | |||
@returns {table} sequence of {coord, name, description} | @returns {table} sequence of {coord, name, description} where coord is the | ||
coordinates in a format suitable for #parseCoords, and name is a string, and | |||
description is a string (the coordinates in a format suitable for displaying | |||
to the reader). If for some reason the name can't be found, the description | |||
is nil and the name contains display-format coordinates. | |||
@throws {L10n.error.noNamedCoords} if no named coordinates are found. | @throws {L10n.error.noNamedCoords} if no named coordinates are found. | ||
]]-- | ]]-- | ||
function | function getNamedCoords(page) | ||
local parts = mw.text.split(page or "", "#", true) | local parts = mw.text.split(page or "", "#", true) | ||
local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1] | local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1] | ||
local section = parts[2] | local section = parts[2] | ||
local pageWikitext = transclude.get(section and name.."#"..section or name) | local pageWikitext = transclude.get(section and name.."#"..section or name) | ||
local coordTemplates = | local coordTemplates = extractCoordTemplates(pageWikitext) | ||
if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end | if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end | ||
local frame = mw.getCurrentFrame() | local frame = mw.getCurrentFrame() | ||
Line 274: | Line 295: | ||
end | end | ||
function parseCoords(coords) | |||
function | |||
local parts = mw.text.split((mw.ustring.match(coords,'[_%.%d]+[NS][_%.%d]+[EW]') or ''), '_') | local parts = mw.text.split((mw.ustring.match(coords,'[_%.%d]+[NS][_%.%d]+[EW]') or ''), '_') | ||
Line 303: | Line 317: | ||
end | end | ||
function wikidataCoords(item_id) | |||
function | |||
if not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then | if not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then | ||
error(L10n.error.noCoords, 0) | error(L10n.error.noCoords, 0) | ||
Line 327: | Line 333: | ||
end | end | ||
function makeCoords(args, plainOutput) | |||
local coords, lat, long | |||
local frame = mw.getCurrentFrame() | |||
if getParameterValue(args, 'coord') then | |||
coords = frame:preprocess( getParameterValue(args, 'coord') ) | |||
lat, long = parseCoords(coords) | |||
else | |||
lat, long = wikidataCoords(getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage()) | |||
end | |||
function | if plainOutput then | ||
return lat, long | |||
end | |||
return {[0] = long, [1] = lat} | |||
end | |||
function makeCircleCoords(args) | |||
local lat, long = makeCoords(args, true) | |||
local radius = getParameterValue(args, 'radius') | |||
if not radius then | |||
radius = getParameterValue(args, 'radiusKm') and tonumber(getParameterValue(args, 'radiusKm'))*1000 | |||
if not radius then | |||
radius = getParameterValue(args, 'radiusMi') and tonumber(getParameterValue(args, 'radiusMi'))*1609.344 | |||
if not radius then | |||
radius = getParameterValue(args, 'radiusFt') and tonumber(getParameterValue(args, 'radiusFt'))*0.3048 | |||
end | |||
end | |||
end | |||
local edges = getParameterValue(args, 'edges') or L10n.defaults.edges | |||
if not lat or not long then | |||
error(L10n.error.noCircleCoords, 0) | |||
elseif not radius then | |||
error(L10n.error.noRadius, 0) | |||
elseif tonumber(radius) <= 0 then | |||
error(L10n.error.negativeRadius, 0) | |||
elseif tonumber(edges) <= 0 then | |||
error(L10n.error.negativeEdges, 0) | |||
end | |||
return circleToPolygon(lat, long, radius, tonumber(edges)) | |||
end | |||
function circleToPolygon(lat, long, radius, n) -- n is number of edges | |||
-- Based on https://github.com/gabzim/circle-to-polygon, ISC licence | -- Based on https://github.com/gabzim/circle-to-polygon, ISC licence | ||
function offset(cLat, cLon, distance, bearing) | |||
local lat1 = math.rad(cLat) | local lat1 = math.rad(cLat) | ||
local lon1 = math.rad(cLon) | local lon1 = math.rad(cLon) | ||
Line 366: | Line 403: | ||
end | end | ||
function makeContentJson(contentArgs) | |||
function | |||
local data = {} | local data = {} | ||
if | if getParameterValue(contentArgs, 'type') == L10n.str.point or getParameterValue(contentArgs, 'type') == L10n.str.circle then | ||
local isCircle = | local isCircle = getParameterValue(contentArgs, 'type') == L10n.str.circle | ||
data.type = "Feature" | data.type = "Feature" | ||
data.geometry = { | data.geometry = { | ||
type = isCircle and "LineString" or "Point", | type = isCircle and "LineString" or "Point", | ||
coordinates = isCircle and | coordinates = isCircle and makeCircleCoords(contentArgs) or makeCoords(contentArgs) | ||
} | } | ||
data.properties = { | data.properties = { | ||
title = | title = getParameterValue(contentArgs, 'title') or mw.getCurrentFrame():getParent():getTitle() | ||
} | } | ||
if isCircle then | if isCircle then | ||
-- TODO: This is very similar to below, should be extracted into a function | -- TODO: This is very similar to below, should be extracted into a function | ||
data.properties.stroke = | data.properties.stroke = getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor | ||
data.properties["stroke-width"] = tonumber( | data.properties["stroke-width"] = tonumber(getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth | ||
local strokeOpacity = | local strokeOpacity = getParameterValue(contentArgs, 'strokeOpacity') | ||
if strokeOpacity then | if strokeOpacity then | ||
data.properties['stroke-opacity'] = tonumber(strokeOpacity) | data.properties['stroke-opacity'] = tonumber(strokeOpacity) | ||
end | end | ||
local fill = | local fill = getParameterValue(contentArgs, 'fill') | ||
if fill then | if fill then | ||
data.properties.fill = fill | data.properties.fill = fill | ||
local fillOpacity = | local fillOpacity = getParameterValue(contentArgs, 'fillOpacity') | ||
data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6 | data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6 | ||
end | end | ||
else -- is a point | else -- is a point | ||
data.properties["marker-symbol"] = | data.properties["marker-symbol"] = getParameterValue(contentArgs, 'marker') or L10n.defaults.marker | ||
data.properties["marker-color"] = | data.properties["marker-color"] = getParameterValue(contentArgs, 'markerColor') or L10n.defaults.markerColor | ||
data.properties["marker-size"] = | data.properties["marker-size"] = getParameterValue(contentArgs, 'markerSize') or L10n.defaults.markerSize | ||
end | end | ||
else | else | ||
data.type = "ExternalData" | data.type = "ExternalData" | ||
if | if getParameterValue(contentArgs, 'type') == L10n.str.data or getParameterValue(contentArgs, 'from') then | ||
data.service = "page" | data.service = "page" | ||
elseif | elseif getParameterValue(contentArgs, 'type') == L10n.str.line then | ||
data.service = "geoline" | data.service = "geoline" | ||
elseif | elseif getParameterValue(contentArgs, 'type') == L10n.str.shape then | ||
data.service = "geoshape" | data.service = "geoshape" | ||
elseif | elseif getParameterValue(contentArgs, 'type') == L10n.str.shapeInverse then | ||
data.service = "geomask" | data.service = "geomask" | ||
end | end | ||
if | if getParameterValue(contentArgs, 'id') or (not (getParameterValue(contentArgs, 'from')) and mw.wikibase.getEntityIdForCurrentPage()) then | ||
data.ids = | data.ids = getParameterValue(contentArgs, 'id') or mw.wikibase.getEntityIdForCurrentPage() | ||
else | else | ||
data.title = | data.title = getParameterValue(contentArgs, 'from') | ||
end | end | ||
data.properties = { | data.properties = { | ||
stroke = | stroke = getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor, | ||
["stroke-width"] = tonumber( | ["stroke-width"] = tonumber(getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth | ||
} | } | ||
local strokeOpacity = | local strokeOpacity = getParameterValue(contentArgs, 'strokeOpacity') | ||
if strokeOpacity then | if strokeOpacity then | ||
data.properties['stroke-opacity'] = tonumber(strokeOpacity) | data.properties['stroke-opacity'] = tonumber(strokeOpacity) | ||
end | end | ||
local fill = | local fill = getParameterValue(contentArgs, 'fill') | ||
if fill and (data.service == "geoshape" or data.service == "geomask") then | if fill and (data.service == "geoshape" or data.service == "geomask") then | ||
data.properties.fill = fill | data.properties.fill = fill | ||
local fillOpacity = | local fillOpacity = getParameterValue(contentArgs, 'fillOpacity') | ||
if fillOpacity then | if fillOpacity then | ||
data.properties['fill-opacity'] = tonumber(fillOpacity) | data.properties['fill-opacity'] = tonumber(fillOpacity) | ||
Line 624: | Line 472: | ||
end | end | ||
data.properties.title = | data.properties.title = getParameterValue(contentArgs, 'title') or mw.title.getCurrentTitle().text | ||
if | if getParameterValue(contentArgs, 'description') then | ||
data.properties.description = | data.properties.description = getParameterValue(contentArgs, 'description') | ||
end | end | ||
Line 632: | Line 480: | ||
end | end | ||
function makeTagAttribs(args, isTitle) | |||
function | |||
local attribs = {} | local attribs = {} | ||
if | if getParameterValue(args, 'zoom') then | ||
attribs.zoom = | attribs.zoom = getParameterValue(args, 'zoom') | ||
end | end | ||
if | if isDeclined(getParameterValue(args, 'icon')) then | ||
attribs.class = "no-icon" | attribs.class = "no-icon" | ||
end | end | ||
if | if getParameterValue(args, 'type') == L10n.str.point and not coordsDerivedFromFeatures then | ||
local lat, long = | local lat, long = makeCoords(args, 'plainOutput') | ||
attribs.latitude = tostring(lat) | attribs.latitude = tostring(lat) | ||
attribs.longitude = tostring(long) | attribs.longitude = tostring(long) | ||
end | end | ||
if | if isAffirmed(getParameterValue(args, 'frame')) and not(isTitle) then | ||
attribs.width = | attribs.width = getParameterValue(args, 'frameWidth') or L10n.defaults.frameWidth | ||
attribs.height = | attribs.height = getParameterValue(args, 'frameHeight') or L10n.defaults.frameHeight | ||
if | if getParameterValue(args, 'frameCoordinates') then | ||
local frameLat, frameLong = | local frameLat, frameLong = parseCoords(getParameterValue(args, 'frameCoordinates')) | ||
attribs.latitude = frameLat | attribs.latitude = frameLat | ||
attribs.longitude = frameLong | attribs.longitude = frameLong | ||
else | else | ||
if | if getParameterValue(args, 'frameLatitude') then | ||
attribs.latitude = | attribs.latitude = getParameterValue(args, 'frameLatitude') | ||
end | end | ||
if | if getParameterValue(args, 'frameLongitude') then | ||
attribs.longitude = | attribs.longitude = getParameterValue(args, 'frameLongitude') | ||
end | end | ||
end | end | ||
if not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures then | if not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures then | ||
local success, lat, long = pcall( | local success, lat, long = pcall(wikidataCoords, getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage()) | ||
if success then | if success then | ||
attribs.latitude = tostring(lat) | attribs.latitude = tostring(lat) | ||
Line 674: | Line 515: | ||
end | end | ||
end | end | ||
if | if getParameterValue(args, 'frameAlign') then | ||
attribs.align = | attribs.align = getParameterValue(args, 'frameAlign') | ||
end | end | ||
if | if isAffirmed(getParameterValue(args, 'plain')) then | ||
attribs.frameless = "1" | attribs.frameless = "1" | ||
else | else | ||
attribs.text = | attribs.text = getParameterValue(args, 'text') or L10n.defaults.text | ||
end | end | ||
else | else | ||
attribs.text = | attribs.text = getParameterValue(args, 'text') or L10n.defaults.text | ||
end | end | ||
return attribs | return attribs | ||
end | end | ||
function makeTitleOutput(args, tagContent) | |||
local titleTag = mw.text.tag('maplink', makeTagAttribs(args, true), tagContent) | |||
function | |||
local titleTag = mw.text.tag('maplink', | |||
local spanAttribs = { | local spanAttribs = { | ||
style = "font-size: small;", | style = "font-size: small;", | ||
Line 704: | Line 538: | ||
end | end | ||
function makeInlineOutput(args, tagContent) | |||
function | |||
local tagName = 'maplink' | local tagName = 'maplink' | ||
if | if getParameterValue(args, 'frame') then | ||
tagName = 'mapframe' | tagName = 'mapframe' | ||
end | end | ||
return mw.text.tag(tagName, | return mw.text.tag(tagName, makeTagAttribs(args), tagContent) | ||
end | end | ||
-- Get the number of key-value pairs in a table, which might not be a sequence. | |||
function tableCount(t) | |||
local count = 0 | |||
for k, v in pairs(t) do | |||
count = count + 1 | |||
end | |||
return count | |||
end | |||
-- For a table where the values are all tables, returns either the tableCount of | |||
-- the subtables if they are all the same, or nil if they are not all the same. | |||
function subTablesCount(t) | |||
local count = nil | |||
for k, v in pairs(t) do | |||
if count == nil then | |||
count = tableCount(v) | |||
elseif count ~= tableCount(v) then | |||
return nil | |||
end | |||
end | |||
return count | |||
end | |||
function tableFromList(listString) | |||
if type(listString) ~= "string" or listString == "" then return nil end | |||
local separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or | |||
(mw.ustring.find(listString, ";", 0, true ) and ";") or "," | |||
local pattern = "%s*"..separator.."%s*" | |||
return mw.text.split(listString, pattern) | |||
end | |||
--[[ | --[[ | ||
Makes the HTML required for the swicther to work, including the templatestyles | Makes the HTML required for the swicther to work, including the templatestyles tag | ||
@param {table} params table sequence of {map, label} tables | @param {table} params table sequence of {map, label} tables | ||
@param {string} params{}.map Wikitext for mapframe map | @param {string} params{}.map Wikitext for mapframe map | ||
Line 733: | Line 591: | ||
@retruns {string} swicther HTML | @retruns {string} swicther HTML | ||
]]-- | ]]-- | ||
function | function makeSwitcherHtml(params, options) | ||
if not options then option = {} end | if not options then option = {} end | ||
local frame = mw.getCurrentFrame() | local frame = mw.getCurrentFrame() | ||
Line 786: | Line 644: | ||
end | end | ||
local p = {} | local p = {} | ||
Line 795: | Line 649: | ||
function p.main(frame) | function p.main(frame) | ||
local parent = frame.getParent(frame) | local parent = frame.getParent(frame) | ||
local switch = | local switch = getParameterValue(parent.args, 'switch') | ||
local isMulti = switch and mw.text.trim(switch) ~= "" | local isMulti = switch and mw.text.trim(switch) ~= "" | ||
local output = isMulti and p.multi(parent.args) or p._main(parent.args) | local output = isMulti and p.multi(parent.args) or p._main(parent.args) | ||
Line 803: | Line 657: | ||
-- Entry points for modules | -- Entry points for modules | ||
function p._main(_args) | function p._main(_args) | ||
local args = | local args = trimArgs(_args) | ||
local tagContent = | local tagContent = makeContent(args) | ||
local display = mw.text.split( | local display = mw.text.split(getParameterValue(args, 'display') or L10n.defaults.display, '%s*' .. L10n.str.dsep .. '%s*') | ||
local displayInTitle = display[1] == L10n.str.title or display[2] == L10n.str.title | local displayInTitle = display[1] == L10n.str.title or display[2] == L10n.str.title | ||
local displayInline = display[1] == L10n.str.inline or display[2] == L10n.str.inline | local displayInline = display[1] == L10n.str.inline or display[2] == L10n.str.inline | ||
Line 813: | Line 667: | ||
local output | local output | ||
if displayInTitle and displayInline then | if displayInTitle and displayInline then | ||
output = | output = makeTitleOutput(args, tagContent) .. makeInlineOutput(args, tagContent) | ||
elseif displayInTitle then | elseif displayInTitle then | ||
output = | output = makeTitleOutput(args, tagContent) | ||
elseif displayInline then | elseif displayInline then | ||
output = | output = makeInlineOutput(args, tagContent) | ||
else | else | ||
error(L10n.error.badDisplayPara) | error(L10n.error.badDisplayPara) | ||
Line 826: | Line 680: | ||
function p.multi(_args) | function p.multi(_args) | ||
local args = | local args = trimArgs(_args) | ||
if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) end | if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) end | ||
local switchParamValue = | local switchParamValue = getParameterValue(args, 'switch') | ||
local switchLabels = | local switchLabels = tableFromList(switchParamValue) | ||
if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) end | if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) end | ||
Line 843: | Line 697: | ||
local switchList = string.match(val, "^"..L10n.str.switch..":(.+)") | local switchList = string.match(val, "^"..L10n.str.switch..":(.+)") | ||
if switchList ~= nil then | if switchList ~= nil then | ||
local values = | local values = tableFromList(switchList) | ||
if #values == 1 then | if #values == 1 then | ||
error(string.format(L10n.error.oneSwitchValue, name), 0) | error(string.format(L10n.error.oneSwitchValue, name), 0) | ||
Line 850: | Line 704: | ||
end | end | ||
end | end | ||
if | if tableCount(switchParams) == 0 then | ||
error(L10n.error.noSwitchLists, 0) | error(L10n.error.noSwitchLists, 0) | ||
end | end | ||
local switchCount = | local switchCount = subTablesCount(switchParams) | ||
if not switchCount then | if not switchCount then | ||
error(L10n.error.switchMismatches, 0) | error(L10n.error.switchMismatches, 0) | ||
Line 861: | Line 715: | ||
-- Ensure a plain frame will be used (thumbnail will be built by the | -- Ensure a plain frame will be used (thumbnail will be built by the | ||
-- | -- makeSwitcherHtml function if required, so that switcher options are | ||
-- inside the thumnail) | -- inside the thumnail) | ||
mapframeArgs.plain = "yes" | mapframeArgs.plain = "yes" | ||
Line 876: | Line 730: | ||
}) | }) | ||
end | end | ||
return | return makeSwitcherHtml(switcher, { | ||
alignment = args["frame-align"] or "right", | alignment = args["frame-align"] or "right", | ||
isThumbnail = (args.frame and not args.plain) and true or false, | isThumbnail = (args.frame and not args.plain) and true or false, |
Revision as of 04:28, 27 June 2020
Documentation for this module may be created at Module:Mapframe/doc
-- 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 transclude = require("Module:Transclude") -- local copy of https://www.mediawiki.org/wiki/Module:Transclude local L10n = {} -- 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 `{{{one| {{{two|}}} }}}` 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", } -- Names of other templates this module depends on L10n.template = { Coord = "coord" -- lowercase template name } -- Error messages L10n.error = { badDisplayPara = "Invalid display parameter", noCoords = "Coordinates must be specified on Wikidata or in |" .. ( type(L10n.para.coord)== 'table' and L10n.para.coord[1] or L10n.para.coord ) .. "=", wikidataCoords = "Coordinates not found on Wikidata", noCircleCoords = "Circle centre coordinates must be specified, or available via Wikidata", negativeRadius = "Circle radius must be a positive number", noRadius = "Circle radius must be specified", negativeEdges = "Circle edges must be a positive number", noSwitchPara = "Found only one switch value in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=", oneSwitchLabel = "Found only one label in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=", noSwitchLists = "At least one parameter must have a SWITCH: list", switchMismatches = "All SWITCH: lists must have the same number of values", -- "%s" and "%d" tokens will be replaced with strings and numbers when used oneSwitchValue = "Found only one switch value in |%s=", fewerSwitchLabels = "Found %d switch values but only %d labels in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=", noNamedCoords = "No named coordinates found in %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 = "Map", frameWidth = "300", frameHeight = "200", markerColor = "5E74F3", markerSize = nil, strokeColor = "#ff0000", strokeWidth = 6, edges = 32 -- number of edges used to approximate a circle } -- #### End of L10n settings #### function getParameterValue(args, param_id, suffix) suffix = suffix or '' if type( L10n.para[param_id] ) ~= 'table' then return args[L10n.para[param_id]..suffix] end for _i, paramAlias in ipairs(L10n.para[param_id]) do if args[paramAlias..suffix] then return args[paramAlias..suffix] end end return nil end -- Trim whitespace from args, and remove empty args. Also fix control characters. function trimArgs(argsTable) local cleanArgs = {} for key, val in pairs(argsTable) do if 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 cleanArgs[key] = val:gsub('%c',' ') end else cleanArgs[key] = val end end return cleanArgs end function isAffirmed(val) if not(val) then return false end return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false end function isDeclined(val) if not(val) then return false end return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false end local coordsDerivedFromFeatures = false; function makeContent(args) if getParameterValue(args, 'raw') then coordsDerivedFromFeatures = true -- Kartographer should be able to automatically calculate coords from raw geoJSON return getParameterValue(args, 'raw') end local content = {} local argsExpanded = {} for k, v in pairs(args) do local index = string.match( k, '^[^0-9]+([0-9]*)$' ) if index ~= nil then local indexNumber = '' if index ~= '' then indexNumber = tonumber(index) else indexNumber = 1 end if argsExpanded[indexNumber] == nil then argsExpanded[indexNumber] = {} end argsExpanded[indexNumber][ string.gsub(k, index, '') ] = v end end for contentIndex, contentArgs in pairs(argsExpanded) do local argType = getParameterValue(contentArgs, "type") -- Kartographer automatically calculates coords if geolines/shapes are used (T227402) if not coordsDerivedFromFeatures then coordsDerivedFromFeatures = ( argType == L10n.str.line or argType == L10n.str.shape ) and true or false end if argType == L10n.str.named then local namedCoords = getNamedCoords(getParameterValue(contentArgs, "from")) local typeKey = type(L10n.para.type) == "table" and L10n.para.type[1] or L10n.para.type local coordKey = type(L10n.para.coord) == "table" and L10n.para.coord[1] or L10n.para.coord local titleKey = type(L10n.para.title) == "table" and L10n.para.title[1] or L10n.para.title local descKey = type(L10n.para.description) == "table" and L10n.para.description[1] or L10n.para.description for _, namedCoord in pairs(namedCoords) do contentArgs[typeKey] = "point" contentArgs[coordKey] = namedCoord.coord contentArgs[titleKey] = namedCoord.name contentArgs[descKey] = namedCoord.description content[#content+1] = makeContentJson(contentArgs) end else content[#content + 1] = makeContentJson(contentArgs) end end --Single item, no array needed if #content==1 then return content[1] end --Multiple items get placed in a FeatureCollection local contentArray = '[\n' .. table.concat( content, ',\n') .. '\n]' return contentArray end -- recursively extract coord templates function extractCoordTemplates(wikitext) local output = {} local templates = mw.ustring.gmatch(wikitext, '{%b{}}') local subtemplates = {} for template in templates do local name = mw.ustring.match(template, '{{([^}|]+)') -- get the template name local nameParam = mw.ustring.match(template, "|%s*name%s*=%s*[^}|]+") if mw.ustring.lower(mw.text.trim(name)) == L10n.template.Coord then if nameParam then table.insert(output, template) end elseif mw.ustring.find(template, L10n.template.Coord) then local subOutput = extractCoordTemplates(mw.ustring.sub(template, 2)) for _, t in pairs(subOutput) do table.insert(output, t) end end end -- ensure coords are not using title display for k, v in pairs(output) do output[k] = mw.ustring.gsub(v, "|%s*display%s*=[^|}]+", "|display=inline") end return output end --[[ getNamedCoords Gets all named coordiates from a page or a section of a page. @param {string|nil} page Page name, or name#section, to get named coordinates from. If the name is omitted, i.e. #section or nil or empty string, then the current page will be used. @returns {table} sequence of {coord, name, description} where coord is the coordinates in a format suitable for #parseCoords, and name is a string, and description is a string (the coordinates in a format suitable for displaying to the reader). If for some reason the name can't be found, the description is nil and the name contains display-format coordinates. @throws {L10n.error.noNamedCoords} if no named coordinates are found. ]]-- function getNamedCoords(page) local parts = mw.text.split(page or "", "#", true) local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1] local section = parts[2] local pageWikitext = transclude.get(section and name.."#"..section or name) local coordTemplates = extractCoordTemplates(pageWikitext) if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end local frame = mw.getCurrentFrame() local sep = "________" local expandedContent = frame:preprocess(table.concat(coordTemplates, sep)) local expandedTemplates = mw.text.split(expandedContent, sep) local namedCoords = {} for _, expandedTemplate in pairs(expandedTemplates) do local coord = mw.ustring.match(expandedTemplate, "<span class=\"geo%-dec\".->(.-)</span>") if coord then local name = mw.ustring.match(expandedTemplate, "<span class=\"fn org\">(.-)</span>") or coord local description = name ~= coord and coord local coord = mw.ustring.gsub(coord, "[° ]", "_") table.insert(namedCoords, {coord=coord, name=name, description=description}) end end if #namedCoords == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end return namedCoords end function parseCoords(coords) local parts = mw.text.split((mw.ustring.match(coords,'[_%.%d]+[NS][_%.%d]+[EW]') or ''), '_') local lat_d = tonumber(parts[1]) local lat_m = tonumber(parts[2]) -- nil if coords are in decimal format local lat_s = lat_m and tonumber(parts[3]) -- nil if coords are either in decimal format or degrees and minutes only local lat = lat_d + (lat_m or 0)/60 + (lat_s or 0)/3600 if parts[#parts/2] == 'S' then lat = lat * -1 end local long_d = tonumber(parts[1+#parts/2]) local long_m = tonumber(parts[2+#parts/2]) -- nil if coords are in decimal format local long_s = long_m and tonumber(parts[3+#parts/2]) -- nil if coords are either in decimal format or degrees and minutes only local long = long_d + (long_m or 0)/60 + (long_s or 0)/3600 if parts[#parts] == 'W' then long = long * -1 end return lat, long end function wikidataCoords(item_id) if not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then error(L10n.error.noCoords, 0) end local coordStatements = mw.wikibase.getBestStatements(item_id, 'P625') if not coordStatements or #coordStatements == 0 then error(L10n.error.wikidataCoords, 0) end local hasNoValue = ( coordStatements[1].mainsnak and coordStatements[1].mainsnak.snaktype == 'novalue' ) if hasNoValue then error(L10n.error.wikidataCoords, 0) end local wdCoords = coordStatements[1]['mainsnak']['datavalue']['value'] return tonumber(wdCoords['latitude']), tonumber(wdCoords['longitude']) end function makeCoords(args, plainOutput) local coords, lat, long local frame = mw.getCurrentFrame() if getParameterValue(args, 'coord') then coords = frame:preprocess( getParameterValue(args, 'coord') ) lat, long = parseCoords(coords) else lat, long = wikidataCoords(getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage()) end if plainOutput then return lat, long end return {[0] = long, [1] = lat} end function makeCircleCoords(args) local lat, long = makeCoords(args, true) local radius = getParameterValue(args, 'radius') if not radius then radius = getParameterValue(args, 'radiusKm') and tonumber(getParameterValue(args, 'radiusKm'))*1000 if not radius then radius = getParameterValue(args, 'radiusMi') and tonumber(getParameterValue(args, 'radiusMi'))*1609.344 if not radius then radius = getParameterValue(args, 'radiusFt') and tonumber(getParameterValue(args, 'radiusFt'))*0.3048 end end end local edges = getParameterValue(args, 'edges') or L10n.defaults.edges if not lat or not long then error(L10n.error.noCircleCoords, 0) elseif not radius then error(L10n.error.noRadius, 0) elseif tonumber(radius) <= 0 then error(L10n.error.negativeRadius, 0) elseif tonumber(edges) <= 0 then error(L10n.error.negativeEdges, 0) end return circleToPolygon(lat, long, radius, tonumber(edges)) end function circleToPolygon(lat, long, radius, n) -- n is number of edges -- Based on https://github.com/gabzim/circle-to-polygon, ISC licence function offset(cLat, cLon, distance, bearing) local lat1 = math.rad(cLat) local lon1 = math.rad(cLon) local dByR = distance / 6378137 -- distance divided by 6378137 (radius of the earth) wgs84 local lat = math.asin( math.sin(lat1) * math.cos(dByR) + math.cos(lat1) * math.sin(dByR) * math.cos(bearing) ) local lon = lon1 + math.atan2( math.sin(bearing) * math.sin(dByR) * math.cos(lat1), math.cos(dByR) - math.sin(lat1) * math.sin(lat) ) return {math.deg(lon), math.deg(lat)} end local coordinates = {}; local i = 0; while i < n do table.insert(coordinates, offset(lat, long, radius, (2*math.pi*i*-1)/n) ) i = i + 1 end table.insert(coordinates, offset(lat, long, radius, 0)) return coordinates end function makeContentJson(contentArgs) local data = {} if getParameterValue(contentArgs, 'type') == L10n.str.point or getParameterValue(contentArgs, 'type') == L10n.str.circle then local isCircle = getParameterValue(contentArgs, 'type') == L10n.str.circle data.type = "Feature" data.geometry = { type = isCircle and "LineString" or "Point", coordinates = isCircle and makeCircleCoords(contentArgs) or makeCoords(contentArgs) } data.properties = { title = getParameterValue(contentArgs, 'title') or mw.getCurrentFrame():getParent():getTitle() } if isCircle then -- TODO: This is very similar to below, should be extracted into a function data.properties.stroke = getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor data.properties["stroke-width"] = tonumber(getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth local strokeOpacity = getParameterValue(contentArgs, 'strokeOpacity') if strokeOpacity then data.properties['stroke-opacity'] = tonumber(strokeOpacity) end local fill = getParameterValue(contentArgs, 'fill') if fill then data.properties.fill = fill local fillOpacity = getParameterValue(contentArgs, 'fillOpacity') data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6 end else -- is a point data.properties["marker-symbol"] = getParameterValue(contentArgs, 'marker') or L10n.defaults.marker data.properties["marker-color"] = getParameterValue(contentArgs, 'markerColor') or L10n.defaults.markerColor data.properties["marker-size"] = getParameterValue(contentArgs, 'markerSize') or L10n.defaults.markerSize end else data.type = "ExternalData" if getParameterValue(contentArgs, 'type') == L10n.str.data or getParameterValue(contentArgs, 'from') then data.service = "page" elseif getParameterValue(contentArgs, 'type') == L10n.str.line then data.service = "geoline" elseif getParameterValue(contentArgs, 'type') == L10n.str.shape then data.service = "geoshape" elseif getParameterValue(contentArgs, 'type') == L10n.str.shapeInverse then data.service = "geomask" end if getParameterValue(contentArgs, 'id') or (not (getParameterValue(contentArgs, 'from')) and mw.wikibase.getEntityIdForCurrentPage()) then data.ids = getParameterValue(contentArgs, 'id') or mw.wikibase.getEntityIdForCurrentPage() else data.title = getParameterValue(contentArgs, 'from') end data.properties = { stroke = getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor, ["stroke-width"] = tonumber(getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth } local strokeOpacity = getParameterValue(contentArgs, 'strokeOpacity') if strokeOpacity then data.properties['stroke-opacity'] = tonumber(strokeOpacity) end local fill = getParameterValue(contentArgs, 'fill') if fill and (data.service == "geoshape" or data.service == "geomask") then data.properties.fill = fill local fillOpacity = getParameterValue(contentArgs, 'fillOpacity') if fillOpacity then data.properties['fill-opacity'] = tonumber(fillOpacity) end end end data.properties.title = getParameterValue(contentArgs, 'title') or mw.title.getCurrentTitle().text if getParameterValue(contentArgs, 'description') then data.properties.description = getParameterValue(contentArgs, 'description') end return mw.text.jsonEncode(data) end function makeTagAttribs(args, isTitle) local attribs = {} if getParameterValue(args, 'zoom') then attribs.zoom = getParameterValue(args, 'zoom') end if isDeclined(getParameterValue(args, 'icon')) then attribs.class = "no-icon" end if getParameterValue(args, 'type') == L10n.str.point and not coordsDerivedFromFeatures then local lat, long = makeCoords(args, 'plainOutput') attribs.latitude = tostring(lat) attribs.longitude = tostring(long) end if isAffirmed(getParameterValue(args, 'frame')) and not(isTitle) then attribs.width = getParameterValue(args, 'frameWidth') or L10n.defaults.frameWidth attribs.height = getParameterValue(args, 'frameHeight') or L10n.defaults.frameHeight if getParameterValue(args, 'frameCoordinates') then local frameLat, frameLong = parseCoords(getParameterValue(args, 'frameCoordinates')) attribs.latitude = frameLat attribs.longitude = frameLong else if getParameterValue(args, 'frameLatitude') then attribs.latitude = getParameterValue(args, 'frameLatitude') end if getParameterValue(args, 'frameLongitude') then attribs.longitude = getParameterValue(args, 'frameLongitude') end end if not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures then local success, lat, long = pcall(wikidataCoords, getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage()) if success then attribs.latitude = tostring(lat) attribs.longitude = tostring(long) end end if getParameterValue(args, 'frameAlign') then attribs.align = getParameterValue(args, 'frameAlign') end if isAffirmed(getParameterValue(args, 'plain')) then attribs.frameless = "1" else attribs.text = getParameterValue(args, 'text') or L10n.defaults.text end else attribs.text = getParameterValue(args, 'text') or L10n.defaults.text end return attribs end function makeTitleOutput(args, tagContent) local titleTag = mw.text.tag('maplink', makeTagAttribs(args, true), tagContent) local spanAttribs = { style = "font-size: small;", id = "coordinates" } return mw.text.tag('span', spanAttribs, titleTag) end function makeInlineOutput(args, tagContent) local tagName = 'maplink' if getParameterValue(args, 'frame') then tagName = 'mapframe' end return mw.text.tag(tagName, makeTagAttribs(args), tagContent) end -- Get the number of key-value pairs in a table, which might not be a sequence. function tableCount(t) local count = 0 for k, v in pairs(t) do count = count + 1 end return count end -- For a table where the values are all tables, returns either the tableCount of -- the subtables if they are all the same, or nil if they are not all the same. function subTablesCount(t) local count = nil for k, v in pairs(t) do if count == nil then count = tableCount(v) elseif count ~= tableCount(v) then return nil end end return count end function tableFromList(listString) if type(listString) ~= "string" or listString == "" then return nil end local separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or (mw.ustring.find(listString, ";", 0, true ) and ";") or "," local pattern = "%s*"..separator.."%s*" return mw.text.split(listString, pattern) end --[[ Makes the HTML required for the swicther to work, including the templatestyles tag @param {table} params table sequence of {map, label} tables @param {string} params{}.map Wikitext for mapframe map @param {string} params{}.label Label text for swicther option @param {table} options @param {string} options.alignment "left" or "center" or "right" @param {boolean} options.isThumbnail Display in a thumbnail @param {string} options.width Width of frame, e.g. "200" @param {string} [options.caption] Caption wikitext for thumnail @retruns {string} swicther HTML ]]-- function makeSwitcherHtml(params, options) if not options then option = {} end local frame = mw.getCurrentFrame() local styles = frame:extensionTag{ name = "templatestyles", args = {src = "Template:Maplink/styles-multi.css"} } local container = mw.html.create("div") :addClass("switcher-container") :addClass("mapframe-multi-container") if options.alignment == "left" or options.alignment == "right" then container:addClass("float"..options.alignment) else -- alignment is "center" container:addClass("center") end for i = 1, #params do container :tag("div") :wikitext(params[i].map) :tag("span") :addClass("switcher-label") :css("display", "none") :wikitext(mw.text.trim(params[i].label)) end if not options.isThumbnail then return styles .. tostring(container) end local classlist = container:getAttr("class") classlist = mw.ustring.gsub(classlist, "%a*"..options.alignment, "") container:attr("class", classlist) local outerCountainer = mw.html.create("div") :addClass("mapframe-multi-outer-container") :addClass("mw-kartographer-container") :addClass("thumb") if options.alignment == "left" or options.alignment == "right" then outerCountainer:addClass("t"..options.alignment) else -- alignment is "center" outerCountainer :addClass("tnone") :addClass("center") end outerCountainer :tag("div") :addClass("thumbinner") :css("width", options.width.."px") :node(container) :node(options.caption and mw.html.create("div") :addClass("thumbcaption") :wikitext(options.caption) ) return styles .. tostring(outerCountainer) end local p = {} -- Entry point for templates function p.main(frame) local parent = frame.getParent(frame) local switch = getParameterValue(parent.args, 'switch') local isMulti = switch and mw.text.trim(switch) ~= "" local output = isMulti and p.multi(parent.args) or p._main(parent.args) return frame:preprocess(output) end -- Entry points for modules function p._main(_args) local args = trimArgs(_args) local tagContent = makeContent(args) local display = mw.text.split(getParameterValue(args, 'display') or L10n.defaults.display, '%s*' .. L10n.str.dsep .. '%s*') local displayInTitle = display[1] == L10n.str.title or display[2] == L10n.str.title local displayInline = display[1] == L10n.str.inline or display[2] == L10n.str.inline local output if displayInTitle and displayInline then output = makeTitleOutput(args, tagContent) .. makeInlineOutput(args, tagContent) elseif displayInTitle then output = makeTitleOutput(args, tagContent) elseif displayInline then output = makeInlineOutput(args, tagContent) else error(L10n.error.badDisplayPara) end return output end function p.multi(_args) local args = trimArgs(_args) if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) end local switchParamValue = getParameterValue(args, 'switch') local switchLabels = tableFromList(switchParamValue) if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) end local mapframeArgs = {} local switchParams = {} for name, val in pairs(args) do -- Copy to mapframeArgs, if not the switch labels or a switch parameter if val ~= switchParamValue and not string.match(val, "^"..L10n.str.switch..":") then mapframeArgs[name] = val end -- Check if this is a param to switch. If so, store the name and switch -- values in switchParams table. local switchList = string.match(val, "^"..L10n.str.switch..":(.+)") if switchList ~= nil then local values = tableFromList(switchList) if #values == 1 then error(string.format(L10n.error.oneSwitchValue, name), 0) end switchParams[name] = values end end if tableCount(switchParams) == 0 then error(L10n.error.noSwitchLists, 0) end local switchCount = subTablesCount(switchParams) if not switchCount then error(L10n.error.switchMismatches, 0) elseif switchCount > #switchLabels then error(string.format(L10n.error.fewerSwitchLabels, switchCount, #switchLabels), 0) end -- Ensure a plain frame will be used (thumbnail will be built by the -- makeSwitcherHtml function if required, so that switcher options are -- inside the thumnail) mapframeArgs.plain = "yes" local switcher = {} for i = 1, switchCount do local label = switchLabels[i] for name, values in pairs(switchParams) do mapframeArgs[name] = values[i] end table.insert(switcher, { map = p._main(mapframeArgs), label = "Show "..label }) end return makeSwitcherHtml(switcher, { alignment = args["frame-align"] or "right", isThumbnail = (args.frame and not args.plain) and true or false, width = args["frame-width"] or L10n.defaults.frameWidth, caption = args.text }) end return p