-- This extended version of Databox is stored at https://sv.wikipedia.org/wiki/Modul:Databox, but can be used by other languages
-- Versions: (Besides minor adjustments to the property_blacklist and layout)
-- 2024-03-03 Parameter "keep_title_case" to not change first letter to upper case (default false)
-- 2023-06-18 Shows monolingualtext but only in local language - and maximum one value per property
--  		  Can show any commonsMedia file that has media caption qualifier in the local language, or with content in ("language of work" qualifier) the local language
--			  Can show several maps if several coordinates, limited by parameter maxFiles (default 1).
-- 2023-06-11 Locator map image shown if it has media caption in the local language
-- 2020-05-25 Hide "is instance of" -> "human"
-- 2020-05-25 Category:Databox that shows qid code
-- 2020-05-24 Years linked to articles. Decades, centuries and millennia formated in local language.
-- 2020-05-13 P155 (follows) and P156 (followed by) merged into one "Chronology" list, also showing current object.
--			Parameter "list_separator" for replacing comma in lists.  
-- 2020-05-10 Parameter "era" for choosing if year "BCE" (or similar in local language) should be replaced by "BC", empty string or other.
--			Upper-case initial letter of P31 (instance of).
-- 2020-05-05 P31 hidden if too long list. Administrative wiki category if too long list.
-- 2020-05-02 More than two parent/child levels in bulleted list.
-- 2020-05-01 Property short names based on P1813. Image legend/caption. 
-- 2020-04-28 Properties linked to articles (based on Property:P1629 of the property). 
--			Datatype "url" not shown (except for official web site). Input parameter "levels". 
-- 2020-04-27 Two higher and two lower levels of child and parent items shown as bulleted list for some properties
-- 2020-04-21 Parameters "width", "height", "zoom" and "list_length". Soft hyphens auto-inserted in long property names.
-- 2020-04-18 (Monolingual text strings hidden.) Property name hidden if no good value. First image shown if several images. 
-- 2020-04-16 Description shown (only in local language). First letter of label upper-case.
-- 2020-04-13 Values of datatype "quantity" shown. Pen hidden in printout.
-- 2020-04-13 Mapframe code copied from the 2019-03-04 af.wikipedia.org version.
-- 2020-04-07 Imported from the 2019-04-26 fr.wikipedia.org version 

-- Blocked properties:
local property_blacklist = {
	'P360', --is a list of
	'P4224', --category contains
	'P935', -- Commons gallery
	'P1472', -- Commons Creator page
	'P1612', -- Commons Institution page
	'P373', -- Commons category
	'P3722', -- Commons maps category
	'P7561', --  for the interior of the item
	'P1151', -- topic's main Wikimedia portal
	'P1424', -- topic's main template
	'P910', -- topic's main category
	'P1200', -- bodies of water basin category
	'P1792', -- category of associated people
	'P1464', -- category for people born here
	'P1465', -- category for people who died here
	'P1791', -- category of people buried here
	'P1740', -- category for films shot at this location
	'P2033', -- Category for pictures taken with camera
	'P2517', -- category for recipients of this award
	'P4195', -- category for employees of the organization
	'P1754', -- category related to list
	'P301', -- category's main topic
	'P971', -- category combines topics
	'P3876', -- category for alumni of educational institution
	'P1753', -- list related to category
	'P7867', -- category for maps
	'P1921', -- Wikidata RDF URI format
	'P3921', -- Wikidata SPARQL query equivalent
	'P1204', -- Wikimedia portal's main topic
	'P1423', -- template's main topic
	'P1709', -- equivalent class
	'P3950', -- narrower external class
	'P2888', -- exact match
	'P1382', -- coincident with
	'P2670', -- has parts of the class
	'P3113', -- does not have part
	'P2737', -- union of
	'P2738', -- disjoint union of
	'P2445', -- metasubclass of
	'P1963', -- properties for this type
	'P3176', -- uses property
	'P1889', -- different from
	'P460', -- said to be the same as
	'P2959', -- permanent duplicated item
	'P2860', -- cites
	'P5125', -- wikimedia outline
	'P5008', -- on focus list of Wikimedia project
	'P7084', -- related category
	'P1687', -- Wikidata main property for this item 
	'P2559', -- Wikidata usage instructions
	'P5692', -- Wikidata dummy value
	'P1343', -- described by source
	'P972',  -- catalogue
	'P1282', -- OSM tag or key
	'P553',  -- web site account
	'P968',  -- email
	'P2572', -- hashtag
	'P3761', -- IPv4 range
	'P4839', -- Wolfram Language entity code
	'P6104', -- Maintained by Wikiproject
	'P5996', -- Category for films in this language
	'P2354', -- list article (seldom available in local language)
	'P6365', -- member category
	'P528', -- catalog code
	'P667', -- ICPC 2 ID
	'P944', -- Code of nomenclature
	'P1438', -- Jewish Encyclopedia ID (Russian)e
	'P1402', -- Foundational Model of Anatomy ID
	'P1461', -- Patientplus ID
	'P1692', -- ICD-9-CM code
	'P1748', -- NCI Thesaurus ID
	'P1193', -- prevalence (often different value in different countries)
	'P2176', -- drug used for treatment (we avoid medical advise)
	'P2293', -- genetic association
	'P1814', -- Japanese name in kana
	'P747', -- editions
	'P1433', -- published in
	'P4969', -- derivative work
	'P217', -- inventory number
	'P2540', -- Aarne–Thompson–Uther Tale Type Index
	'P1036', -- DDC
	'P1149', -- LCC
	'P1150', -- RVK
	'P1190', -- UDC
	'P1987', -- MCN code
	'P2263', -- ISOCat id
	'P2283', -- Uses 
	'P2184', -- History of subject. (Should be shown if article in local language)
	'P989',  -- spoken text. (Should be shown if in local language)
	'P1793', -- format as a regex
	'P4354', -- search formatter URL
	'P5869', -- model item
	'P859',  -- sponsor
	'P7973', -- quantity symbol (LaTeX)
	'P6216', -- copyright status
	'P1830', -- owner of (seldom useful)
	'P487',  -- Unicode character 
	'P8933', -- category for the view from the item
	'P1299', -- depicted by
	'P6112', -- category for members of a team
	'P8687',  -- social media followers 
	'P1559',  -- name in native language
	'P8596', -- category for multimedia files depicting exterior views of this item 
	'P7763', -- copyright status as a creator
	'P8989', -- category for the view of the item
	'P7782', -- category for ship name
	'P2817', -- appears in the heritage monument list
	'P8402' -- open data portal
}

-- Exceptions to the datatype blocking:
local property_whitelist = { 
	'P856', -- official website
	'P3896',-- geoshape
	'P345', -- IMDB id
	'P6375' -- street address
}

-- Properties with higher level items:
local properties_with_parents = { 
	'P131', -- located in the administrative territorial entity
	'P144', -- based on
	'P155', -- follows
	'P171', -- parent taxon
	'P276', -- location
	'P279', -- subclass of
	'P361', -- part of
	'P706', -- located on terrain feature
	'P749', -- parent organization
	'P807', -- separated/forked from
	'P1365', -- replaced
	'P1647', -- subproperty of
	'P3730' -- next higher rank
}

local properties_with_children = { 
-- Properties with lower level items:
	'P150', -- contains administrative territorial entity
	'P156', -- followed by
	'P355', -- subsidiary
	'P527', -- has part 
	'P1012', -- contains
	'P1366', -- replaced by
	'P3729', -- next lower rank
	'P4330', -- contains
	'P7888' -- merged into
}

local function buildInteractiveMap(width, point, item_id, zoom)
--Utility function to build maps
	local geojson = {
		{
			type = 'Feature',
			geometry = {
				type = "Point",
				coordinates = {point.longitude, point.latitude}
			},
			properties = {
				title = point.text or '',
				['marker-symbol'] = point.marker or 'marker',
				['marker-color'] =  point.markercolor or "#224422",
			}
		}
	}
	local args = {
		['height'] = width,
		['width'] = width,
		['frameless'] = 'frameless',
		['align'] = 'center',
		['latitude'] = point.latitude,
		['longitude'] = point.longitude,
		['zoom'] = zoom,
		['lang'] = lang -- fallbacks to wiki language if local name is missing. )
	}
	return mw.getCurrentFrame():extensionTag('mapframe', mw.text.jsonEncode(geojson), args)
end

function Set(list) -- values to booleans with keys
	local set = {}
	for _, l in pairs(list) do 
		set[l] = true
	end
	return set
end

function  listCase(str)
	-- Capitalizes first visible character of list produced by formatStatements() 
	-- Example: <span><span>[[link|first item]]</span>, <span>second item</span></span>
	--	 -->  <span><span>[[link|First item]]</span>, <span>second item</span></span>
	return str
		:gsub('^<span><span>%[%[(.-)|(.-)%]%]</span>',
			function(a,b) 
				return '<span><span>[[' .. a .. '|' 
				.. b:gsub('^%l', string.upper) 
				.. ']]</span>' 
			end)
		:gsub('^<span><span>(%l)', 
			function(a) 
				return '<span><span>' ..string.upper(a) 
			end)
end

function listSeparate(str, list_separator)
	-- Replaces comma in list produced by formatStatements() 
	-- Example: list_separator = ';<br>'  
	--   str  = <span><span>[[link|first item]]</span>, <span>second item</span></span>
	--	 ->   <span><span>[[link|first item]]</span>;<br> <span>second item</span></span>
	if list_separator ~= ',' then
		return str
			:gsub('</span>,', '</span>'..list_separator)
	else 
		return str
	end
end

function hyphenate(str, lang)
	-- Inserts soft hyphens in long words, typically before each consonant that is followed by a vowel (lower-case letters)
	-- Should work good enough for most languages. Language specific exceptions may be added.
	local nonHyphenatedLanguages = Set{'ar', 'he', 'zh', 'ja', 'ko', 'vi', 'fa', 'ps'}
	if nonHyphenatedLanguages[lang] then -- Not languages without alphabetic writing system or with few vowels 
		return str
	end
	result = ''
	for word in str:gmatch("%S+") do 
		if #word < 10 then
			result = result .. ' ' .. word
		else
			result = result .. ' ' 
			.. word:sub(1,3) -- Not too early in word
			.. word:sub(4)
			:gsub("([bcdfghjklmnpqrstvwxzđçčĉñŋĝĥĵŝšŧžßÐðþğşśćńŁżźбвгжийклмнпрст]"
				.. "[aouåeiyäöæøáéíóúýàèâêëüãŭœāēīōūəąęóадеёзоу])",
				"&shy;%1") -- Insert soft-hyphens before each consonant that is followed by vowel
			:gsub("&shy;([\128-\193])", "%1" ) -- Revert split of two-byte UTF-8 character
			:gsub("-(%a?%a?%a?%a?)&shy;", "-%1"):gsub("&shy;(%a?%a?%a?%a?)-", "%1-") -- Not too near a hard hyphen
			
			-- Some Scandinavian exceptions for wikidata properties, relevant to similar languages:
			:gsub("&shy;x", "x&shy;") -- Example: tids-komp-le-xi-tet -> tids-komp-lex-i-tet   
			:gsub("sc&shy;h", "&shy;sch") -- Example: sc-h -> -sch
			:gsub("ss&shy;j", "s&shy;sj")-- Example: ss-j-> s-sj
			:gsub("n&shy;g", "ng&shy;") -- Example: n-g -> ng- 
			:gsub("ngs", "ngs&shy;") -- Example: Befolk-ningsg-rup-pe -> Befolk-nings-g-rup-pe, Rege-ring-s-che-fens -> Rege-rings-chefens 
			:gsub("g&shy;rup&shy;pe", "&shy;gruppe") -- Example: g-rup-pe -> -gruppe
			:gsub("nist&shy;ra", "nis&shy;tra") -- Example: admi-nist-ra-tion -> admi-nis-tra-tion
			:gsub("&shy;ror&shy;ga[&shy;]*n", "r&shy;organ") -- Example: dotte-ror-ga-ni-sa-tion -> dotter-organi-sa-tion
			:gsub("&shy;rob[&shy;]*jek", "r&shy;objek") -- Example: dot-te-rob-jekt -> dot-ter-objekt
			:gsub("s&shy;ta&shy;tus", "&shy;status") -- Example: skydds-status
			:gsub("k&shy;las[&shy;]*s", "&shy;klass") -- Example: deci-malk-las-si-fi-ka-tion -> deci-mal-klas-si-fi-ka-tion
			:gsub("&shy;nom&shy;rå&shy;de", "n&shy;område") -- Example: vatte-nom-rå-de -> vatten-område
			:gsub("&shy;som&shy;rå&shy;de", "s&shy;område") -- Example: Rets-gyl-dig-hed-som-rå-de -> Rets-gyl-dig-heds-om-rå-de
			:gsub("ra&shy;lort", "ral&shy;ort") -- Example: central-ort
			:gsub("s&shy;kydd", "&shy;skydd") -- Example: Kul-turs-kydd -> Kul-tur-skydd
			:gsub("k&shy;ri&shy;te&shy;ri", "&shy;kri&shy;te&shy;ri")-- Example: Värld-sarvsk-ri-te-rium -> Värld-sarvs-kri-te-rium
			:gsub("guasp&shy;he", "gua&shy;sphe") -- Example: lingua-sphere
			:gsub("gars&shy;kap", "gar&shy;skap") -- Example: medbor-gars-kap -> medbor-gar-skap
			:gsub("k&shy;var&shy;ter", "&shy;kvarter") -- Example: Hovedk-var-ter -> Hoved-kvarter
			:gsub("s&shy;ted", "&shy;sted") -- Example: Pro-duk-tionss-ted -> Pro-duk-tions-sted
			:gsub("&shy;&shy;", "&shy;") -- Example -- -> -
		end
	end
	return result:sub(2,-1)
	-- :gsub("&shy;", "-") -- Show soft hyphens as hard hyphens. Only for sandboxed test purposes.
end

function year(str, lang, replaceTime)
	-- Postprocesses years (datatype time) in local language

	-- Incorrect formating of the first decade.
	if lang == 'sv'	then
		str = str
			:gsub("<span>0</span>", "<span>00-talet</span>")
			:gsub("<span>0 BCE</span>", "<span>00-talet f.v.t.</span>")
	end
	-- Replace BCE with BC (or corresponding in local language) depending on era template parameter:
	if replaceTime then
		for p,r in pairs(replaceTime) do
			str = str:gsub(p, r)
		end
	end
	
	-- Link years to articles:
	str = str
		:gsub("([%s>])(%d?%d?%d?%d)</span>", "%1[[%2]]</span>") -- April 1852 -> April [[1852]]
		:gsub("([%s>])(%d?%d?%d?%d) ([%a%.]*)</span>", "%1[[%2 %3]]</span>") -- April 20 BCE -> April [[20 BCE]]

	-- Format decades, centuries and millennias correctly in local language, and link to articles:  
	if lang=='sv' then
		str = str
			:gsub("(%d?1%d)%.? år([h|t])(%a+)det?", "%1:e år%2%3det") -- 12. årtusende -> 12:e årtusendet
			:gsub("(%d?%d?[1-2])%.? år([h|t])(%a+)det?", "%1:a år%2%3det") -- 2. århundrade  -> 2:a århundradet
			:gsub("(%d?%d?[0,3-9])%.? år([h|t])(%a+)det?", "%1:e år%2%3det") -- 13 århundrandet f.Kr. -> 13:e århundradet f.Kr 
			:gsub("<span>(%d?%d?%d?)00-talet</span>", "<span>[[%100-talet (decennium)]]</span>") -- 1900-talet -> [[1900-talet (årtionde)]]
			:gsub("<span>(%d?%d?%d?)00-talet ([%a%.]*)</span>", "<span>[[%100-talet	(decennium) %2]]</span>") -- 100-talet f.Kr. -> [[100-talet f.Kr. (decennium)]]
			:gsub("(%d?%d?%d):[a|e] århundradet", 
				function(a) 
					return tonumber(a)-1 .. '00-talet' 
				end) -- 21:a århundradet -> 2000-talet
			:gsub("<span>(%d+)-talet</span>", "<span>[[%1-talet]]</span>") -- 2000-talet -> [[2000-talet]]
			:gsub("<span>(%d+)-talet ([%a%.]*)</span>", "<span>[[%1-talet %2]]</span>") -- 000-talet f.Kr. -> [[000-talet f.Kr.]]	
			:gsub("(%d?%d?%d):[a|e] årtusendet", 
				function(a) 
					return tonumber(a)-1 .. '000-talet' 
				end) -- 2:a århundradet -> 2000-talet
			:gsub("<span>(%d+)-talet</span>", "<span>[[%1-talet (millennium)]]</span>") -- 2000-talet -> [[2000-talet (millennium)]]
			:gsub("<span>(%d+)-talet ([%a%.]*)</span>", "<span>[[%1-talet %2 (millennium)]]</span>") -- 0000-talet f.Kr. -> [[0000-talet f.Kr. (millennium)]]	
	end	
	return str
end

local p = {}

function p.databox(frame)
	local args = frame:getParent().args
	local itemId = nil
	if args.item then
		itemId = args.item
	end
	local item = mw.wikibase.getEntity(itemId) 
	if item == nil then
		mw.addWarning("Wikidata item not found")
		return ""
	end
	
	local width = '260' -- default max width of template, image and map, and height of map 
	if args.width then
		width = args.width
	end
	local height = '240' -- default max height of image. hidden if <= 0.
	if args.height then
		height = args.height
	end
	local zoom = 12 -- default map zoom level. hidden if <0.
	if args.zoom then
		zoom = tonumber(args.zoom)
	end
	local list_length = 8 -- default max no of values in lists
	if args.list_length then
		list_length = tonumber(args.list_length)
	end
	local list_separator = ',' -- default no replacement of comma in lists
	if args.list_separator then
		list_separator = args.list_separator
	end
	local levels = 3 -- default max no of child and parent levels
	if args.levels then
		levels = tonumber(args.levels)
	end
	local maxFiles = 1 -- default max no of coordinate location maps
	if args.maxFiles then
		maxFiles = tonumber(args.maxFiles)
	end
	local langObject = mw.language.getContentLanguage()
	local lang = langObject:getCode()

	local langIdDict = { -- Dictionary for translating language code to language Wikidata item id
		['af'] = 'Q14196',
		['atj'] = 'Q56590',
		['be-tarask'] = 'Q8937989',
		['ca'] = 'Q7026',
		['ceb'] = 'Q33239',
		['ckb'] = 'Q36811',
		['cs'] = 'Q9056',
		['da'] = 'Q9035',
		['dag'] = 'Q32238',
		['de'] = 'Q188',
		['en'] = 'Q1860', 
		['es'] = 'Q1321',
		['ewe'] = 'Q30005',
		['fi'] = 'Q1412',
		['fa'] = 'Q9168',
		['fr'] = 'Q150',
		['frr'] = 'Q28224',
		['haw'] = 'Q33569',
		['he'] = 'Q9288',
		['hi'] = 'Q1568',
		['it'] = 'Q652', 
		['ja'] = 'Q5287',
		['kab'] = 'Q35853',
		['ko'] = 'Q9176',
		['mzn'] = 'Q13356',
		['nap'] = 'Q33845',
		['nds'] = 'Q25433',
		['nl'] = 'Q10000',
		['no'] = 'Q9043',
		['nqo'] = 'Q18546266',
		['pap'] = 'Q33856',
		['pl'] = 'Q809',
		['pcm'] = 'Q33655',
		['pt'] = 'Q5146',
		['ru'] = 'Q7737',
		['rue'] = 'Q26245',
		['sh'] = 'Q9301',
		['sv'] = 'Q9027',
		['tr'] = 'Q256',
		['uk'] = 'Q8798',
		['vi'] = 'Q9199',
		['zh'] = 'Q7850'
	}
	local dump = ''
	local langId -- Local language Wikidata item id
	langId = langIdDict[lang] or nil

	local edit_message = mw.message.new('vector-view-edit'):plain() .. ' Wikidata'
	
	-- Date formating
	local bceDict = { -- Dictionary: Before current era (BCE) in different languages
		['da'] = 'f.v.t.',
		['en'] = 'BCE',
		['sv'] = 'f.v.t.'
	}
	local bcDict = { -- Dictionary: Before Christ (BC) in different languages
		['da'] = 'f.Kr.',
		['en'] = 'BC',	
		['sv'] = 'f.Kr.'
	}
	local bc = bcDict[lang] or 'BC'
	local bce = bceDict[lang] or 'BCE'
	local era = bc -- default era 
	if args.era then
		if args.era == 'BC' then
			era = bc -- replace 'BCE' by 'BC' in content language
		elseif args.era == 'BCE' then
			era = bce -- replace 'BC' by 'BCE' in content language
		else
			era = args.era -- replace 'BCE' and 'BC' by arbitrary argument value, for example empty string
		end
	end
	local replaceTime = {} -- global variable
	replaceTime[' BCE'] = ' '..era
	replaceTime[' '..bce] = ' '..era
	replaceTime[' '..bc] = ' '..era

	local noValueDict = { -- Dictionary: No value
		['da'] = 'ingen værdi',
		['en'] = 'no value',
		['sv'] = 'inget värde'
	}
	local wikicategory = ''
	
	local databoxRoot = mw.html.create('div')
		:addClass('infobox')
		:css({
			float = 'right',
			clear = 'right',
			border = '1px solid #aaa',
			['background-color'] = '#f9f9f9',
			['width'] = width .. 'px',
			padding = '0 0.4em',
			margin = '0 0 0.4em 0.4em',
		})

	--Title
	local title = item:getLabel() or mw.title.getCurrentTitle().text
    if args.keep_title_case == nil or #args.keep_title_case == 0 or args.keep_title_case == 'false' then
	    title = langObject:ucfirst(title)
	end
	databoxRoot:tag('div')
		:css({
			['text-align'] = 'center',
			['background-color'] = 'LightGrey',
			padding = '0em 0.4',
			margin = '0em 0',
			['font-size'] = '120%',
			['font-weight'] = 'bold',
		})
		:wikitext(title)

	--Description
	local descr, descrLang = item:getDescriptionWithLang()
	if descrLang == lang then -- Do not show any fallback language
		databoxRoot:tag('div')
		:css({
			['text-align'] = 'center',
			['vertical-align'] = 'text-top',
			['font-size'] = '90%',
			['line-height'] = '140%',
			padding = '0.2em 0.4',
			margin = '0.0em 0.4',
			['padding-bottom'] = '0.5em',
		})
		:wikitext(langObject:ucfirst(descr):sub(1,-1))
		:wikitext('<sup class="noprint Inline-Template">&nbsp;[[File:Arbcom_ru_editing.svg|' 
			.. edit_message .. '|8px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' 
			.. item.id .. ']]</sup>')
	end
	
	--Show first good image with legend/caption in content language, or first good image
	if tonumber(height) > 0 then
		local images = item:getBestStatements('P18') -- p18 is 'image'
		if #images >= 1 then
			local image = images[1]
			for _, i in pairs(images) do
				
 				if i.qualifiers and i.qualifiers.P2096 then -- P2096 is 'caption'
 					for _, c in pairs(i.qualifiers.P2096) do 
 						if c.snaktype == 'value' and c.datavalue.value.language == lang then
							caption = c
				 			image = i
							break
 						end
 					end 
 				end
 				if caption then
 					break
 				end
			end 
 			if image.mainsnak.snaktype == 'value' then
	 			databoxRoot
		   		:tag('div')
		   		:css({
					['text-align'] = 'center',
					padding = '0.0em 0.4',
				})
				:wikitext('[[File:' .. image.mainsnak.datavalue.value .. '|frameless|' 
			   		.. width .. 'x' .. height .. 'px]]')			
			   	if caption then
		 			databoxRoot
			   		:tag('div')
		   			:css({
						['text-align'] = 'center',
						['font-size'] = '90%',
						['line-height'] = '140%',
						['padding-bottom'] = '0.5em',
				})
					:wikitext(caption.datavalue.value.text)
					:wikitext('<sup class="noprint Inline-Template">&nbsp;[[File:Arbcom_ru_editing.svg|' 
						.. edit_message .. '|8px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' 
						.. item.id .. '#' .. 'P18' .. ']]</sup>')
			   	end
			end
		end
	end
	
	--Table:
	local dataTable = databoxRoot
		:tag('table')
		:css({
			['text-align'] = 'left',
			['font-size'] = '90%',
			['line-height'] = '140%',
			['hyphens'] = 'auto', -- works only in some browsers and languages
			['word-break'] = 'break-word',
			['width'] = '100%',
			['table-layout'] = 'fixed',
			['padding-bottom'] = '0.5em',
		})
	
	--Instance of:
	local dataValues 
	local statements = item:getBestStatements('P31')
	if #statements > list_length then -- Hide too long list of values
		if lang == 'sv' then
			wikicategory = wikicategory .. '[[Kategori:Databox med dold lång lista]]'
		end
	elseif #statements >= 1 then
		dataValues=item:formatStatements('P31').value
		if lang == 'sv' then
			if dataValues:match("<span>%[%[Människa|människa%]%]</span>") then -- Remove 'is instance of' -> 'human' TODO: Same for other languages
				if #statements == 1 then
					dataValues = ''
				else
					dataValues = dataValues:gsub("<span>%[%[Människa|människa%]%]</span>,?%s?", "")
				end
			end
		elseif lang == 'da' then
			if dataValues:match("<span>%[%[Menneske|menneske%]%]</span>") then -- Remove 'is instance of' -> 'human'
				if #statements == 1 then
					dataValues = ''
				else
					dataValues = dataValues:gsub("<span>%[%[Menneske|menneske%]%]</span>,?%s?", "")
				end
			end
		end
		dataValues = dataValues:gsub(", </span>$", "</span>") -- Removing ending comma after removed ", human"
		
		if #dataValues > 0 then
			dataValues=listCase(dataValues)
				
			dataTable:tag('caption')
				 :css({
				 	['background-color'] = 'LightGrey',
				 	['font-weight'] = 'bold',
				 	['margin-top'] = '0.4em',
				 	margin = '0.5em 0',
					padding = '1em 1',
				 })
				:wikitext(dataValues)			 
				:wikitext('<sup class="noprint Inline-Template">&nbsp;[[File:Arbcom_ru_editing.svg|' 
					   	.. edit_message .. '|8px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' 
					   	.. item.id .. '#P31' .. ']]</sup>')
	
			if #statements >= math.max(list_length-3,3) and lang == 'sv' then -- warning of long list but not too long list
				wikicategory = wikicategory .. '[[Kategori:Databox med lång lista]]'
			end
		end
	end
	
	local properties = mw.wikibase.orderProperties(item:getProperties())
	local property_blacklist_hash = Set(property_blacklist)
	property_blacklist_hash['P18'] = true --Showed separately
	property_blacklist_hash['P31'] = true --Showed separately
	local property_whitelist_hash = Set(property_whitelist)
	local properties_with_parents_hash = Set(properties_with_parents)
	local properties_with_children_hash = Set(properties_with_children)
	local countryid = ' '
	pcall(function () 
		countryid = item.claims['P17'][1].mainsnak.datavalue.value.id 
	end)

	for _, property in pairs(properties) do
		local datatype = item.claims[property][1].mainsnak.datatype
		local statements = item:getBestStatements(property)
		if ( (datatype ~= 'external-id' 
					and datatype ~= 'commonsMedia'
					and datatype ~= 'url')
				or property_whitelist_hash[property] )
			and not property_blacklist_hash[property] 
			and 1 <= #statements then
			
			if #statements > list_length then
				if lang == 'sv' then
				   	wikicategory = wikicategory .. '[[Kategori:Databox med dold lång lista]]' 
				end	
			else
				local propertyValue = item:formatStatements(property)
				propertyValue.label = langObject:ucfirst(hyphenate(propertyValue.label, lang)) -- left table cell content
				local propertyEntity = mw.wikibase.getEntity(property) -- Time consuming
				if propertyEntity then
					
					-- Replace property name by short name if only one in content language:
					if propertyEntity['claims']['P1813'] then
						local shortNames = propertyEntity['claims']['P1813'] -- 'P1813' = short name.
						shortname = ''
						for _, s in pairs(shortNames) do 
	 						if s.mainsnak.snaktype == 'value' 
	 						   and s.mainsnak.datavalue.value.language == lang then -- (Should check that only one value is in the content lang)
								if #shortname > 0 then
									shortname = ''
									break -- Several shortnames in the local language
								end
								shortname = s.mainsnak.datavalue.value.text
							end
	 					end 
						if #shortname > 0 then
							propertyValue.label = langObject:ucfirst(hyphenate(shortname, lang))  
						end
					end
	
					-- Link row label (property name) to related article in content language:
					local propertySubjects = propertyEntity:getBestStatements('P1629') -- 'P1629 = subject item of this property'
					if #propertySubjects == 1 
					   and propertyEntity['claims']['P1629'][1].mainsnak.snaktype == 'value' then
						local subjectItemQid = propertyEntity['claims']['P1629'][1].mainsnak.datavalue.value.id
						articleSitelink = mw.wikibase.getSitelink(subjectItemQid)
						if articleSitelink ~= nil then -- Property subject item has local article
							propertyValue.label = '[[' .. articleSitelink .. '|' .. propertyValue.label .. ']]'  
						end
						
					end
				
				end
			
				local dataValues -- right table cell content
				if #statements == 1 
						and levels >= 2 
						and (properties_with_parents_hash[property] 
							or properties_with_children_hash[property])
						then 
					dataValues = propertyValue.value
					if merged_chronology and property == 'P156' then -- If 'P155' (follows) already shown as bulleted list, P156 (followed by) should be part of same list.
						propertyValue.label = '' -- Hide 'Followed by' in left column
					end
					if property == 'P155' or property == 'P156' then -- follows or followed by
						dataValues = '• ' .. dataValues
					end					
				
					local level = {}
					
					-- Show parent/child item if any:
					if item['claims'][property][1].mainsnak.snaktype == 'value' then					
						level[1] = {}
						level[1].value = item['claims'][property][1].mainsnak.datavalue.value -- (Can give non-best statement?)
						level[1].item = mw.wikibase.getEntity(level[1].value.id) -- Time consuming
						level[1].statements = mw.wikibase.getBestStatements(level[1].item.id, property) 
					end
					
					if item['claims'][property][1].mainsnak.snaktype == 'value' 
					   and #level[1].statements == 1 and level[1].statements[1].mainsnak.datavalue then
						level[1].qid = level[1].statements[1].mainsnak.datavalue.value.id
						if level[1].qid ~= countryid then -- do not repeat country as administrative belonging or place 
							level[1].propertyValue = level[1].item:formatStatements(property)
							if level[1].propertyValue then
							-- Show multi-level list as bulleted list:
								if not (property == 'P155' or property == 'P156') then
									dataValues = '• ' .. dataValues
								end							
								level[1].qid = level[1].statements[1].mainsnak.datavalue.value.id
								if properties_with_children_hash[property] then -- next lower level / child item: put in end of the list. 
									if property == 'P156' then -- follows
										dataValues = dataValues 
										.. '<br/>• ' .. level[1].propertyValue.value
									else -- indent
										dataValues = dataValues 
										.. '<br/>' .. '&nbsp;• ' .. level[1].propertyValue.value
									end
									
								else -- next higher level / parent item: put first in list
									if property == 'P155' then -- followed by
										dataValues = '• ' .. level[1].propertyValue.value
										.. '<br/>' .. dataValues 
									else -- indent
										dataValues = '• ' .. level[1].propertyValue.value
										.. '<br/>&nbsp;' .. dataValues 
									end
								end
						
								local lc = 2 -- level counter
								while lc<levels and level[lc-1].item.claims[property][1].mainsnak.datavalue do 
									level[lc]={}
									level[lc].value = level[lc-1].item.claims[property][1].mainsnak.datavalue.value -- (Best statement?)
									level[lc].item = mw.wikibase.getEntity(level[lc].value.id) -- Time consuming
									level[lc].statements = mw.wikibase.getBestStatements(level[lc].item.id, property)  
									 
									if #level[lc].statements > 0 and level[lc].statements[1].mainsnak.datavalue then 
										level[lc].qid = level[lc].statements[1].mainsnak.datavalue.value.id
										if #level[lc].statements == 1 
												and level[lc].qid ~= countryid -- do not repeat country as administrative belonging or place 
												then
											level[lc].propertyValue = level[lc].item:formatStatements(property) -- (Best statement?)
											if properties_with_children_hash[property] then -- next lower level / child item 
												if property == 'P156' then -- follows
													dataValues = dataValues .. '<br/>'
													.. '• ' .. level[lc].propertyValue.value
												else -- indent
													dataValues = dataValues .. '<br/>'
													.. string.rep('&nbsp;', lc) .. '• ' .. level[lc].propertyValue.value
												end
											else -- next higher level / parent item
												if property == 'P155' then -- followed by
													dataValues = '• ' .. level[lc].propertyValue.value
													.. '<br/>' .. dataValues
												else -- indent
													dataValues = '• ' .. level[lc].propertyValue.value
													.. '<br/>&nbsp;' .. dataValues:gsub('<br/>', '<br/>&nbsp;')
												end
											end
											lc = lc+1
										else
											break
										end
									else
										break
									end
								end -- while lc
								if lang == 'sv' then
									wikicategory = wikicategory .. '[[Kategori:Databox med ' .. lc .. ' nivåer]]' 
								end
							end
						end
					end
					if property == 'P155' then -- P155 (follows) was a bulleted list
						if level[1] then
							followed_by = mw.wikibase.getBestStatements(level[1].item.id, 'P156')
							if followed_by and #followed_by == 1 then -- P156 (followed by) may also be a bulleted list
								-- Show merged chronology list, including this wikidata object
								dataValues = dataValues
								.. "<p>• \'\'\'" .. item:getLabel() .. "\'\'\'"
								if lang == 'da' or lang == 'sv'  then
									propertyValue.label = 'Kronologi'
									merged_chronology = true
								end
								if lang == 'en' then
									propertyValue.label = 'Chronology'
									merged_chronology = true
								end
							end
						end
					end

				else -- not a multi-level list
					if datatype == 'url' then  -- only show first url
						if statements[1].mainsnak.snaktype == 'value' then
							if #statements[1].mainsnak.datavalue.value>40 then -- replace long url by "link"
								if lang == 'sv' then
									dataValues = frame:preprocess('[' .. statements[1].mainsnak.datavalue.value .. ' länk]')
								else
									dataValues = frame:preprocess('[' .. statements[1].mainsnak.datavalue.value .. ' link]')
								end
							else -- hide "https://" or "http:// and / in the end"
								dataValues = frame:preprocess('[' .. statements[1].mainsnak.datavalue.value 
									.. ' ' .. statements[1].mainsnak.datavalue.value:gsub('https?://', ''):gsub('/$', '') .. ']')
							end
						end
					
					elseif datatype == 'geo-shape' then -- only show first geo-shape
						if lang == 'sv' then
							dataValues = frame:preprocess('[https://commons.wikimedia.org/wiki/' 
								.. statements[1].mainsnak.datavalue.value:gsub(' ', '_') .. ' kartlänk]')
						else
							dataValues = frame:preprocess('[https://commons.wikimedia.org/wiki/' 
								.. statements[1].mainsnak.datavalue.value:gsub(' ', '_') .. ' link]')
						end
					
					elseif datatype == 'monolingualtext' then
						for _, s in pairs(statements) do
							if s.mainsnak.snaktype == 'value' 
							   and s.mainsnak.datavalue.value.language == lang then
								dataValues = frame:preprocess(s.mainsnak.datavalue.value)
								if lang == 'sv' then
									wikicategory = wikicategory .. '[[Kategori:Databox med monolingualtext]]'
								end
								break -- Maximum one monolingualtext per property
							end
						end
					else 
						dataValues = frame:preprocess(propertyValue.value)
						if #statements > 1 then
							dataValues = listSeparate(dataValues, list_separator)
						end
						if datatype == 'time' then
							dataValues = year(dataValues, lang, replaceTime)
						end
						if #statements >= math.max(list_length-3,3) 
						   and lang == 'sv' then --Warning on long but not hidden list
							wikicategory = wikicategory .. '[[Kategori:Databox med lång lista]]'
						end
					end
				end
				
				-- Replace "no value" with a dash:
				if dataValues  and noValueDict[lang] then
					dataValues = dataValues:gsub('<span>' .. noValueDict[lang] .. '</span>', '<span>–</span>')
				end
				
				-- Render table row:
				if dataValues and dataValues ~= '<span>–</span>' then
					dataTable:tag('tr')
						:tag('th')
						:css({
			   				['vertical-align'] = 'text-top',
						})
							:attr('scope', 'row')
							:attr('colspan', '1')
							:wikitext(frame:preprocess(propertyValue.label)):done()
						:tag('td')
						:css({
			   				['vertical-align'] = 'text-top',
						 })
							:attr('colspan', '2')
							:wikitext(dataValues)
							:wikitext('<sup class="noprint Inline-Template">&nbsp;[[File:Arbcom_ru_editing.svg|' 
								.. edit_message .. '|8px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' 
								.. item.id .. '#' .. property .. ']]</sup>')
				end -- if dataValues
			
			end -- if #statements
		end -- if datatype
	end -- for property
	
	--Automatic coordinate location map(s)
	if zoom >= 0 then
		local coordinates_statements = item:getBestStatements('P625') -- P625 is coordinate location
		local cnt = 0
		for _, s in pairs(coordinates_statements) do
			if s.mainsnak.datavalue and s.mainsnak.datavalue.value.globe == 'http://www.wikidata.org/entity/Q2' then
				cnt = cnt + 1
				databoxRoot:wikitext(buildInteractiveMap(width, s.mainsnak.datavalue.value, item.id, zoom))
				if lang == 'sv' then
					if cnt == 1 then
						wikicategory = wikicategory .. '[[Kategori:Sidor med kartor skapade med Databox]]' .. ' '
					else
						wikicategory = wikicategory .. '[[Kategori:Sidor med flera kartor skapade med Databox]]' .. ' '
					end
				end
			end
			if cnt >= maxFiles then
				break -- Show maximum maxFiles maps or coordinates.
			end
		end -- for
	end

	--Other commonsMedia files: show first good file for each property that have media legend in content language
	if tonumber(height) > 0 then
		for _, property in pairs(properties) do
			local datatype = item.claims[property][1].mainsnak.datatype
			local statements = item:getBestStatements(property)
			if (datatype == 'commonsMedia')
				and not property_blacklist_hash[property] 
				and property ~= 'P18' then -- Image showed separately
	
				local files = item:getBestStatements(property)
				if #files >= 1 then
					local file
					local caption
					for _, i in pairs(files) do
			 			if i.qualifiers then
			 				if i.qualifiers.P2096 then -- P2096 is 'caption'
			 					for _, q in pairs(i.qualifiers.P2096) do 
			 						if q.snaktype == 'value' and q.datavalue.value.language == lang then
										caption = q
						 				file = i
										break
			 						end
			 					end
			 				elseif langId and i.qualifiers.P407 then -- P407 is 'language of work or name'
			 					for _, q in pairs(i.qualifiers.P407) do 
			 						if q.snaktype == 'value' 
			 							and q.datavalue.value.id == langId -- local language
			 						then
						 				file = i
						 				break
			 						end
			 					end
			 				end
			 			end -- if i.qualifiers
			 			if file then
			 				break
			 			end
			 		end -- for _, i in pairs(files) 
			 		if file then
			 			if caption then
				 	 		databoxRoot
							:tag('div')
							:css({
								['text-align'] = 'center',
								padding = '0.0em 0.4',
							})
							:wikitext('[[File:' .. file.mainsnak.datavalue.value .. '|frameless|' 
					   			.. width .. 'x' .. height .. 'px]]')			
					   		databoxRoot
					   		:tag('div')
				   			:css({
								['text-align'] = 'center',
								['font-size'] = '90%',
								['line-height'] = '140%',
								['padding-bottom'] = '0.5em',
							})
							:wikitext(caption.datavalue.value.text)
							:wikitext('<sup class="noprint Inline-Template">&nbsp;[[File:Arbcom_ru_editing.svg|' 
								.. edit_message .. '|8px|baseline|class=noviewer|link=https://www.wikidata.org/wiki/' 
								.. item.id .. '#' .. property .. ']]</sup>')
						else -- no caption
							databoxRoot
							:tag('div')
							:css({
								['text-align'] = 'center',
								padding = '0.0em 0.4',
							})
							:wikitext('[[File:' .. file.mainsnak.datavalue.value .. '|frameless|' 
					   			.. width .. 'x' .. height .. 'px]]')			
					   		databoxRoot
					   		:tag('div')
				   			:css({
								['text-align'] = 'center',
								['font-size'] = '90%',
								['line-height'] = '140%',
								['padding-bottom'] = '0.5em',
							})

			 			end -- if caption
						if lang == 'sv' then
							if property == 'P242' then -- P242 is 'locator map image'
								wikicategory = wikicategory .. '[[Kategori:Sidor med översiktskartor skapade med Databox]]' .. ' '
							else
								wikicategory = wikicategory .. '[[Kategori:Sidor med andra mediafiler i Databox]]' .. ' '
							end 
						end
					end -- if file
				end -- if #files
			end -- if datatype
		end -- for _, property
	end -- if height

	--Category
	if mw.title.getCurrentTitle().namespace == 0 then -- Only in main namespace 
		if tostring(databoxRoot):match('<span>Q%d+</span>') then
			if lang == 'da' then
				wikicategory = wikicategory .. '[[Kategori:Databox der viser Qid-kode]]' .. ' '
			elseif lang == 'sv' then
				wikicategory = wikicategory .. '[[Kategori:Databox som visar qid-kod]]' .. ' '
			elseif lang == 'en' then
				wikicategory = wikicategory .. '[[Category:Databox that shows qid code]]' .. ' '
			elseif lang == 'frr' then
				wikicategory = wikicategory .. '[[Kategorie:Databox mit Qid-Code]]' .. ' '
			end
		end
		if #wikicategory then
			databoxRoot:wikitext(wikicategory)
		end
  	end
	if #dump then
		databoxRoot:wikitext(dump)
	end
	return tostring(databoxRoot)
end -- function

return p