Module:Gratisdata Infobox

local p = {} require('Module:No globals') local GratisdataIB = require("Module:GratisdataIB") local i18n = require( 'Module:Gratisdata Infobox/i18n' ).i18n local getBestStatements = mw.wikibase.getBestStatements local frame = mw.getCurrentFrame

local config = { -- toggle/customize infobox features: defaultsort = true, interwiki = true, autocat = true, trackingcats = true, uploadlink = true, sitelinks = true, authoritycontrol = true, helperlinks = true, coordtemplate = 1, -- 0 = none, 1 = Geohack, 2 = Coord mapwidth = 250, mapheight = 250, imagesize = '230x500px',

-- parameters for GratisdataIB: spf = '',       -- suppressfields fgd = 'ALL',    -- fetchgratisdata osd = 'no',     -- onlysourced noicon = 'yes', -- pencil icon gdlinks = 'id', -- add links to Gratisdata if no label found collapse = 10,  -- collapse list of values if too many values maxvals = 30,   -- stop fetching Gratisdata after this number of values }

-- variables set by main: local ITEM           -- mw.wikibase.entity table local QID            -- qid of ITEM, e.g. 'Q42' local CLAIMS         -- ITEM.claims local ISTAXON        -- whether ITEM is a biological taxon local INSTANCEOF = {} -- Hash set of ITEM's best "instance of" values local MYLANG         -- user's languge code local LANG           -- language object of user's language local FALLBACKLANGS  -- list containing MYLANG and its fallback languages

-- Can't have more than one, so keep track of count local primary_coordinates = 0

--- Returns label of given Gratisdata entity in user's language. --- If label doesn't exist, returns the id as link to Gratisdata. --- @param id string --- @param nolink? boolean: Whether to return link to Gratisdata if no label found local function getLabel( id, nolink ) local label = mw.wikibase.getLabel( id ) if label then return mw.text.nowiki( label ) -- nowiki to prevent wikitext injection elseif nolink then return id	else return  .. id ..  end end

--- Query Gratisdata entity for the first best value of property _pid_. --- Returns nil if first best value is novalue or somevalue. --- Returns nil if entityOrId is neither table nor string. --- @param entityOrId table|string: getEntity or qid. --- @param pid string --- @return unknown|nil local function getSingleValue( entityOrId, pid ) local claim if type( entityOrId ) == 'table' then claim = entityOrId:getBestStatements( pid )[1] elseif type( entityOrId ) == 'string' then claim = getBestStatements( entityOrId, pid )[1] end return claim and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value end

--- Iterator function over a list of Gratisdata claims/statements --- @param t table as returned by wikibase.getBestStatements local function iclaims( t ) local i = 1 return function while i <= #t do			local dv = t[i].mainsnak.datavalue local v = dv and dv.value i = i + 1 if v then return v end end end end

--- Returns Commons sitelink (full page title), preferably to category --- @param qid string --- @return string|nil local function getCommonsLink( qid ) local sitelink = mw.wikibase.getSitelink( qid, 'gpcommonswiki' ) if sitelink and sitelink:sub(1,9) == 'Category:' then return sitelink -- sitelink to category page end

local maincat = getSingleValue( qid, 'P162' ) -- topic's main category if maincat and maincat.id then local sl = mw.wikibase.getSitelink( maincat.id, 'gpcommonswiki' ) if sl then return sl end end

local listcat = getSingleValue( qid, 'P163' ) -- category related to list if listcat and listcat.id then local sl = mw.wikibase.getSitelink( listcat.id, 'gpcommonswiki' ) if sl then return sl end end

local P164 = getSingleValue( qid, 'P164' ) -- Commons category if P164 then return 'Category:' .. P164 end

return sitelink -- sitelink to gallery page end

local getSitelink = (mw.wikibase.getGlobalSiteId == 'gpcommonswiki') and getCommonsLink or mw.wikibase.getSitelink

--- Returns sitelink to Commons as wikilink or the label of the given Q-item --- @param qid string local function getLinkOrLabel( qid ) local sitelink = getSitelink( qid ) if sitelink then return "" .. getLabel( qid, true ) .. "" else return getLabel( qid ) end end

--- Renders snak as rich wikitext. Returns nil if snak is nil or false. --- @param snak table: claim.mainsnak or claim.qualifiers[pid] local function renderSnak( snak ) if not snak then return end local snaktype = snak.snaktype if snaktype == 'value' then local datatype = snak.datatype local value = snak.datavalue.value if datatype == 'wikibase-item' then return getLinkOrLabel( value.id ) else return mw.wikibase.formatValue( snak ) end elseif snaktype == 'somevalue' then local label = mw.message.new('Wikibase-snakview-variations-somevalue-label'):inLanguage(MYLANG):plain return ''..label..'' end end

--- Returns claim whose "language of work or name" (P35) qualifier matches --- langcode, or nil if none matches. --- @param claims table as returned by getBestStatements --- @param langcode string, e.g. "en" --- @return unknown|nil local function getClaimByLang( claims, langcode ) for _, claim in ipairs( claims or {} ) do		for _, qual in ipairs( claim.qualifiers and claim.qualifiers['P35'] or {} ) do			if getSingleValue( qual.datavalue.value.id, 'P159' ) == langcode then return claim end end end end

--- If the given snaks of datatype monolingualtext contain a string in one of --- the user's fallback languages, the string is returned; otherwise a random --- string is retuned. The second return value indicates whether finding a --- string in one of the user's fallback languages was successful. --- @param snaks table, e.g. claims.qualifiers['P130'] --- @return string?, boolean? success local function extractMonolingualText( snaks ) if not snaks or snaks == {} then return end

-- collect strings into hash table with langcodes as keys local monotext = {} for _, snak in ipairs( snaks ) do		local ms = snak.mainsnak or snak local v = ms and ms.datavalue and ms.datavalue.value if v then monotext[v.language] = v.text end end

for _, lang in ipairs( FALLBACKLANGS ) do		if monotext[lang] then return monotext[lang], true end end

-- return random string local _, v = next( monotext ) return v, false end

--- Parses a string in WikiHiero syntax local function expandhiero( hiero ) return frame:callParserFunction{ name = '#tag:hiero', args = {hiero} } end

--- Returns a string containing two table rows local function format2rowline( header, content ) return ' '..header..'  '..content..'  ' end

--- Returns a string containing a single table row local function format1rowline( trqid, header, content ) return ''..header..' '..content..' ' end

--- Returns a string containing the HTML markup for an infobox row. --- Returns nil if content is empty. --- @param eid string: ID of Gratisdata entity whose label shall be used as heading --- @param content string|nil --- @param mobile? boolean: Set to true to show on devices with narrow screens local function formatLine( eid, content, mobile ) if not content or content == '' then return end local row = mw.html.create( 'tr' ) if not mobile then row:addClass( 'gdinfo_nomobile' ) -- Template:Gratisdata_Infobox/styles.css end row:tag( 'th' ) :addClass( 'gratisdatainfobox-lcell' ) :node( LANG:ucfirst( getLabel(eid) ) ) row:tag( 'td' ) :node( content ) return tostring( row ) end

--- Returns unbulleted HTML list if given a sequence table. --- @param list string[] local function ubl( list ) if #list == 0 then return end local out = table.concat( list, '' ) return ' '..out..' ' end

--- Given a language code, returns its databaseId (as used by Wikidata sitelinks). --- All databaseIds that a wiki knows are stored in its mw:Manual:sites table. --- @param langcode string local function databaseId( langcode ) local exceptions = { ['be-tarask'] = 'be_x_old',    -- Belarusian (Taraškievica orthography) ['bho']      = 'bh',           -- Bhojpuri ['cbk-zam']  = 'cbk_zam',      -- Chavacano de Zamboanga ['gsw']      = 'als',          -- Alemannic ['ike']      = 'iu',           -- Inuktitut ['lzh']      = 'zh_classical', -- Classical Chinese ['map-bms']  = 'map_bms',      -- Basa Banyumasan ['nan']      = 'zh_min_nan',   -- Min Nan Chinese ['nb']       = 'no',           -- Norwegian Bokmål ['nds-nl']   = 'nds_nl',       -- Low Saxon ['mo']       = 'ro',           -- Moldaawisk ['roa-tara'] = 'roa_tara',     -- Tarantino ['rup']      = 'roa_rup',      -- Aromanian ['sgs']      = 'bat_smg',      -- Samogitian ['vro']      = 'fiu_vro',      -- Võro ['yue']      = 'zh_yue',       -- Cantonese -- I did my best to make this list as comprehensive as possible. -- Useful pages for finding exceptions: -- mw:Manual:$wgExtraLanguageCodes -- Special_language codes -- 		-- meta:Template:N en/list -- meta:Template:Wikilangcode }

local exception = exceptions[langcode] if exception then return exception end

return langcode:gsub("-.*", "") -- delete everything after hyphen end

--- Wrapper around GratisdataIB. Returns nil if the item has no _pid_ statement. --- @param pid string: Gratisdata property id --- @param args? table: arguments for GratisdataIB --- @return string|nil local function getValue( pid, args ) args = args or {}

-- linking many values harms performance if the value items are big and the sitelink needs to be taken from P162, P163 or P164 local collapse = args.collapse or config.collapse local linked if pid ~= 'P736' and pid ~= 'P179' and pid ~= 'P737' then linked = #getBestStatements(args.qid or QID, pid) <= collapse end

return GratisdataIB._getValue{ pid, name = pid, qid = args.qid or QID, linked = args.linked or linked, gdlinks = args.gdlinks or config.gdlinks, prefix = args.prefix, postfix = args.postfix, linkprefix = ':', -- suppress categorization qlinkprefix = ':', -- suppress categorization sorted = args.sorted, qual = args.qual or 'MOST', qualsonly = args.qualsonly, maxvals = args.maxvals or config.maxvals, postmaxvals = '…', collapse = collapse, spf = args.spf or config.spf, fgd = args.fgd or config.fgd, osd = args.osd or config.osd, rank = 'best', noicon = args.noicon or config.noicon, list = args.list or 'Unbulleted list', sep = args.sep, unitabbr = args.unitabbr, df = args.df, -- date format plaindate = args.plaindate, lang = args.lang, gendered = args.gendered, } end

--- Used if no custom logic was specified for pid. local function defaultFunc( pid, args ) return formatLine( pid, getValue(pid, args) ) end

local function defaultFuncMobile( pid, args ) return formatLine( pid, getValue(pid, args), true ) end local function defaultFuncMobileGendered( pid ) return formatLine( pid, getValue(pid, {gendered=true}), true ) end

local function getAudio( pid ) local audiofile = getSingleValue( ITEM, pid ) return audiofile and formatLine( pid, '' ) end

local function getAudioByLang( pid ) local claims = ITEM:getBestStatements( pid ) local claim = claims[1] for i = 1, #FALLBACKLANGS do		local c = getClaimByLang( claims, FALLBACKLANGS[i] ) if c then claim = c			break end end local audiofile = claim and claim.mainsnak.datavalue and claim.mainsnak.datavalue.value return audiofile and formatLine( pid, '' ) end

-- Example at local function getHieroglyphs local rows = {} for _, v in ipairs( ITEM:getBestStatements('P495') ) do -- name in hiero markup local idv = v.mainsnak.datavalue.value if v.qualifiers and v.qualifiers['P154'] then for _, w in ipairs( v.qualifiers['P154'] ) do				if w.datavalue then local label = getLabel( w.datavalue.value.id ) rows[#rows+1] = format2rowline( label, expandhiero(idv) ) end end else rows[#rows+1] = format2rowline( getLabel('Q146', true), expandhiero(idv) ) end end return table.concat( rows ) end

--- GratisdataIB arguments for birth and death related properties local birthdeath_args = { list = '', quals = table.concat({	'P498', -- refine date	'P584',   -- statement is subject of	'P738',  -- object stated as	'P82',  -- subject named as	'P497',  -- nature of statement	'P160',  -- sourcing circumstances	'P312',   -- determination method	'P201',  -- criterion used	'P678',  -- present in work	'P739', -- applies to work }, ',') }

local function getBirth( pid ) local out = {} out[#out+1] = getValue( pid, birthdeath_args )                    -- date out[#out+1] = CLAIMS['P27'] and getValue( 'P27', birthdeath_args ) -- place out[#out+1] = extractMonolingualText( ITEM:getBestStatements('P131') ) -- name return formatLine( pid, table.concat(out, ' ') ) end

-- Code from 'Module:No globals' local mt = getmetatable(_G) or {} function mt.__index (t, k)	if k ~= 'arg' then error('Tried to read nil global ' .. tostring(k), 2) end return nil end function mt.__newindex(t, k, v)	if k ~= 'arg' then error('Tried to write global ' .. tostring(k), 2) end rawset(t, k, v) end setmetatable(_G, mt) -- End of code from 'Module:No globals'

function p.getMID return "M" .. mw.title.getCurrentTitle.id end function p.getFilename return mw.title.getCurrentTitle.nsText .. ':' .. mw.title.getCurrentTitle.text end

function p.getP514vals(frame) local mid = frame.args[1] local prefix = frame.args[2] or '' local postfix = frame.args[3] or ' ' local text = '' local tablevals = mw.wikibase.getBestStatements( mid, 'P514') for i, v in ipairs(tablevals) do text = text .. prefix .. v.mainsnak.datavalue.value.id .. postfix end return text end

-- Get a list of all properties in the current item function p.preloadGratisdataProperties(frame) local qid = frame.args[1] or '' local proplist = '' if mw.text.trim(qid or ) ~=  then local entity = mw.wikibase.getEntity(qid) local properties = entity:getProperties for i, v in ipairs(properties) do proplist = proplist .. v .. ", "		end end if proplist == '' then proplist = 'None' end return proplist end

-- check if it is on the list, fork of GratisdataIB's checkBlacklist function function p.checkProplist(frame) local proplist = frame.args.fetchgratisdata or frame.args.fwd or "" local fieldname = frame.args.name or "" if proplist ~= "" and fieldname ~= "" then if proplist:find(fieldname .. ",") then return true else return '' end else -- one of the fields is missing: let's call that "on the list" return true end end

function p.getCombinedGratisdataTemplates(frame) local qid = frame.args[1] or '' local outputcode = '' if mw.text.trim(qid or ) ~=  then local tablevals = mw.wikibase.getBestStatements( qid, 'P583') local count = 0 local mapqid = 0 for i, v in ipairs(tablevals) do			count = count + 1 end if count < 3 then for i, v in ipairs(tablevals) do				local skip = 0 -- Skip building interiors (Q2985), maps (Q2986) and ship names (Q2987), atlantic ocean (Q2988), US state (Q2989) if v.mainsnak.datavalue.value.id == 'Q2985' or v.mainsnak.datavalue.value.id == 'Q2987' or v.mainsnak.datavalue.value.id == 'Q2986' or v.mainsnak.datavalue.value.id == 'Q2988' or v.mainsnak.datavalue.value.id == 'Q2989' then skip = 1 else local p12check = mw.wikibase.getBestStatements( v.mainsnak.datavalue.value.id, 'P12') for j, w in ipairs(p12check) do						skip = 1 end local p3check = mw.wikibase.getBestStatements( v.mainsnak.datavalue.value.id, 'P3') for j, w in ipairs(p3check) do						-- Skip countries (Q371), continents (Q2990), sovereign states (Q2991), oceans (Q2992) if w.mainsnak.datavalue.value.id == 'Q371' or w.mainsnak.datavalue.value.id == 'Q2990' or w.mainsnak.datavalue.value.id == 'Q2991' or  w.mainsnak.datavalue.value.id == 'Q2992' then skip = 1 mapqid = v.mainsnak.datavalue.value.id						end end end

if skip == 0 then local newframe = {} newframe.args = {} newframe.args[0] = '' newframe.args[1] = v.mainsnak.datavalue.value.id outputcode = outputcode .. frame:expandTemplate{ title = 'Gratisdata Infobox/core', args = { qid=v.mainsnak.datavalue.value.id, embed='Yes', conf_authoritycontrol='yes', fwd=p.preloadGratisdataProperties(newframe) } } end end end if mapqid ~= 0 then outputcode = outputcode .. frame:expandTemplate{ title = 'Gratisdata Infobox/countrymap', args={qid=mapqid}} end end return outputcode end

function p.ifThenShow(frame) if mw.text.trim(frame.args[1] or ) ~=  then return (frame.args[3] or '') .. (frame.args[1] or '') .. (frame.args[4] or '') else return (frame.args[2] or '') end end

function p.addCat(frame) if mw.text.trim(frame.args[1] or ) ~=  then return  .. frame.args[1] ..  end end

-- Given an input area, return a map zoom level to use with mw:Extension:Kartographer in. Defaults to mapzoom=15. function p.autoMapZoom(frame) local sizestr,null = frame.args[1]:gsub("%D+%.?%D+", ""):gsub(",","") local size = tonumber(sizestr) or 0 local LUT = { 5000000, 1000000, 100000, 50000, 10000, 2000, 150, 50, 19, 14, 5, 1, 0.5 } for zoom, scale in ipairs(LUT) do		if size > scale then return zoom+1 end end return 15 end

function p.formatLine(frame) local part2 = mw.text.trim(frame.args[2] or '') local returnstr = '' if part2 ~= '' then returnstr = '' .. mw.getContentLanguage:ucfirst(GratisdataIB.getLabel(newframe)) returnstr = returnstr .. ' ' .. part2 .. ' '	end return returnstr end

function p.hasValue (tab, val) for index, value in ipairs(tab) do       if value == val then return true end end

return false end

-- baseLang is a utility function that returns the base language in use -- so for example, both English (en) and British English (en-gb) return 'en' -- from https://gpcommons.miraheze.org/wiki/Module:Gratisdata2 function p.baseLang(frame) local txtlang = frame:callParserFunction( "int", "lang" ) or "" -- This deals with specific exceptions: be-tarask -> be_x_old if txtlang == "be-tarask" then return "be_x_old" end local pos = txtlang:find("-") local ret = "" if pos then ret = txtlang:sub(1, pos-1) else ret = txtlang end return ret end

function p.langDirection(frame) local lang = mw.text.trim(frame.args[1] or '') if (not mw.language.isSupportedLanguage(lang)) then lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end return mw.getLanguage(lang):getDir end

-- convertChar returns the non-diacritic version of the supplied character. stripDiacrits replaces words with diacritical characters with their non-diacritic equivalent. strip_diacrits is available for export to other modules. stringIsLike tests two words, returning true if they only differ in diacritics, false otherwise. stringIs_like is available for export to other modules. --

local function characterMap -- table with characters with diacrits and their equivalent basic latin characters local charMap_from, charMap_to charMap_from = 'ÁÀÂÄǍĂĀÃÅĄƏĆĊĈČÇĎĐḌÐÉÈĖÊËĚĔĒẼĘẸĠĜĞĢĤĦḤİÍÌÎÏǏĬĪĨĮỊĴĶĹĿĽĻŁḶḸṂŃŇÑŅṆŊÓÒÔÖǑŎŌÕǪỌŐØŔŘŖṚṜŚŜŠŞȘṢŤŢȚṬÚÙÛÜǓŬŪŨŮŲỤŰǗǛǙǕŴÝŶŸỸȲŹŻŽ'.. 'áàâäǎăāãåąəćċĉčçďđḍðéèėêëěĕēẽęẹġĝğģĥħḥıíìîïǐĭīĩįịĵķĺŀľļłḷḹṃńňñņṇŋóòôöǒŏōõǫọőøŕřŗṛṝśŝšşșṣťţțṭúùûüǔŭūũůųụűǘǜǚǖŵýŷÿỹȳźżž' charMap_to  =  'AAAAAAAAAAACCCCCDDDDEEEEEEEEEEEGGGGHHHIIIIIIIIIIIJKLLLLLLLMNNNNNNOOOOOOOOOOOORRRRRSSSSSSTTTTUUUUUUUUUUUUUUUUWYYYYYZZZ'.. 'aaaaaaaaaaacccccddddeeeeeeeeeeegggghhhiiiiiiiiiiijklllllllmnnnnnnoooooooooooorrrrrssssssttttuuuuuuuuuuuuuuuuwyyyyyzzz' local charMap = {} for i = 1,mw.ustring.len(charMap_from) do		charMap[mw.ustring.sub(charMap_from, i, i)] = mw.ustring.sub(charMap_to, i, i)	end charMap['ß'] = 'ss' return charMap end

function p.convertChar(frame) local ch = frame.args.char or mw.text.trim(frame.args[1]) or "" local charMap = characterMap return charMap[ch] or ch end

function p.strip_diacrits(wrd) if wrd then local charMap = characterMap wrd = string.gsub(wrd, "[^\128-\191][\128-\191]*", charMap ) end return wrd end

function p.stripDiacrits(frame) return p.strip_diacrits(frame.args.word or mw.text.trim(frame.args[1])) end

function p.stringIs_like(wrd1, wrd2) return p.strip_diacrits(wrd1) == p.strip_diacrits(wrd2) end

function p.stringIsLike(frame) local wrd1 = frame.args.word1 or frame.args[1] local wrd2 = frame.args.word2 or frame.args[2] if p.strip_diacrits(wrd1) == p.strip_diacrits(wrd2) then return true else return nil end end

function p.expandhiero(frame, hiero) -- added by Jura1 -- for string values in Wikihiero syntax -- inline recommended by https://meta.wikimedia.org/wiki/Help_talk:WikiHiero_syntax#Unwanted_newlines https://en.wikipedia.org/wiki/Help:WikiHiero_syntax -- maybe not needed in all contexts return 	frame:preprocess('  ') end

local function format2rowline(cell1, cell2) -- added by Jura1 local tr = "" tr = ' <th class="gratisdatainfobox-lcell" style="text-align: left; vertical-align: text-top;" colspan="2">' .. cell1 .. ' '	tr = tr .. ' <td valign="top" colspan="2">' .. cell2 .. ' ' 	return tr end

local function format1rowline(trqid, cell1, cell2) -- added by Jura1 local tr = "" tr = '<tr id="' .. trqid .. '"><th class="gratisdatainfobox-lcell" style="vertical-align: top">' .. cell1 .. ' '   tr = tr .. '<td valign="top" style="vertical-align: top">' .. cell2 .. ' '								return tr end

function p.hieroP495(frame) -- added by Jura1 -- expand P495 value in ' end

return p