FANDOM


--[[
* Modulo per implementare le funzionalità del template Bio.
]]
 
require("Module:No globals")
 
-- Variabili globali
local args            -- argomenti passati al template
local cfg             -- configurazione
local textTable = {}  -- table per contenere la risposta da ritornare
local SPACE = " " -- HTML Entity Code per lo spazio
local attivitaParams = { "Attività", "Attività2", "Attività3" }
local nazionalitaParams = { "Nazionalità", "NazionalitàNaturalizzato", "Cittadinanza" }
 
-------------------------------------------------------------------------------
--                           Funzioni di utilità
-------------------------------------------------------------------------------
 
-- Aggiunge testo alla risposta, svolge anche la funzione di concatenatore
local function dumpText(...)
	local arg = {...}
 
	for _, val in ipairs(arg) do
		table.insert(textTable, val)
	end
end
 
-- Aggiunge una categoria alla risposta
local function dumpCategory(category)
	if mw.title.getCurrentTitle().namespace == 0 then
		dumpText("[[Categoria:", category, "]]", "\n")
	end
end
 
-- Aggiunge un wlink alla risposta, se target è nil utilizza label come target.
-- labelPrefix, se presente, viene rimosso dalla label e anteposto al wlink.
local function dumpWlink(target, label, labelPrefix)
	if target and label and labelPrefix then
		local count
		label, count = label:gsub("^" .. labelPrefix .. " ", "")
		if count == 1 then
			dumpText(labelPrefix, SPACE)
		end
	end
 
	if target and label then
	   dumpText("[[", target, "|", label, "]]")
	else
	   dumpText("[[", target or label, "]]")
	end
end
 
-- Aggiunge una immagine alla risposta, size e caption sono opzionali
local function dumpImage(name, size, caption)
	dumpText("[[File:", name, "|thumb")
 
	if size then
		dumpText("|", size, "px")
	end
	if caption then
		dumpText("|", caption)
	end
 
	dumpText("]]", "\n")
end
 
-- Aggiunge l'output del [[Template:Avviso]] e una categoria di warning alla risposta
local function dumpAvviso(tipo, immagine, immagine_a_destra, testo, category)
	local text
 
	text = mw.getCurrentFrame():expandTemplate {
		title = "Avviso",
		args = {
			["tipo"] = tipo,
			["immagine"] = immagine,
			["immagine a destra"] = immagine_a_destra,
			["testo"] = testo
		}
	}
 
	dumpText(text)
	if category then
		dumpCategory(cfg.categorie[category])
	end
end
 
-- Wrapper di mw.title.exists, verifica sia che name sia valido, sia che esista
local function titleExists(name)
	local title = mw.title.new(name)
 
	return title and title.exists
end
 
-- Se date inizia con "1 " o "1°" ritorna una nuova data che inizia per "1º", altrimenti date
local function fixFirstOfMonth(date)
	date = date:gsub("^1%s", "1º ")
	date = date:gsub("^1\194\176", "1º")
 
	return date
end
 
-- Ritorna "ed" se nextWord inizia con "e", altrimenti "e"
local function getEufonica(nextWord)
	return nextWord:sub(1, 1) == "e" and "ed" or "e"
end
 
-- Parsifica un TimeValue di Wikidata e ne ritorna "giornomese, anno"
local function parseWikidataTimeValue(property)
	local entity, value, year, month, day, daymonth
 
	entity = mw.wikibase.getEntityObject()
	if entity and entity.claims and
	   entity.claims[property] and #entity.claims[property] > 0 and
	   entity.claims[property][1].mainsnak.snaktype == "value" then
		value = entity.claims[property][1].mainsnak.datavalue.value
		year, month, day = value.time:match(".+(%d%d%d%d%d)%-(%d%d)%-(%d%d).+")
		if value.precision == 11 then
			month = mw.getLanguage("it"):formatDate("F", tonumber(year) .. "-" .. month .. "-" .. day)
			daymonth = tonumber(day) .. " " .. month
		end
		if value.precision == 9 or value.precision == 11 then
			year = tonumber(year) .. (value.time:sub(1, 1) == "-" and " a.C." or "")
		end
	end
 
	return daymonth, year
end
 
-------------------------------------------------------------------------------
--                           Parsing parametri
-------------------------------------------------------------------------------
 
-- Utilizzata da parseParams per controllare il valore di un parametro.
-- Ritorna true se il valore è valido altrimenti false.
local function checkParamValue(value, valueTest, otherArgs)
	local ret = true
 
	if type(valueTest) == "function" then
		ret = valueTest(value, otherArgs)
	elseif type(valueTest) == "string" and not value:match(valueTest) then
		ret = false
	end
 
	return ret
end
 
-- Parsifica i parametri passati al modulo e aggiunge eventuali categorie di errore.
-- Ritorna i parametri conosciuti scartando quelli valorizzati a stringa vuota.
local function parseParams(origArgs)
	local paramcfg = require("Modulo:Bio/Parametri")
	local retArgs = {}
 
	-- controlla i parametri conosciuti e li copia
	for k, v in pairs(origArgs) do
		if paramcfg.params[k] then
			if v ~= "" then
				retArgs[k] = v
			end
		else
			dumpAvviso(cfg.warningParams.tipo,
					   cfg.warningParams.immagine,
					   cfg.warningParams.immagine_a_destra,
					   cfg.warningParams.testo:gsub("$1", "il parametro '" ..
							(tonumber(k) and (v == "" and " " or v) or k ) .. "' è sconosciuto"), "unknown-params")
		end
	end
 
	-- controlla il valore
	for i, validator in pairs(paramcfg.validators) do
		if retArgs[validator.param] then
			if not checkParamValue(retArgs[validator.param], validator.valuetest, retArgs) then
				if validator.errmsg then
					dumpAvviso(cfg.warningParams.tipo,
							   cfg.warningParams.immagine,
							   cfg.warningParams.immagine_a_destra,
							   cfg.warningParams.testo:gsub("$1", validator.errmsg), nil)
				end
				dumpCategory(cfg.categorie["wrong-params"])
			end
		end
	end
 
	return retArgs
end
 
-- Cerca alcuni parametri se mancanti su Wikidata
local function checkWikidata()
	local daymonth, year
	-- GiornoMeseNascita e AnnoNascita
	if not args["GiornoMeseNascita"] or not args["AnnoNascita"] then
		daymonth, year = parseWikidataTimeValue("P569")
		args["GiornoMeseNascita"] = args["GiornoMeseNascita"] or daymonth
		args["AnnoNascita"] = args["AnnoNascita"] or year
	end
	-- GiornoMeseMorte e AnnoMorte
	if not args["GiornoMeseMorte"] or not args["AnnoMorte"] then
		daymonth, year = parseWikidataTimeValue("P570")
		args["GiornoMeseMorte"] = args["GiornoMeseMorte"] or daymonth
		args["AnnoMorte"] = args["AnnoMorte"] or year
	end
end
 
-------------------------------------------------------------------------------
--                           classe Categories
-------------------------------------------------------------------------------
 
local Categories = {}
 
function Categories:new()
	local self = {}
	local sortkey, plurals
	setmetatable(self, { __index = Categories,
						 __tostring = function(t) return self:_tostring() end })
	textTable = {}
	self.plurale_attivita = nil
	self.plurale_nazionalita = nil
	-- al di fuori del namespace 0 esegue comunque il controllo di attività e nazionalità
	plurals = self:_getPluralsAttivitaNazionalita()
 
	if mw.title.getCurrentTitle().namespace == 0 then
		-- imposta la magic word defaultsort
		if args["ForzaOrdinamento"] then
			sortkey = args["ForzaOrdinamento"]
		elseif args["Cognome"] and args["Nome"] then
			sortkey = args["Cognome"] .. " ," .. args["Nome"]
		end
		if sortkey then
			mw.getCurrentFrame():preprocess("{{DEFAULTSORT:" .. sortkey .. "}}")
		end
		-- Categorie impostato a "no" disabilita la categorizzazione per attività
		if args["Categorie"] ~= "no" then
			self:_addAttivitaCategories(plurals)
		end
		self:_addNatiMortiCategories()
		dumpCategory(cfg.categorie["bot"])
		-- Categoria temporanea, vedi richiesta:
		-- Speciale:LinkPermanente/66620402#Add_this_text_to_Template:Bio
		if args["Nazionalità"] then
			local entity = mw.wikibase.getEntityObject()
			if not entity or (entity.claims and not entity.claims["P27"]) then
				dumpCategory("Voci con template Bio e nazionalità assente su Wikidata")
			end
		end
	end
 
	return self
end
 
function Categories:_tostring()
	return table.concat(textTable)
end
 
-- Ritorna il plurale dell'attività o nil se non trovato (con eventuale warning)
function Categories:_getPluralAttivita(attivita, warning)
	local plural
 
	self.plurale_attivita = self.plurale_attivita or mw.loadData("Modulo:Bio/Plurale attività")
	plural = self.plurale_attivita[attivita]
	if not plural and warning then
		dumpAvviso(cfg.warningA.tipo, cfg.warningA.immagine, cfg.warningA.immagine_a_destra,
				   cfg.warningA.testo .. cfg.warningA.testo2a:gsub("$1", attivita) .. cfg.warningA.testo3, "warning")
	end
 
	return plural
end
 
-- Ritorna il plurale della nazionalità o nil se non trovato (con eventuale warning)
function Categories:_getPluralNazionalita(nazionalita, warning)
	local plural
 
	self.plurale_nazionalita = self.plurale_nazionalita or mw.loadData("Modulo:Bio/Plurale nazionalità")
	plural = self.plurale_nazionalita[nazionalita]
	if not plural and warning then
		dumpAvviso(cfg.warningN.tipo, cfg.warningN.immagine, cfg.warningN.immagine_a_destra,
				   cfg.warningN.testo .. cfg.warningN.testo2a:gsub("$1", nazionalita) .. cfg.warningN.testo3, "warning")
	end
 
	return plural
end
 
-- Ritorna il plurale dei parametri necessari per le categorie
function Categories:_getPluralsAttivitaNazionalita()
	local plurals = {}
 
	-- Nazionalità può essere vuota solo quando c'è Categorie=no e FineIncipit
	if not args["Nazionalità"] and not (args["Categorie"] == "no" and args["FineIncipit"]) then
		dumpAvviso(cfg.warningN.tipo, cfg.warningN.immagine, cfg.warningN.immagine_a_destra,
				   cfg.warningN.testo .. cfg.warningN.testo2b .. cfg.warningN.testo3, "warning")
	end
	-- Nazionalità può essere sbagliata solo quando c'è Categorie=no e manca FineIncipit
	if not (args["Categorie"] == "no" and not args["FineIncipit"]) then
		for _, nazionalita in ipairs(nazionalitaParams) do
			if args[nazionalita] then
				plurals[nazionalita] = self:_getPluralNazionalita(args[nazionalita], true)
			end
		end
	end
	-- Attività può essere vuota solo quando c'è Categorie=no e FineIncipit
	if not args["Attività"] and not (args["Categorie"] == "no" and args["FineIncipit"]) then
		dumpAvviso(cfg.warningA.tipo, cfg.warningA.immagine, cfg.warningA.immagine_a_destra,
				   cfg.warningA.testo .. cfg.warningA.testo2b .. cfg.warningA.testo3, "warning")
	end
	-- Attività può essere sbagliata solo quando c'è Categorie=no e manca FineIncipit
	if not (args["Categorie"] == "no" and not args["FineIncipit"]) then
		for _, attivita in ipairs(attivitaParams) do
			if args[attivita] then
				plurals[attivita] = self:_getPluralAttivita(args[attivita], true)
			end
		end
	end
 
	return plurals
end
 
-- Aggiunge le categorie: Attività nazionalità [del XYZ secolo]
function Categories:_addAttivitaCategories(plurals)
	local catname, epoca1, epoca2, added
 
	epoca1 = args["Epoca"] and cfg.epoche[args["Epoca"]]
	epoca2 = args["Epoca2"] and cfg.epoche[args["Epoca2"]]
	for _, attivita in ipairs(attivitaParams) do
		if plurals[attivita] then
			for _, nazionalita in ipairs(nazionalitaParams) do
				if plurals[nazionalita] then
					added = false
					catname = plurals[attivita] .. " " .. plurals[nazionalita]
					for _, epoca in ipairs({ epoca1, epoca2 }) do
						if epoca and titleExists("Categoria:" .. catname .. " " .. epoca) then
							dumpCategory(catname .. " " .. epoca)
							added = true
						end
					end
					-- se non è stata aggiunta la categoria per epoca1 e epoca2
					-- aggiunge la cat. semplice, e.g. "Scrittori italiani"
					if not added then
						dumpCategory(catname)
					end
				end
			end
		end
	end
end
 
-- Aggiunge le categorie: Nati/Morti nell'anno/giorno/luogo
function Categories:_addNatiMortiCategories()
	local cat1, cat2
 
	if args["AnnoNascita"] then
		cat1 = "Nati nel " .. args["AnnoNascita"]
		cat2 = "Nati nell'" .. args["AnnoNascita"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end
	end
 
	if args["AnnoMorte"] then
		if args["AnnoMorte"] == "?" then
			dumpCategory(cfg.categorie["annomorte-punto-interrogativo"])
		else
			cat1 = "Morti nel " .. args["AnnoMorte"]
			cat2 = "Morti nell'" .. args["AnnoMorte"]	
			if titleExists("Categoria:" .. cat1) then
				dumpCategory(cat1)
			elseif titleExists("Categoria:" .. cat2) then
				dumpCategory(cat2)
			end
		end
	else
		dumpCategory("Persone viventi")
	end
 
	if args["GiornoMeseNascita"] then
		cat1 = "Nati il " .. fixFirstOfMonth(args["GiornoMeseNascita"])
		cat2 = "Nati l'" .. args["GiornoMeseNascita"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end			   
	end
 
	if args["GiornoMeseMorte"] then
		cat1 = "Morti il " .. fixFirstOfMonth(args["GiornoMeseMorte"])
		cat2 = "Morti l'" .. args["GiornoMeseMorte"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end			   
	end
 
	if args["LuogoNascitaLink"] then
		cat1 = "Nati a " .. args["LuogoNascitaLink"]
		cat2 = "Nati ad " .. args["LuogoNascitaLink"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end
	elseif args["LuogoNascita"] then
		cat1 = "Nati a " .. args["LuogoNascita"]
		cat2 = "Nati ad " .. args["LuogoNascita"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end
	end
 
	if args["LuogoMorteLink"] then
		cat1 = "Morti a " .. args["LuogoMorteLink"]
		cat2 = "Morti ad " .. args["LuogoMorteLink"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end
	elseif args["LuogoMorte"] then
		cat1 = "Morti a " .. args["LuogoMorte"]
		cat2 = "Morti ad " .. args["LuogoMorte"]
		if titleExists("Categoria:" .. cat1) then
			dumpCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			dumpCategory(cat2)
		end
	end
end
 
-------------------------------------------------------------------------------
--                           classe Incipit
-------------------------------------------------------------------------------
 
local Incipit = {}
 
function Incipit:new()
	local self = {}
 
	setmetatable(self, { __index = Incipit,
						 __tostring = function(t) return self:_tostring() end })
	textTable = {}
	self:_addImmagine()
	self:_addNomeCognome()
	self:_addNascitaMorte()
	if args["PostCognomeVirgola"] then
		dumpText(",")
	end
	if args["FineIncipit"] ~= "," then
		dumpText(SPACE)
	end
	if args["FineIncipit"] then
		dumpText(args["FineIncipit"])
	else
		self:_addAttivita()
	end
	if args["Punto"] ~= "no" then
		dumpText((args["FineIncipit"] == "e" or
				  args["FineIncipit"] == "ed" or 
				  args["FineIncipit"] == ",") and
				  SPACE or ".")
	end
 
	return self
end
 
function Incipit:_tostring()
	return table.concat(textTable)
end
 
-- Ritorna true se text (AttivitàAltre, PostNazionalità e PostCognome) necessita di uno spazio iniziale
function Incipit:_needSpace(text)
	return mw.ustring.match(mw.ustring.sub(text, 1, 1), "%w") ~= nil or
		   text:sub(1, 2) == "[[" or
		   text:sub(1, 1) == "(" or
		   text:sub(1, 1) == "'" or
		   mw.ustring.sub(text, 1, 1) == "–" or
		   text:sub(1, 5) == "<span"
end
 
function Incipit:_getArticleMan(attivita)
	local article
	if cfg.articoli_maschili["uno"][attivita] then
		article = "uno"
	elseif cfg.articoli_maschili["una"][attivita] then
		article = "una"
	else
		article = "un"
	end
	return article
end
 
function Incipit:_getArticleWoman(attivita)
	local article
	-- aggiunge anche uno spazio nel caso non usi l'apostrofo
	if cfg.articoli_femminili["un"][attivita] then
		article = "un" .. SPACE
	elseif attivita and attivita:match("^[aeiou]") then
		article = "un'"
	else
		article = "una" .. SPACE
	end
	return article
end
 
function Incipit:_addImmagine()
	local caption
	if args["Immagine"] then
		if args["Didascalia"] then
			caption = args["Didascalia"]
		else
			if args["Nome"] then
				caption = args["Nome"]
			end
			if args["Cognome"] then
				caption = (caption or "") .. " " .. args["Cognome"]
			end
		end
		if args["Didascalia2"] then
			caption = (caption or "") .. "<hr />" .. args["Didascalia2"]
		end
		dumpImage(args["Immagine"], args["DimImmagine"], caption)
	elseif args["Didascalia2"] then
		-- parentesi () extra per non ritornare anche il gsub.count
		dumpText( (cfg.didascalia2:gsub("$1", args["Didascalia2"])) )
	end
end
 
function Incipit:_addNomeCognome()
	if args["Titolo"] then
		dumpText(args["Titolo"], SPACE)
	end
 
	-- inizio grassetto
	dumpText("'''")
 
	if args["Nome"] then
		dumpText(args["Nome"])
	end
 
	if args["Cognome"] then
		dumpText(SPACE, args["Cognome"])
	end
 
	-- fine grassetto
	dumpText("'''")
 
	if args["PostCognomeVirgola"] then
		dumpText(",", SPACE, args["PostCognomeVirgola"])
	end
 
	if args["PostCognome"] then
		if self:_needSpace(args["PostCognome"]) then
			dumpText(SPACE)
		end
		dumpText(args["PostCognome"])
	end
end
 
function Incipit:_addNascitaMorte()
	-- si apre la parentesi
	dumpText(SPACE, "(")
 
	if args["PreData"] then
		 dumpText(args["PreData"], ";", SPACE)
	end
 
	if args["LuogoNascita"] then
		dumpWlink(args["LuogoNascitaLink"], args["LuogoNascita"])
		if args["LuogoNascitaAlt"] then
			dumpText(SPACE, args["LuogoNascitaAlt"])
		end
		dumpText(",", SPACE)
	end
 
	if args["GiornoMeseNascita"] then
		if titleExists(args["GiornoMeseNascita"]) then
			dumpWlink(args["GiornoMeseNascita"])
		else
			dumpText(args["GiornoMeseNascita"])
		end
		dumpText(SPACE)
	end
 
	if args["AnnoNascita"] then
		if titleExists(args["AnnoNascita"]) then
			dumpWlink(args["AnnoNascita"])
		else
			dumpText(args["AnnoNascita"])
		end
	else
		dumpText("...")
	end
 
	if args["NoteNascita"] then
		dumpText(args["NoteNascita"])
	end
 
	if args["AnnoMorte"] then
		dumpText(SPACE, "–", SPACE)
		if args["LuogoMorte"] then
			dumpWlink(args["LuogoMorteLink"], args["LuogoMorte"])
			if args["LuogoMorteAlt"] then
				dumpText(SPACE, args["LuogoMorteAlt"])
			end
			dumpText(",", SPACE)
		end
 
		if args["GiornoMeseMorte"] then
			if titleExists(args["GiornoMeseMorte"]) then
				dumpWlink(args["GiornoMeseMorte"])
			else
				dumpText(args["GiornoMeseMorte"])
			end
			dumpText(SPACE)
		end
 
		if args["AnnoMorte"] then
			if args["AnnoMorte"] == "?" then
				dumpText("...")
			else
				if titleExists(args["AnnoMorte"]) then
					dumpWlink(args["AnnoMorte"])
				else
					dumpText(args["AnnoMorte"])
				end
			end
		end
	end
 
	if args["NoteMorte"] then
		dumpText(args["NoteMorte"])
	end
 
	-- si chiude la parentesi
	dumpText(")")
end
 
function Incipit:_addAttivita()
	local link_attivita = mw.loadData("Modulo:Bio/Link attività")
	local link_nazionalita = mw.loadData("Modulo:Bio/Link nazionalità")	
 
	if args["PreAttività"] then
		dumpText(args["PreAttività"], SPACE)
	else
		dumpText("è", SPACE)
		if args["AnnoMorte"] then
			dumpText((not args["Sesso"] or args["Sesso"] == "M")
					 and "stato" or "stata", SPACE)
		end
		if not args["Sesso"] or args["Sesso"] == "M" then
			dumpText(self:_getArticleMan(args["Attività"]), SPACE)
		else
			dumpText(self:_getArticleWoman(args["Attività"]))
		end
	end
 
	dumpWlink(link_attivita[args["Attività"]], args["Attività"] or "", "ex")
 
	if args["Attività2"] then
		if args["Attività3"] or args["AttivitàAltre"] then
			dumpText(",")
		else
			dumpText(SPACE, getEufonica(args["Attività2"]))
		end
		dumpText(SPACE)
		dumpWlink(link_attivita[args["Attività2"]], args["Attività2"], "ex")
	end
 
	if args["Attività3"] then
		if args["AttivitàAltre"] then
			dumpText(",")
		else
			dumpText(SPACE, getEufonica(args["Attività3"]))
		end
		dumpText(SPACE)
		dumpWlink(link_attivita[args["Attività3"]], args["Attività3"], "ex")
	end
 
	if args["AttivitàAltre"] then
		if self:_needSpace(args["AttivitàAltre"]) then
			dumpText(SPACE)
		end
		dumpText(args["AttivitàAltre"])
	end
 
	dumpText(SPACE)
	dumpWlink(link_nazionalita[args["Nazionalità"]], args["Nazionalità"] or "")
 
	if args["Cittadinanza"] then
		dumpText(SPACE, "con cittadinanza", SPACE)
		dumpWlink(link_nazionalita[args["Cittadinanza"]], args["Cittadinanza"])
	end
 
	if args["NazionalitàNaturalizzato"] then
		dumpText(SPACE)
		dumpWlink("Naturalizzazione",
				  (not args["Sesso"] or args["Sesso"] == "M" or
				  (args["Sesso"] == "F" and self:_getArticleWoman(args["Attività"]) == "un&#32;")) and
				  "naturalizzato" or "naturalizzata")
		dumpText(SPACE)
		dumpWlink(link_nazionalita[args["NazionalitàNaturalizzato"]], args["NazionalitàNaturalizzato"])
	end
 
	if args["PostNazionalità"] then
		if self:_needSpace(args["PostNazionalità"]) then
			dumpText(SPACE)
		end
		dumpText(args["PostNazionalità"])
	end
end
 
-------------------------------------------------------------------------------
--                                    API
-------------------------------------------------------------------------------
 
local p = {}
 
-- Entry-point per {{Bio}}
function p.bio(frame)
	-- lettura configurazione
	cfg = mw.loadData("Modulo:Bio/Configurazione")
 
	-- parsifica i parametri e aggiunge eventuali messaggi e categorie di errore 
	args = parseParams(frame:getParent().args)
 
	-- cerca alcuni parametri se mancanti su Wikidata
	if cfg.wikidata then
		checkWikidata()
	end
 
	return table.concat(textTable) .. tostring(Categories:new()) .. tostring(Incipit:new())
end
 
return p