Module:Infobox mapframe: Difference between revisions

From New Prairie Wiki
Jump to navigation Jump to search
Created page with "local mf = require('Module:Mapframe') local getArgs = require('Module:Arguments').getArgs local yesno = require('Module:Yesno') local infoboxImage = require('Module:InfoboxImage').InfoboxImage -- Defaults local DEFAULT_FRAME_WIDTH = "270" local DEFAULT_FRAME_HEIGHT = "200" local DEFAULT_ZOOM = 10 local DEFAULT_GEOMASK_STROKE_WIDTH = "1" local DEFAULT_GEOMASK_STROKE_COLOR = "#777777" local DEFAULT_GEOMASK_FILL = "#888888" local DEFAULT_GEOMASK_FILL_OPACITY = "0.5" local..."
 
No edit summary
 
Line 11: Line 11:
local DEFAULT_GEOMASK_STROKE_COLOR = "#777777"
local DEFAULT_GEOMASK_STROKE_COLOR = "#777777"
local DEFAULT_GEOMASK_FILL = "#888888"
local DEFAULT_GEOMASK_FILL = "#888888"
local DEFAULT_GEOMASK_FILL_OPACITY = "0.5"
local DEFAULT_GEOMASK_FILL_OPACITY = "0.25"
local DEFAULT_SHAPE_STROKE_WIDTH = "3"
local DEFAULT_SHAPE_STROKE_WIDTH = "2"
local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000"
local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000"
local DEFAULT_SHAPE_FILL = "#606060"
local DEFAULT_SHAPE_FILL = "#606060"
local DEFAULT_SHAPE_FILL_OPACITY = "0.5"
local DEFAULT_SHAPE_FILL_OPACITY = "0.1"
local DEFAULT_LINE_STROKE_WIDTH = "5"
local DEFAULT_LINE_STROKE_WIDTH = "5"
local DEFAULT_LINE_STROKE_COLOR = "#FF0000"
local DEFAULT_LINE_STROKE_COLOR = "#FF0000"
local DEFAULT_MARKER_COLOR = "#5E74F3"
local DEFAULT_MARKER_COLOR = "#5E74F3"


local util = {}
function util.noop(info)
local DEFAULT_NOOP_OUTPUT = ""
-- uncomment this when debugging
-- DEFAULT_NOOP_OUTPUT = "debug: mapframe no-op: " .. info
-- mw.log(DEFAULT_NOOP_OUTPUT)
return DEFAULT_NOOP_OUTPUT
end


-- Trim whitespace from args, and remove empty args
-- Trim whitespace from args, and remove empty args
function trimArgs(argsTable)
function util.trimArgs(argsTable)
local cleanArgs = {}
local cleanArgs = {}
for key, val in pairs(argsTable) do
for key, val in pairs(argsTable) do
Line 37: Line 48:
end
end


function getBestStatement(item_id, property_id)
function util.getBestStatement(item_id, property_id)
if not(item_id) or not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then
if not(item_id) or not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then
return false
return false
Line 52: Line 63:
end
end


function hasWikidataProperty(item_id, property_id)
function util.hasWikidataProperty(item_id, property_id)
return getBestStatement(item_id, property_id) and true or false
return util.getBestStatement(item_id, property_id) and true or false
end
end


function getStatementValue(statement)
function util.getStatementValue(statement)
return statement and statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value or nil
return statement and statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value or nil
end
end


function relatedEntity(item_id, property_id)
function util.relatedEntity(item_id, property_id)
local value = getStatementValue( getBestStatement(item_id, property_id) )
local value = util.getStatementValue( util.getBestStatement(item_id, property_id) )
return value and value.id or false
return value and value.id or false
end
end


function idType(id)
function util.idType(id)
if not id then  
if not id then  
return nil
return nil
Line 77: Line 88:
end
end


 
function util.shouldAutoRun(frame)
 
function shouldAutoRun(frame)
-- Check if should be running
-- Check if should be running
local pargs = frame.getParent(frame).args
local pargs = frame.getParent(frame).args
local explicitlyOn = yesno(mw.text.trim(pargs.mapframe or "")) -- true of false or nil
local explicitlyOn = yesno(mw.text.trim(pargs.mapframe or "")) -- true of false or nil
if pargs.coordinates == "{{{coordinates}}}" then explicitlyOn = false end
if pargs.coordinates == "{{{coordinates}}}" then explicitlyOn = false end
if type(pargs.coordinates) == 'string' and string.match(pargs.coordinates, 'coord.missing') then explicitlyOn = false end
local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false) -- true or false
local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false) -- true or false
return explicitlyOn or onByDefault
return explicitlyOn or onByDefault
end
end


function argsFromAuto(frame)
function util.argsFromAuto(frame)
-- Get args from the frame (invoke call) and the parent (template call).
-- Get args from the frame (invoke call) and the parent (template call).
-- Frame arguments are default values which are overridden by parent values
-- Frame arguments are default values which are overridden by parent values
-- when both are present
-- when both are present
local args = getArgs(frame, {parentFirst = true})
local args = getArgs(frame, {parentFirst = true})
 
-- Discard args not prefixed with "mapframe-", remove that prefix from those that remain
-- Discard args not prefixed with "mapframe-", remove that prefix from those that remain
local fixedArgs = {}
local fixedArgs = {}
Line 106: Line 116:
elseif name == "id" or name == "qid" and not fixedArgs.id then
elseif name == "id" or name == "qid" and not fixedArgs.id then
fixedArgs.id = val
fixedArgs.id = val
        -- allow captionstyle to be unprefixed, for compatibility with [[Module:Infobox]]
        elseif name == "captionstyle" and not fixedArgs.captionstyle then
            fixedArgs.captionstyle = val
end
end
end
end
return fixedArgs
return fixedArgs
end
function util.parseCustomWikitext(customWikitext)
-- infoboxImage will format an image if given wikitext containing an
-- image, or else pass through the wikitext unmodified
return infoboxImage({
args = {
image = customWikitext
}
})
end
function util.trackAndWarn(trackingCat, warning)
    local title = mw.title.getCurrentTitle()
    local results = title and title.namespace == 0 and trackingCat and '[[Category:'..trackingCat..']]' or ''
    if warning then
        local warn = require('Module:If preview')._warning
        results = results..warn({warning})
    end
    return results
end
function util.ternary(flag, other)
    other = other or 'other'
    flag = flag == 'none' and 'no' or flag
    local yesNoOut = yesno(flag,other)
    local yes = (yesNoOut == true)
    local no = (yesNoOut == false)
    return yes, no
end
end


local p = {}
local p = {}


p.autocaption = function(frame)
 
if not shouldAutoRun(frame) then return "" end
p._caption = function(args)
local args = argsFromAuto(frame)
if args.caption then
if args.caption then
return args.caption
return args.caption
elseif args.switcher then  
elseif args.switcher then  
return ""
return util.noop("no caption or switcher")
end
end
local maskItem
local maskItem
local maskType = idType(args.geomask)
local maskType = util.idType(args.geomask)
if maskType == 'item' then
if maskType == 'item' then
maskItem = args.geomask
maskItem = args.geomask
elseif maskType == "property" then
elseif maskType == "property" then
maskItem = relatedEntity(args.id or mw.wikibase.getEntityIdForCurrentPage(), args.geomask)
maskItem = util.relatedEntity(args.id or mw.wikibase.getEntityIdForCurrentPage(), args.geomask)
end
end
local maskItemLabel = maskItem and mw.wikibase.getLabel( maskItem )
local maskItemLabel = maskItem and mw.wikibase.getLabel( maskItem )
return maskItemLabel and "Location in "..maskItemLabel or ""
return maskItemLabel and "Location in "..maskItemLabel
end
or util.noop("missing maskItemLabel with type " .. (maskType or "nil") .. " and item " .. (maskItem or "nil"))
 
function parseCustomWikitext(customWikitext)
-- infoboxImage will format an image if given wikitext containing an
-- image, or else pass through the wikitext unmodified
return infoboxImage({
args = {
image = customWikitext
}
})
end
end


p.auto = function(frame)
if not shouldAutoRun(frame) then return "" end
local args = argsFromAuto(frame)
if args.custom then
return frame:preprocess(parseCustomWikitext(args.custom))
end
local mapframe = p._main(args)
return frame:preprocess(mapframe)
end
p.main = function(frame)
local parent = frame.getParent(frame)
local parentArgs = parent.args
local mapframe = p._main(parentArgs)
return frame:preprocess(mapframe)
end


--A list of types for objects that are too small to allow Kartographer to take over zoom
--A list of types for objects that are too small to allow Kartographer to take over zoom
Line 169: Line 187:


p._main = function(_config)
p._main = function(_config)
    -- accumulate tracking cats
    local tracking = ''
-- `config` is the args passed to this module
-- `config` is the args passed to this module
local config = trimArgs(_config)
local config = util.trimArgs(_config)
     -- allow alias for config.coord
     -- allow alias for config.coord
Line 178: Line 198:
local wikidataId = config.id or mw.wikibase.getEntityIdForCurrentPage()
local wikidataId = config.id or mw.wikibase.getEntityIdForCurrentPage()
if not(wikidataId) and not(config.coord) then
if not(wikidataId) and not(config.coord) then
return ''
return false, util.trackAndWarn('Pages using infobox mapframe with missing coordinates')
end
end


-- Require coords (specified or from wikidata), so that map will be centred somewhere
-- Require coords (specified or from wikidata), so that map will be centred somewhere
-- (P625 = coordinate location)
-- (P625 = coordinate location)
local hasCoordinates = hasWikidataProperty(wikidataId, 'P625') or config.coord
    local wdCoordinates = util.getStatementValue(util.getBestStatement(wikidataId, 'P625'))
if not hasCoordinates then
    if not (config.coord or wdCoordinates) then
return ''
return false, util.trackAndWarn('Pages using infobox mapframe with missing coordinates')
end
end


Line 199: Line 219:
args["frame-align"]  = "center"
args["frame-align"]  = "center"


args["frame-coord"] = config["frame-coordinates"] or config["frame-coord"] or ""
args["frame-coord"] = config["frame-coordinates"] or config["frame-coord"]
-- Note: config["coordinates"] or config["coord"] should not be used for the alignment of the frame;
-- Note: config["coordinates"] or config["coord"] should not be used for the alignment of the frame;
-- see talk page ( https://en.wikipedia.org/wiki/Special:Diff/876492931 )
-- see talk page ( https://en.wikipedia.org/wiki/Special:Diff/876492931 )


-- deprecated lat and long parameters
-- deprecated lat and long parameters
args["frame-lat"]    = config["frame-lat"] or config["frame-latitude"] or ""
args["frame-lat"]    = config["frame-lat"] or config["frame-latitude"]
args["frame-long"]  = config["frame-long"] or config["frame-longitude"] or ""
args["frame-long"]  = config["frame-long"] or config["frame-longitude"]


     -- if zoom isn't specified from config:
     -- if zoom isn't specified from config, first check wikidata
     local zoom = config.zoom
     local zoom = config.zoom or util.getStatementValue(util.getBestStatement(wikidataId, 'P6592'))
     if not zoom then
     if not zoom then
    -- Calculate zoom from length or area (converted to km or km2)
    -- Calculate zoom from length or area (converted to km or km2)
Line 218: Line 238:
                         area_ha=config.area_ha, area_acre=config.area_acre,
                         area_ha=config.area_ha, area_acre=config.area_acre,
                         type=config.type, population=config.population,
                         type=config.type, population=config.population,
                viewport_px=math.min(args["frame-width"],args["frame-height"])})
                viewport_px=math.min(args["frame-width"],args["frame-height"]),
                        latitude=wdCoordinates and wdCoordinates.latitude})
     end
     end
     args.zoom = zoom or DEFAULT_ZOOM
     args.zoom = zoom or DEFAULT_ZOOM


-- Conditionals: whether point, geomask should be shown
-- Use OSM relation ID if available; otherwise use geoshape if that is available
local hasOsmRelationId = hasWikidataProperty(wikidataId, 'P402') -- P402 is OSM relation ID
-- (geoshape is required for defunct entities, which are outside OSM's scope)
local shouldShowPointMarker;
local hasOsmRelationId = util.hasWikidataProperty(wikidataId, 'P402') -- P402 is OSM relation ID
local hasGeoshape = util.hasWikidataProperty(wikidataId, 'P3896') -- P3896 is geoshape
local wikidataProvidesGeo = hasOsmRelationId or hasGeoshape


     -- determine marker argument value, determine whether to show marker
     -- determine marker argument value, determine whether to show marker
     local pointValue = config.point
     local forcePoint, suppressPoint = util.ternary(config.point)
    if pointValue == 'none' then
     local forceMarker, suppressMarker = util.ternary(config.marker,true)
        pointValue = 'no'
 
    end
     forcePoint = forcePoint or forceMarker
    pointValue = yesno(pointValue,'other')
     suppressPoint = suppressPoint or suppressMarker
     local markerValue = config.marker
     if markerValue == 'none' then
        markerValue = 'no'
    end
     markerValue = yesno(markerValue,true)


if pointValue == true then
    local showMarker = not suppressPoint and (forcePoint or not wikidataProvidesGeo or config.coord)
shouldShowPointMarker = true
   
elseif pointValue == false then
    -- wikidata = "yes" turns on both shape and line
shouldShowPointMarker = false
    -- wikidata = "no" turns off both shape and line
else
    -- otherwise show both if wikidata provides geo
shouldShowPointMarker = not(hasOsmRelationId) or markerValue or config.coord
    local forceWikidata, suppressWikidata = util.ternary(config.wikidata)
end
    local showShape = not suppressWikidata and (forceWikidata or wikidataProvidesGeo or not config.coord)
    local showLine = showShape


     -- determine shape parameter value, determine whether to show or suppress shape
     -- determine shape parameter value, determine whether to show or suppress shape
     -- also determine whether to invert shape
     -- also determine whether to invert shape
     local shapeValue = config.shape
     local forceShape, suppressShape = util.ternary(config.shape)
    if shapeValue == 'none' then
     showShape = wikidataId and not suppressShape and (forceShape or showShape)
      shapeValue = 'no'
    end
    shapeValue = yesno(shapeValue,'other')
 
     local forceShape = shapeValue == true and hasOsmRelationId
    local suppressShape = shapeValue == false
local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape'
local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape'


     -- determine line parameter value, determine whether to show or suppress line
     -- determine line parameter value, determine whether to show or suppress line
     local lineValue = config.line
     local forceLine, suppressLine = util.ternary(config.line)
    if lineValue == 'none' then
     showLine = wikidataId and not suppressLine and (forceLine or showLine)
      lineValue = 'no'
    end
    local lineValue = yesno(lineValue,'other')
 
     local forceLine = lineValue == true and hasOsmRelationId
    local suppressLine = lineValue == false


     -- determine whether to use wikidata (independent of shape/line)
     local maskItem
local maskItem
local useWikidata = wikidataId -- by default, if wikidata is supplied, use wikidata
-- But do not use wikidata when local coords are specified (and not turned off), unless explicitly set
if useWikidata and config.coord and shouldShowPointMarker then
useWikidata = yesno(config.wikidata,true)
end
-- Switcher
-- Switcher
if config.switcher == "zooms" then
if config.switcher == "zooms" then
Line 290: Line 290:
local maskLabels = {}
local maskLabels = {}
local maskItems = {}
local maskItems = {}
local maskItemId = relatedEntity(wikidataId, "P276") or  relatedEntity(wikidataId, "P131")  
local maskItemId = util.relatedEntity(wikidataId, "P276") or  util.relatedEntity(wikidataId, "P131")  
local maskLabel = mw.wikibase.getLabel(maskItemId)
local maskLabel = mw.wikibase.getLabel(maskItemId)
while maskItemId and maskLabel and mw.text.trim(maskLabel) ~= "" do
while maskItemId and maskLabel and mw.text.trim(maskLabel) ~= "" do
table.insert(maskLabels, maskLabel)
table.insert(maskLabels, maskLabel)
table.insert(maskItems, maskItemId)
table.insert(maskItems, maskItemId)
maskItemId = maskItemId and relatedEntity(maskItemId, "P131")
maskItemId = maskItemId and util.relatedEntity(maskItemId, "P131")
maskLabel = maskItemId and mw.wikibase.getLabel(maskItemId)
maskLabel = maskItemId and mw.wikibase.getLabel(maskItemId)
end
end
Line 323: Line 323:
-- resolve geomask item id (if not using geomask switcher)
-- resolve geomask item id (if not using geomask switcher)
if not maskItem then --   
if not maskItem then --   
local maskType = idType(config.geomask)
local maskType = util.idType(config.geomask)
if maskType == 'item' then
if maskType == 'item' then
maskItem = config.geomask
maskItem = config.geomask
elseif maskType == "property" then
elseif maskType == "property" then
maskItem = relatedEntity(wikidataId, config.geomask)
maskItem = util.relatedEntity(wikidataId, config.geomask)
end
end
end
end


     -- if asking for shape or line from Wikidata
     -- if asking for shape or line from Wikidata
     -- and if Wikidata actually has shape/line data (hasOsmRelationId=true)
     -- and if Wikidata actually has shape/line data (wikidataProvidesGeo=true)
     -- and if no geomask
     -- and if no geomask
     -- and if zoom not explicitly set
     -- and if zoom not explicitly set
     -- and if the object size inferred from its type is not too small
     -- and if the object size inferred from its type is not too small
     -- then let Kartographer "take over" zoom
     -- then let Kartographer "take over" zoom
     if (forceLine or forceShape or (useWikidata and not (suppressShape and suppressLine)))
     if (showLine or showShape) and wikidataProvidesGeo and not maskItem
      and hasOsmRelationId and not maskItem and not config.zoom
      and not config.zoom and not (config.type and tinyType[config.type]) then
      and not (config.type and tinyType[config.type]) then
    args.zoom = nil
    args.zoom = nil
     end
     end
    if not maskItem and not showShape and not showLine and not showMarker then
        return false, util.trackAndWarn('Pages using infobox mapframe with no geometry','No geometry specified for mapframe')
    end
-- Keep track of arg numbering
-- Keep track of arg numbering
Line 367: Line 370:
args["frame-lat"] = nil
args["frame-lat"] = nil
args["frame-long"] = nil
args["frame-long"] = nil
local maskArea = getStatementValue( getBestStatement(maskItem, 'P2046') )
local maskArea = util.getStatementValue( util.getBestStatement(maskItem, 'P2046') )
end
end
incrementArgNumber()
incrementArgNumber()
Line 380: Line 383:
-- Shape (or shape-inverse)
-- Shape (or shape-inverse)
if forceShape or (useWikidata and not suppressShape) then
if showShape then
args["type"..argNumber] = shapeType
args["type"..argNumber] = shapeType
if config.id then args["id"..argNumber] = config.id end
if hasGeoshape and not hasOsmRelationId then
args["from"..argNumber] = string.sub( util.getStatementValue( util.getBestStatement(wikidataId, 'P3896') ), 6)
elseif config.id then  
args["id"..argNumber] = config.id
end
args["stroke-width"..argNumber] = config["shape-stroke-width"] or config["stroke-width"] or DEFAULT_SHAPE_STROKE_WIDTH
args["stroke-width"..argNumber] = config["shape-stroke-width"] or config["stroke-width"] or DEFAULT_SHAPE_STROKE_WIDTH
args["stroke-color"..argNumber] = config["shape-stroke-color"] or config["shape-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_SHAPE_STROKE_COLOR
args["stroke-color"..argNumber] = config["shape-stroke-color"] or config["shape-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_SHAPE_STROKE_COLOR
Line 391: Line 398:
-- Line
-- Line
if forceLine or (useWikidata and not suppressLine) then
if showLine then
args["type"..argNumber] = "line"
args["type"..argNumber] = "line"
if config.id then args["id"..argNumber] = config.id end
if hasGeoshape and not hasOsmRelationId then
args["from"..argNumber] = string.sub( util.getStatementValue( util.getBestStatement(wikidataId, 'P3896') ), 6)
elseif config.id then  
args["id"..argNumber] = config.id
end
args["stroke-width"..argNumber] = config["line-stroke-width"] or config["stroke-width"] or DEFAULT_LINE_STROKE_WIDTH
args["stroke-width"..argNumber] = config["line-stroke-width"] or config["stroke-width"] or DEFAULT_LINE_STROKE_WIDTH
args["stroke-color"..argNumber] = config["line-stroke-color"] or config["line-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_LINE_STROKE_COLOR
args["stroke-color"..argNumber] = config["line-stroke-color"] or config["line-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_LINE_STROKE_COLOR
Line 399: Line 410:
end
end


-- Point
-- Point marker
if shouldShowPointMarker then
if showMarker then
args["type"..argNumber] = "point"
args["type"..argNumber] = "point"
if config.id then args["id"..argNumber] = config.id end
if config.id then args["id"..argNumber] = config.id end
Line 408: Line 419:
incrementArgNumber()
incrementArgNumber()
end
end
    -- if Wikidata doesn't link to OSM and the map has no mask or point,
    -- then center the map on the coordinates either from the infobox or from wikidata
    if not maskItem and not showMarker and not wikidataProvidesGeo then
        if config.coord then
            args["frame-coord"] = args["frame-coord"] or config.coord
        else
            args["frame-lat"] = args["frame-lat"] or wdCoordinates.latitude
            args["frame-long"] = args["frame-long"] or wdCoordinates.longitude
        end
        tracking = tracking..util.trackAndWarn('Pages using infobox mapframe with forced centering')
    end
    -- protect against nil frame arguments
    args["frame-coord"] = args["frame-coord"] or ""
    args["frame-lat"] = args["frame-lat"] or ""
    args["frame-long"] = args["frame-long"] or ""


local mapframe = args.switch and mf.multi(args) or mf._main(args)
local mapframe = args.switch and mf.multi(args) or mf._main(args)
local tracking = hasOsmRelationId and '' or '[[Category:Infobox mapframe without OSM relation ID on Wikidata]]'
tracking = tracking..((showLine or showShape) and not wikidataProvidesGeo
return mapframe .. tracking
                          and util.trackAndWarn('Pages using infobox mapframe without shape links in Wikidata')
                          or '')
return true, mapframe.. tracking
end
 
-- Entry points
 
p.main = function(frame)
local parent = frame.getParent(frame)
local parentArgs = parent.args
local _, mapframe = p._main(parentArgs)
return frame:preprocess(mapframe)
end
 
p.auto = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("auto should not autorun")
end
local args = util.argsFromAuto(frame)
if args.custom then
return frame:preprocess(util.parseCustomWikitext(args.custom))
end
local _, mapframe = p._main(args)
return frame:preprocess(mapframe)
end
end


p.autocaption = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("autocaption should not autorun")
end
local args = util.argsFromAuto(frame)
    local caption = p._caption(args)
    return caption
end
p.autoWithCaption = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("autoWithCaption should not autorun")
end
local args = util.argsFromAuto(frame)
    local wikitext
    local caption
    local ok
    if args.custom then
        ok = true
        wikitext = util.parseCustomWikitext(args.custom)
    else
        ok, wikitext = p._main(args)
    end
    if not ok then return wikitext end
    wikitext = frame:preprocess(wikitext)
    caption = p._caption(args)
    local data = mw.html.create():wikitext(wikitext)
    data:tag('div')
    :addClass('infobox-caption')
:cssText(args.captionstyle)
:wikitext(caption)
    return tostring(data)
end
   
return p
return p

Latest revision as of 16:13, 13 July 2026

Lua error in Module:Lua_banner at line 113: attempt to index field 'edit' (a nil value).

This module implements {{Infobox mapframe}}.

See Template:Infobox mapframe/testcases for tests.

Usage

Module that automatically makes a mapframe suitable for an infobox automatically, with minimal user input.

Generic usage

Just use the template {{Infobox mapframe}}, following the documentation there. This module may also be imported to another Lua module.

Automatic maps in infoboxes

Example edits: Template:Infobox prison, Template:Infobox prison/doc

Edit the infobox template (or its sandbox). Add lines like the following examples to the infobox. Replace numbers (4, or 97 to 99), with the appropriate number based on how many other image or data parameters are already present.

If placing near the top of infobox If placing at/near the bottom of the infobox
| image4     = {{#invoke:Infobox mapframe|autoWithCaption}}
| header97   = {{#if:{{{mapframe|}}}|Location}}
| data98     = {{#invoke:Infobox mapframe|autoWithCaption}}

If the template has a call to {{#invoke:Check for unknown parameters|check}}, simply add |mapframe_args=y to the call and the various mapframe args will automatically be included.

Once this is done, the above parameters will be available to users of the template.

  • Defaults values for these parameters can also be specified in the #invoke calls above, e.g.
{{#invoke:Infobox mapframe
  | autoWithCaption
  | mapframe-marker = library
}}
means that the library marker will be used, unless a different value is passed in to the template.
  • The maps are off by default, which means maps will not be displayed unless |mapframe=yes is present in the template call.
    To turn maps on by default, in the #invoke calls above add |onByDefault=yes – which means maps will be displayed unless |mapframe=no is present in the template call.
    onByDefault can also be set to a conditional, such as if another parameters is present, e.g. |onByDefault={{#if:{{{pushpin_map|}}}|no|yes}}
  • Add the new parameters to the template documentation.
    You can use {{Infobox mapframe/doc/parameters}}.
    If any default values are modified in the #invoke calls, add them as parameters in the form Template:Ttparameter-nameTemplate:Ttvalue.
    The default output is shown here collapsed:
Extended content
  • Basic {{Infobox mapframe}} parameters:
    • mapframe – Specify yes or no to show or hide the map, overriding the default. Default: no
      • Internal onByDefault parameter: not specified
    • mapframe-coordinates or mapframe-coord or coordinates or coord – Coordinates to use, instead of any on Wikidata. Use the {{Coord}} template, e.g. |coord={{Coord|12.34|N|56.78|E}}. Default: coordinates from Wikidata d:Property:P625
    • mapframe-caption – Caption for the map. Default: blank (or if |mapframe-geomask= is set, "Location in geomask's label")
    • mapframe-custom – Use a custom map instead of the automatic mapframe. Specify either a {{maplink}} template, or another template that generates a mapframe map, or an image name. If used, most mapframe parameters will be ignored. Default: not specified
    • mapframe-id or id or qid – Id (Q-number) of Wikidata item to use. Default: item for current page
    • mapframe-wikidata – Set to yes to show all shape/line features from OpenStreetMap. Default: not specified
  • Customise which features are displayed:
  • Other optional parameters:
    • mapframe-frame-width or mapframe-width – Frame width. Default: 270
    • mapframe-frame-height or mapframe-height – Frame height. Default: 200
    • mapframe-shape-fill – Color used to fill shape features. Default: #606060
    • mapframe-shape-fill-opacity – Opacity level of shape fill, a number between 0 and 1. Default: 0.1
    • mapframe-stroke-color or mapframe-stroke-colour – Color of line features, and outlines of shape features. Default: #ff0000
      • mapframe-line-stroke-color or mapframe-line-stroke-colour – Color of line features. Default: #ff0000
      • mapframe-shape-stroke-color or mapframe-shape-stroke-colour – Color of outlines of shape features. Default: #ff0000
    • mapframe-stroke-width – Width of line features, and outlines of shape features. Default: 2
      • mapframe-shape-stroke-width – Width of outlines of shape features. Default: 2
      • mapframe-line-stroke-width – Width of line features. Default: 5
    • mapframe-marker – Marker symbol to use for coordinates; see mw:Help:Extension:Kartographer/Icons for options. Default: not specified (i.e. blank)
      • Can also be set to blank to override any default marker symbol
    • mapframe-marker-color or mapframe-marker-colour – Background color for the marker. Default: #5E74F3
    • mapframe-geomask-stroke-color or mapframe-geomask-stroke-colour – Color of outline of geomask shape. Default: #555555
    • mapframe-geomask-stroke-width – Width of outline of geomask shape. Default: 1
    • mapframe-geomask-fill – Color used to fill outside geomask features. Default: #606060
    • mapframe-geomask-fill-opacity – Opacity level of fill outside geomask features, a number between 0 and 1. Default: 0.25
    • mapframe-zoom – Set the zoom level, from "1" (map of earth) to "18" (maximum zoom in), to be used if the zoom level cannot be determined automatically from object length or area. Default: 10
    • mapframe-length_km or mapframe-length_mi or mapframe-area_km2 or mapframe-area_mi2 – Determine zoom level by passing in object's length (in kilometres or miles) or area (in square kilometres or square miles). Default: not specified / not specified / not specified / not specified
    • mapframe-frame-coordinates or mapframe-frame-coord – Alternate latitude and longitude coordinates for initial placement of map, using {{coord}}. Default: derived from item coordinates
    • mapframe-type – Set the type of the object, with the same usage as {{coord}}. Default: not specified
    • mapframe-population – Set the population, useful with type="city", for {{Infobox dim}}. Default: not specified

See also



local mf = require('Module:Mapframe')
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local infoboxImage = require('Module:InfoboxImage').InfoboxImage

-- Defaults
local DEFAULT_FRAME_WIDTH = "270"
local DEFAULT_FRAME_HEIGHT = "200"
local DEFAULT_ZOOM = 10
local DEFAULT_GEOMASK_STROKE_WIDTH = "1"
local DEFAULT_GEOMASK_STROKE_COLOR = "#777777"
local DEFAULT_GEOMASK_FILL = "#888888"
local DEFAULT_GEOMASK_FILL_OPACITY = "0.25"
local DEFAULT_SHAPE_STROKE_WIDTH = "2"
local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000"
local DEFAULT_SHAPE_FILL = "#606060"
local DEFAULT_SHAPE_FILL_OPACITY = "0.1"
local DEFAULT_LINE_STROKE_WIDTH = "5"
local DEFAULT_LINE_STROKE_COLOR = "#FF0000"
local DEFAULT_MARKER_COLOR = "#5E74F3"

local util = {}

function util.noop(info)
	local DEFAULT_NOOP_OUTPUT = ""

	-- uncomment this when debugging
	-- DEFAULT_NOOP_OUTPUT = "debug: mapframe no-op: " .. info
	-- mw.log(DEFAULT_NOOP_OUTPUT)

	return DEFAULT_NOOP_OUTPUT
end

-- Trim whitespace from args, and remove empty args
function util.trimArgs(argsTable)
	local cleanArgs = {}
	for key, val in pairs(argsTable) do
		if type(val) == 'string' then
			val = val:match('^%s*(.-)%s*$')
			if val ~= '' then
				cleanArgs[key] = val
			end
		else
			cleanArgs[key] = val
		end
	end
	return cleanArgs
end

function util.getBestStatement(item_id, property_id)
	if not(item_id) or not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then
		return false
	end
	local statements = mw.wikibase.getBestStatements(item_id, property_id)
	if not statements or #statements == 0 then
		return false
	end
	local hasNoValue = ( statements[1].mainsnak and statements[1].mainsnak.snaktype == 'novalue' )
	if hasNoValue then
		return false
	end
	return statements[1]
end

function util.hasWikidataProperty(item_id, property_id)
	return util.getBestStatement(item_id, property_id) and true or false
end

function util.getStatementValue(statement)
	return statement and statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value or nil
end

function util.relatedEntity(item_id, property_id)
	local value = util.getStatementValue( util.getBestStatement(item_id, property_id) )
	return value and value.id or false
end

function util.idType(id)
	if not id then 
		return nil
	elseif mw.ustring.match(id, "[Pp]%d+") then
		return "property"
	elseif mw.ustring.match(id, "[Qq]%d+") then
		return "item"
	else
		return nil
	end
end

function util.shouldAutoRun(frame)
	-- Check if should be running
	local pargs = frame.getParent(frame).args
	local explicitlyOn = yesno(mw.text.trim(pargs.mapframe or "")) -- true of false or nil
	if pargs.coordinates == "{{{coordinates}}}" then explicitlyOn = false end
	if type(pargs.coordinates) == 'string' and string.match(pargs.coordinates, 'coord.missing') then explicitlyOn = false end
	local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false) -- true or false
	return explicitlyOn or onByDefault
end

function util.argsFromAuto(frame)
	-- Get args from the frame (invoke call) and the parent (template call).
	-- Frame arguments are default values which are overridden by parent values
	-- when both are present
	local args = getArgs(frame, {parentFirst = true})

	-- Discard args not prefixed with "mapframe-", remove that prefix from those that remain
	local fixedArgs = {}
	for name, val in pairs(args) do
		local fixedName = string.match(name, "^mapframe%-(.+)$" )
		if fixedName then
			fixedArgs[fixedName] = val
		-- allow coord, coordinates, etc to be unprefixed
		elseif name == "coordinates" or name == "coord" or name == "coordinate" and not fixedArgs.coord then
			fixedArgs.coord = val
		-- allow id, qid to be unprefixed, map to id (if not already present)
		elseif name == "id" or name == "qid" and not fixedArgs.id then
			fixedArgs.id = val
        -- allow captionstyle to be unprefixed, for compatibility with [[Module:Infobox]]
        elseif name == "captionstyle" and not fixedArgs.captionstyle then
            fixedArgs.captionstyle = val
		end
	end
	return fixedArgs
end


function util.parseCustomWikitext(customWikitext)
	-- infoboxImage will format an image if given wikitext containing an
	-- image, or else pass through the wikitext unmodified
	return infoboxImage({
		args = {
			image = customWikitext
		}
	})
end

function util.trackAndWarn(trackingCat, warning)
    local title = mw.title.getCurrentTitle()
    local results = title and title.namespace == 0 and trackingCat and '[[Category:'..trackingCat..']]' or ''
    if warning then
        local warn = require('Module:If preview')._warning
        results = results..warn({warning})
    end
    return results
end


function util.ternary(flag, other)
    other = other or 'other'
    flag = flag == 'none' and 'no' or flag
    local yesNoOut = yesno(flag,other)
    local yes = (yesNoOut == true)
    local no = (yesNoOut == false)
    return yes, no
end

local p = {}


p._caption = function(args)
	if args.caption then
		return args.caption
	elseif args.switcher then 
		return util.noop("no caption or switcher")
	end
	local maskItem
	local maskType = util.idType(args.geomask)
	if maskType == 'item' then
		maskItem = args.geomask
	elseif maskType == "property" then
		maskItem = util.relatedEntity(args.id or mw.wikibase.getEntityIdForCurrentPage(), args.geomask)
	end
	local maskItemLabel = maskItem and mw.wikibase.getLabel( maskItem )
	return maskItemLabel and "Location in "..maskItemLabel
		or util.noop("missing maskItemLabel with type " .. (maskType or "nil") .. " and item " .. (maskItem or "nil"))
end


--A list of types for objects that are too small to allow Kartographer to take over zoom
local tinyType = {
    landmark=true,
    railwaystation=true,
    edu=true,
    pass=true,
    camera=true
}                 

p._main = function(_config)
    -- accumulate tracking cats
    local tracking = ''
	-- `config` is the args passed to this module
	local config = util.trimArgs(_config)
	
    -- allow alias for config.coord
    config.coord = config.coord or config.coordinates

	-- Require wikidata item, or specified coords
	local wikidataId = config.id or mw.wikibase.getEntityIdForCurrentPage()
	if not(wikidataId) and not(config.coord) then
		return false, util.trackAndWarn('Pages using infobox mapframe with missing coordinates')
	end

	-- Require coords (specified or from wikidata), so that map will be centred somewhere
	-- (P625 = coordinate location)
    local wdCoordinates = util.getStatementValue(util.getBestStatement(wikidataId, 'P625'))
    if not (config.coord or wdCoordinates) then
		return false, util.trackAndWarn('Pages using infobox mapframe with missing coordinates')
	end

	-- `args` is the arguments which will be passed to the mapframe module
	local args = {}

	-- Some defaults/overrides for infobox presentation
	args.display = "inline"
	args.frame = "yes"
	args.plain = "yes"
	args["frame-width"]  = config["frame-width"] or config.width or DEFAULT_FRAME_WIDTH
	args["frame-height"] = config["frame-height"] or config.height or DEFAULT_FRAME_HEIGHT
	args["frame-align"]  = "center"

	args["frame-coord"] = config["frame-coordinates"] or config["frame-coord"]
	-- Note: config["coordinates"] or config["coord"] should not be used for the alignment of the frame;
	-- see talk page ( https://en.wikipedia.org/wiki/Special:Diff/876492931 )

	-- deprecated lat and long parameters
	args["frame-lat"]    = config["frame-lat"] or config["frame-latitude"]
	args["frame-long"]   = config["frame-long"] or config["frame-longitude"]

    -- if zoom isn't specified from config, first check wikidata
    local zoom = config.zoom or util.getStatementValue(util.getBestStatement(wikidataId, 'P6592'))
    if not zoom then
	    -- Calculate zoom from length or area (converted to km or km2)
	    -- Zoom so that length or area is completely included in mapframe
	    local getZoom = require('Module:Infobox dim')._zoom
	    zoom = getZoom({length_km=config.length_km, length_mi=config.length_mi,
                        width_km=config.width_km, width_mi=config.width_mi,
		                area_km2=config.area_km2, area_mi2=config.area_mi2,
                        area_ha=config.area_ha, area_acre=config.area_acre,
                        type=config.type, population=config.population,
		                viewport_px=math.min(args["frame-width"],args["frame-height"]),
                        latitude=wdCoordinates and wdCoordinates.latitude})
    end
    args.zoom = zoom or DEFAULT_ZOOM

	-- Use OSM relation ID if available; otherwise use geoshape if that is available
	-- (geoshape is required for defunct entities, which are outside OSM's scope)
	local hasOsmRelationId = util.hasWikidataProperty(wikidataId, 'P402') -- P402 is OSM relation ID
	local hasGeoshape = util.hasWikidataProperty(wikidataId, 'P3896') -- P3896 is geoshape
	local wikidataProvidesGeo = hasOsmRelationId or hasGeoshape

    -- determine marker argument value, determine whether to show marker
    local forcePoint, suppressPoint = util.ternary(config.point)
    local forceMarker, suppressMarker = util.ternary(config.marker,true)

    forcePoint = forcePoint or forceMarker
    suppressPoint = suppressPoint or suppressMarker

    local showMarker = not suppressPoint and (forcePoint or not wikidataProvidesGeo or config.coord)
    
    -- wikidata = "yes" turns on both shape and line
    -- wikidata = "no" turns off both shape and line
    -- otherwise show both if wikidata provides geo
    local forceWikidata, suppressWikidata = util.ternary(config.wikidata)
    local showShape = not suppressWikidata and (forceWikidata or wikidataProvidesGeo or not config.coord)
    local showLine = showShape

    -- determine shape parameter value, determine whether to show or suppress shape
    -- also determine whether to invert shape
    local forceShape, suppressShape = util.ternary(config.shape)
    showShape = wikidataId and not suppressShape and (forceShape or showShape)
	local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape'

    -- determine line parameter value, determine whether to show or suppress line
    local forceLine, suppressLine = util.ternary(config.line)
    showLine = wikidataId and not suppressLine and (forceLine or showLine)

    local maskItem
	-- Switcher
	if config.switcher == "zooms" then
		-- switching between zoom levels
		local maxZoom = math.max(tonumber(args.zoom), 3) -- what zoom would have otherwise been (if 3 or more, otherwise 3)
		local minZoom = 1 -- completely zoomed out
		local midZoom = math.floor((maxZoom + minZoom)/2) -- midway between maxn and min
		args.switch = "zoomed in, zoomed midway, zoomed out"
		args.zoom = string.format("SWITCH:%d,%d,%d", maxZoom, midZoom, minZoom)
	elseif config.switcher == "auto" then
		-- switching between P276 and P131 areas with recursive lookup, e.g. item's city,
		-- that city's state, and that state's country
		args.zoom = nil -- let kartographer determine the zoom
		local maskLabels = {}
		local maskItems = {}
		local maskItemId = util.relatedEntity(wikidataId, "P276") or  util.relatedEntity(wikidataId, "P131") 
		local maskLabel = mw.wikibase.getLabel(maskItemId)
		while maskItemId and maskLabel and mw.text.trim(maskLabel) ~= "" do
			table.insert(maskLabels, maskLabel)
			table.insert(maskItems, maskItemId)
			maskItemId = maskItemId and util.relatedEntity(maskItemId, "P131")
			maskLabel = maskItemId and mw.wikibase.getLabel(maskItemId)
		end
		if #maskLabels > 1 then
			args.switch = table.concat(maskLabels, "###")
			maskItem = "SWITCH:" .. table.concat(maskItems, ",")
		elseif #maskLabels == 1 then
			maskItem = maskItemId[1]
		end
	elseif config.switcher == "geomasks" and config.geomask then
		-- switching between items in geomask parameter
		args.zoom = nil -- let kartographer determine the zoom
		local separator = (mw.ustring.find(config.geomask, "###", 0, true ) and "###") or
			(mw.ustring.find(config.geomask, ";", 0, true ) and ";") or ","
		local pattern = "%s*"..separator.."%s*"
		local maskItems = mw.text.split(mw.ustring.gsub(config.geomask, "SWITCH:", ""), pattern)
		local maskLabels = {}
		if #maskItems > 1 then
			for i, item in ipairs(maskItems) do
				table.insert(maskLabels, mw.wikibase.getLabel(item))
			end
			args.switch = table.concat(maskLabels, "###")
			maskItem = "SWITCH:" .. table.concat(maskItems, ",")
		end
	end
	
	-- resolve geomask item id (if not using geomask switcher)
	if not maskItem then --  
		local maskType = util.idType(config.geomask)
		if maskType == 'item' then
			maskItem = config.geomask
		elseif maskType == "property" then
			maskItem = util.relatedEntity(wikidataId, config.geomask)
		end
	end

    -- if asking for shape or line from Wikidata
    -- and if Wikidata actually has shape/line data (wikidataProvidesGeo=true)
    -- and if no geomask
    -- and if zoom not explicitly set
    -- and if the object size inferred from its type is not too small
    -- then let Kartographer "take over" zoom
    if (showLine or showShape) and wikidataProvidesGeo and not maskItem
       and not config.zoom and not (config.type and tinyType[config.type]) then
	    args.zoom = nil
    end	

    if not maskItem and not showShape and not showLine and not showMarker then
        return false, util.trackAndWarn('Pages using infobox mapframe with no geometry','No geometry specified for mapframe')
    end
	
	-- Keep track of arg numbering
	local argNumber = ''
	local function incrementArgNumber()
		if argNumber == '' then
			argNumber = 2
		else
			argNumber = argNumber + 1
		end
	end
	
	-- Geomask
	if maskItem then
		args["type"..argNumber] = "shape-inverse"
		args["id"..argNumber] = maskItem
		args["stroke-width"..argNumber] = config["geomask-stroke-width"] or DEFAULT_GEOMASK_STROKE_WIDTH
		args["stroke-color"..argNumber] = config["geomask-stroke-color"] or config["geomask-stroke-colour"] or DEFAULT_GEOMASK_STROKE_COLOR
		args["fill"..argNumber] = config["geomask-fill"] or DEFAULT_GEOMASK_FILL
		args["fill-opacity"..argNumber] = config["geomask-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY
		-- Let kartographer determine zoom and position, unless it is explicitly set in config
		if not config.zoom and not config.switcher then
			args.zoom = nil
			args["frame-coord"] = nil
			args["frame-lat"] = nil
			args["frame-long"] = nil 	
			local maskArea = util.getStatementValue( util.getBestStatement(maskItem, 'P2046') )
		end
		incrementArgNumber()
		-- Hack to fix phab:T255932
		if not args.zoom then
			args["type"..argNumber] = "line"
			args["id"..argNumber] = maskItem
			args["stroke-width"..argNumber] = 0
			incrementArgNumber()
		end
	end
	
	-- Shape (or shape-inverse)
	if showShape then
		args["type"..argNumber] = shapeType
		if hasGeoshape and not hasOsmRelationId then
			args["from"..argNumber] = string.sub( util.getStatementValue( util.getBestStatement(wikidataId, 'P3896') ), 6)
		elseif config.id then 
			args["id"..argNumber] = config.id
		end
		args["stroke-width"..argNumber] = config["shape-stroke-width"] or config["stroke-width"] or DEFAULT_SHAPE_STROKE_WIDTH
		args["stroke-color"..argNumber] = config["shape-stroke-color"] or config["shape-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_SHAPE_STROKE_COLOR
		args["fill"..argNumber] = config["shape-fill"] or DEFAULT_SHAPE_FILL
		args["fill-opacity"..argNumber] = config["shape-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY
		incrementArgNumber()
	end
	
	-- Line
	if showLine then
		args["type"..argNumber] = "line"
		if hasGeoshape and not hasOsmRelationId then
			args["from"..argNumber] = string.sub( util.getStatementValue( util.getBestStatement(wikidataId, 'P3896') ), 6)
		elseif config.id then 
			args["id"..argNumber] = config.id
		end
		args["stroke-width"..argNumber] = config["line-stroke-width"] or config["stroke-width"] or DEFAULT_LINE_STROKE_WIDTH
		args["stroke-color"..argNumber] = config["line-stroke-color"] or config["line-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_LINE_STROKE_COLOR
		incrementArgNumber()
	end

	-- Point marker
	if showMarker then
		args["type"..argNumber] = "point"
		if config.id then args["id"..argNumber] = config.id end
		if config.coord then args["coord"..argNumber] = config.coord end
		if config.marker then args["marker"..argNumber] = config.marker end
		args["marker-color"..argNumber] = config["marker-color"] or config["marker-colour"] or DEFAULT_MARKER_COLOR
		incrementArgNumber()
	end

    -- if Wikidata doesn't link to OSM and the map has no mask or point,
    -- then center the map on the coordinates either from the infobox or from wikidata
    if not maskItem and not showMarker and not wikidataProvidesGeo then
        if config.coord then
            args["frame-coord"] = args["frame-coord"] or config.coord
        else
            args["frame-lat"] = args["frame-lat"] or wdCoordinates.latitude
            args["frame-long"] = args["frame-long"] or wdCoordinates.longitude
        end
        tracking = tracking..util.trackAndWarn('Pages using infobox mapframe with forced centering')
    end

    -- protect against nil frame arguments
    args["frame-coord"] = args["frame-coord"] or ""
    args["frame-lat"] = args["frame-lat"] or ""
    args["frame-long"] = args["frame-long"] or ""

	local mapframe = args.switch and mf.multi(args) or mf._main(args)
	tracking = tracking..((showLine or showShape) and not wikidataProvidesGeo 
                          and util.trackAndWarn('Pages using infobox mapframe without shape links in Wikidata')
                          or '')
	return true, mapframe.. tracking
end

-- Entry points

p.main = function(frame)
	local parent = frame.getParent(frame)
	local parentArgs = parent.args
	local _, mapframe = p._main(parentArgs)
	return frame:preprocess(mapframe)
end

p.auto = function(frame)
	if not util.shouldAutoRun(frame) then
		return util.noop("auto should not autorun")
	end
	local args = util.argsFromAuto(frame)
	if args.custom then
		return frame:preprocess(util.parseCustomWikitext(args.custom))
	end
	local _, mapframe = p._main(args)
	return frame:preprocess(mapframe)
end

p.autocaption = function(frame)
	if not util.shouldAutoRun(frame) then
		return util.noop("autocaption should not autorun")
	end
	local args = util.argsFromAuto(frame)
    local caption = p._caption(args)
    return caption
end

p.autoWithCaption = function(frame)
	if not util.shouldAutoRun(frame) then
		return util.noop("autoWithCaption should not autorun")
	end
	local args = util.argsFromAuto(frame)
    local wikitext
    local caption
    local ok
    if args.custom then
        ok = true
        wikitext = util.parseCustomWikitext(args.custom)
    else
        ok, wikitext = p._main(args)
    end
    if not ok then return wikitext end
    wikitext = frame:preprocess(wikitext)
    caption = p._caption(args)
    local data = mw.html.create():wikitext(wikitext)
    data:tag('div')
	    :addClass('infobox-caption')
		:cssText(args.captionstyle)
		:wikitext(caption)
    return tostring(data)
end
    
return p