Questa pagina è protetta dallo spostamento
Questa pagina è protetta

Modulo:Bio: differenze tra le versioni

Da Wikipedia, l'enciclopedia libera.
Vai alla navigazione Vai alla ricerca
Contenuto cancellato Contenuto aggiunto
aggiunto calcolo di Epoca quando non inserito dall'utente
categorie dopo incipit, vedi discussione
Riga 901: Riga 901:


return table.concat(errorTable) ..
return table.concat(errorTable) ..
table.concat(catTable) ..
Incipit:new():getIncipit() ..
table.concat(catTable)
Incipit:new():getIncipit()
end
end



Versione delle 10:17, 27 giu 2018

Modulo Lua che implementa le funzionalità del Template:Bio.

Ha le seguenti sottopagine di configurazione:

  • Configurazione: parametri di configurazione principali
  • Link attività: tabella di conversione per il link all'attività
  • Ex attivita: tabella contenente le attività ammesse col prefisso "ex"
  • Link nazionalità: tabella di conversione per il link alla nazionalità
  • Plurale attività: tabella di conversione per il plurale dell'attività
  • Plurale nazionalità: tabella di conversione per il plurale della nazionalità
  • Plurale attività genere: tabella di conversione da singolare maschile e femminile al plurale maschile e femminile
  • Parametri: tabella per configurare i parametri accettati dal modulo e i rispettivi valori validi
  • Cat luoghi: tabella di casi particolari per le categorie "Nati/morti a [luogo]"

Funzionamento interno

Lo schema seguente rappresenta l'ordine in cui vengono chiamate le principali funzioni, facendo riferimento a dei parametri di esempio:

{{Bio
|Nome = Giulia
|Cognome = Rossi
|Sesso = F
|LuogoNascita = Roma
|GiornoMeseNascita = 15 gennaio
|AnnoNascita = 1910
|LuogoMorte = Firenze
|GiornoMeseMorte = 15 febbraio
|AnnoMorte = 1990
|Attività = scienziata
|Nazionalità = italiana
}}

--[[
* Modulo che implementa il template Bio.
*
* Nota: non esistendo in Lua una differenziazione tra metodi pubblici e privati, 
* per convenzione, quelli privati iniziano con un underscore.
]]

require("Module:No globals")

local mString = require("Modulo:String")
local mWikidata = require("Modulo:Wikidata")
local cfg = mw.loadData("Modulo:Bio/Configurazione")
-- argomenti passati al template
local args
-- table per contenere gli errori
local errorTable = {}
-- nomi dei parametri per l'attività e la nazionalità
local attivitaParams = { "Attività", "Attività2", "Attività3" }
local nazionalitaParams = { "Nazionalità", "NazionalitàNaturalizzato", "Cittadinanza" }

-- =============================================================================
--                           Funzioni di utilità
-- =============================================================================

-- Aggiunge l'output del [[Template:Avviso]] e una categoria di warning a errorTable
local function addAvviso(testo, category)
	local text

	text = mw.getCurrentFrame():expandTemplate {
		title = "Avviso",
		args = {
			tipo = "stile",
			immagine = "[[File:Nuvola apps important.svg|40px]]",
			["immagine a destra"] = "[[File:Crystal Clear app Login Manager.svg|40px]]",
			testo = testo
		}
	}

	table.insert(errorTable, text)
	if mw.title.getCurrentTitle().namespace == 0 then
		table.insert(errorTable, string.format("[[Categoria:%s]]\n", 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

local function currentTitleEquals(name)
	local title = mw.title.getCurrentTitle().text
	title = mw.text.split(title, " %(")[1]
	return title == name or mw.getContentLanguage():lcfirst(title) == name
end

-- Se date inizia con "1 " o "1°" restituisce 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

-- Restituisce "ed" se nextWord inizia con "e", altrimenti "e"
local function getEufonica(nextWord)
	return nextWord:sub(1, 1) == "e" and "ed" or "e"
end

-- Restituisce true se uno degli argomenti del modulo specificati (params) ha almeno
-- un valore tra quelli indicati (values), altrimenti false
local function argsSearch(params, values)
	local ret = false
	for _, param in ipairs(params) do
		for _, value in ipairs(values) do
			if args[param] == value then
				return true
			end
		end
	end
	return false
end

-- =============================================================================
--                           classe ArgsParser
-- =============================================================================

local ArgsParser = {}

function ArgsParser:new()
	local self = {}
	setmetatable(self, { __index = ArgsParser })
	return self
end

-- Parsifica i parametri passati al modulo e aggiunge eventuali categorie di errore.
-- Restituisce i parametri conosciuti scartando quelli valorizzati a stringa vuota.
function ArgsParser:parse(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
			addAvviso(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 self:_checkParamValue(retArgs[validator.param], validator.valuetest, retArgs) then
				if validator.errmsg then
					addAvviso(cfg.warningParams.testo:gsub("$1", validator.errmsg), "wrong-params")
				end
			end
		end
	end

	return retArgs
end

-- Utilizzata da parse per controllare il valore di un parametro.
-- Restituisce true se il valore è valido altrimenti false.
function ArgsParser:_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

-- =============================================================================
--                           classe CategoryManager
-- =============================================================================

local CategoryManager = {}

function CategoryManager:new()
	local self = {}

	setmetatable(self, { __index = CategoryManager })
	self.plurale_attivita = nil
	self.plurale_nazionalita = nil
	self.categories = {}
	-- al di fuori del namespace 0 esegue comunque il controllo di attività e nazionalità
	self.plurals = self:_getPluralsAttivitaNazionalita()

	if mw.title.getCurrentTitle().namespace == 0 or args.Debug then
		-- imposta la magic word defaultsort
		local sortkey
		if args.ForzaOrdinamento then
			sortkey = args.ForzaOrdinamento:gsub("(.-)%s*,%s*(.*)", "%1 ,%2")
		elseif args.Pseudonimo and currentTitleEquals(args.Pseudonimo) then
			local pseudonimo = mString.collate( { args = { args.Pseudonimo } } )
			if pseudonimo ~= args.Pseudonimo then
				sortkey = pseudonimo
			end
		elseif args.Cognome and args.Nome then
			sortkey = mString.collate( { args = { args.Cognome .. " ," .. args.Nome } } )
		elseif args.Nome then
			local nome = mString.collate( { args = { args.Nome } } )
			if nome ~= args.Nome then
				sortkey = nome
			end
		end
		if sortkey then
			if args.Debug then
				-- per i test di DEFAULTSORT in Modulo:Bio/test
				table.insert(self.categories, string.format("DEFAULTSORT:%s", sortkey))
			else
				mw.getCurrentFrame():preprocess("{{DEFAULTSORT:" .. sortkey .. "}}")
			end
		end
		-- Categorie impostato a "no" disabilita la categorizzazione per attività
		if args.Categorie ~= "no" then
			self:_addAttivita(self.plurals)
		end
		self:_addNatiMorti()
		self:_addCategory(cfg.categorie["bot"])
		-- categoria di servizio per AnnoMorte (o anno corrente) - AnnoNascita > 122
		local years = {
			birth = tonumber(args.AnnoNascita),
			death = not args.AnnoMorte and os.date("%Y") or tonumber(args.AnnoMorte)
		}
		if years.birth and years.death and years.death - years.birth > 122 then
			self:_addCategory(cfg.categorie["controllo-età"])
		end
		-- eventuali categorie di servizio per Wikidata
		if not args.Debug then
			self:_addCategoriesWikidata()
		end
	end

	return self
end

function CategoryManager:getCategories()
	return self.categories
end

function CategoryManager:_addCategory(cat)
	table.insert(self.categories, string.format("[[Categoria:%s]]", cat))
end

-- Aggiunge la categoria se la pagina non ha un elemento Wikidata collegato,
-- oppure non ha la proprietà indicata.
function CategoryManager:_addCategoryWikidata(propertyId, cat)
	if not mWikidata._getProperty({ propertyId }) then
		self:_addCategory(cat)
	end
end

-- Aggiunge eventuali categorie di servizio per Wikidata, tramite controlli
-- più avanzati di quelli che si effettuano abitualmente con {{Controllo Wikidata}}.
function CategoryManager:_addCategoriesWikidata()
	-- Per Speciale:LinkPermanente/80165551#Proposta_categoria_di_servizio_biografie_con_data_di_morte_su_Wikidata
	if not args.AnnoMorte and mWikidata._getProperty({ "P570" }) then
		self:_addCategory("Voci con template Bio senza AnnoMorte ma con data di morte su Wikidata")
	end
	if mWikidata._instanceOf({ "Q5" }) then
		-- Per Speciale:LinkPermanente/66620402#Add_this_text_to_Template:Bio
		if args["Nazionalità"] then
			self:_addCategoryWikidata("P27", "Voci con template Bio e nazionalità assente su Wikidata")
		end
		-- Per Speciale:LinkPermanente/80165551#Wikidata_d:Property:P21
		if not args.Sesso or args.Sesso == "M" then
			self:_addCategoryWikidata("P21", "Voci con template Bio e sesso (M) assente su Wikidata")
		elseif args.Sesso == "F" then
			self:_addCategoryWikidata("P21", "Voci con template Bio e sesso (F) assente su Wikidata")
		end
		-- Per Speciale:LinkPermanente/80254035#Wikidata_properties_P19.2C_P20.2C_P569.2C_P570
		if args.LuogoNascita and not args.LuogoNascitaLink then
			self:_addCategoryWikidata("P19", "Voci con template Bio e LuogoNascita assente su Wikidata")
		end
		if args.LuogoNascitaLink then
			self:_addCategoryWikidata("P19", "Voci con template Bio e LuogoNascitaLink assente su Wikidata")
		end
		if args.LuogoMorte and not args.LuogoMorteLink then
			self:_addCategoryWikidata("P20", "Voci con template Bio e LuogoMorte assente su Wikidata")
		end
		if args.LuogoMorteLink then
			self:_addCategoryWikidata("P20", "Voci con template Bio e LuogoMorteLink assente su Wikidata")
		end
		if args.AnnoNascita then
			self:_addCategoryWikidata("P569", "Voci con template Bio e AnnoNascita assente su Wikidata")
		end
		if args.AnnoMorte and args.AnnoMorte ~= "?" then
			self:_addCategoryWikidata("P570", "Voci con template Bio e AnnoMorte assente su Wikidata")
		end
		if args.Immagine and not titleExists("File:" .. args.Immagine) then
			self:_addCategoryWikidata("P18", "Voci con template Bio e Immagine assente su Wikidata")
		end
		-- Per Speciale:LinkPermanente/80336084#Wikidata_properties_P27
		if (args["Nazionalità"] == "italiano" or args["Nazionalità"] == "italiana") and
		   ((tonumber(args.AnnoNascita) or 0) > 1861 or (tonumber(args.AnnoMorte) or 0) > 1861) then
		   	self:_addCategoryWikidata("P27", "Voci con template Bio e Nazionalità italiana assente su Wikidata")
		elseif args["Nazionalità"] == "statunitense" and
		   ((tonumber(args.AnnoNascita) or 0) > 1776 or (tonumber(args.AnnoMorte) or 0) > 1776) then
		   	self:_addCategoryWikidata("P27", "Voci con template Bio e Nazionalità statunitense assente su Wikidata")
		end
		-- Per Speciale:LinkPermanente/80431600#Wikidata_properties_P106
		if argsSearch(attivitaParams, { "calciatore", "ex calciatore", "calciatrice" }) then
			self:_addCategoryWikidata("P106", "Voci con template Bio e Attività assente su Wikidata (calciatore)")
		end
		if argsSearch(attivitaParams, { "attore", "attrice" }) then
			self:_addCategoryWikidata("P106", "Voci con template Bio e Attività assente su Wikidata (attore)")
		end
		if argsSearch(attivitaParams, { "politico", "politica" }) then
			self:_addCategoryWikidata("P106", "Voci con template Bio e Attività assente su Wikidata (politico)")
		end
	end
end

-- Restituisce il plurale dell'attività o nil se non trovato (con eventuale warning)
function CategoryManager:_getPluralAttivita(attivita)
	local plural

	self.plurale_attivita = self.plurale_attivita or mw.loadData("Modulo:Bio/Plurale attività")
	plural = self.plurale_attivita[attivita]
	if not plural then
		addAvviso(cfg.warningA.testo .. cfg.warningA.testo2a:gsub("$1", attivita) .. cfg.warningA.testo3, "warning")
	end

	return plural
end

-- Restituisce il plurale della nazionalità o nil se non trovato (con eventuale warning)
function CategoryManager:_getPluralNazionalita(nazionalita)
	local plural

	self.plurale_nazionalita = self.plurale_nazionalita or mw.loadData("Modulo:Bio/Plurale nazionalità")
	plural = self.plurale_nazionalita[nazionalita]
	if not plural then
		addAvviso(cfg.warningN.testo .. cfg.warningN.testo2a:gsub("$1", nazionalita) .. cfg.warningN.testo3, "warning")
	end

	return plural
end

-- Restituisce il plurale dei parametri necessari per le categorie
function CategoryManager:_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
		addAvviso(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])
			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
		addAvviso(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])
			end
		end
	end

	return plurals
end

-- Calcola il valore di Epoca se non inserito dall'utente.
function CategoryManager:_getEpoca()
	local ret
	local annoNascita = tonumber(args.AnnoNascita)
	local annoMorte = tonumber(args.AnnoMorte)
	if not annoNascita then
		annoNascita = args.AnnoNascita:match('^(%d+) a%.C%.$')
		annoNascita = annoNascita and tonumber(annoNascita) * -1
	end
	if not annoMorte then
		annoMorte = args.AnnoMorte:match('^(%d+) a%.C%.$')
		annoMorte = annoMorte and tonumber(annoMorte) * -1
	end

	if annoNascita and annoMorte and
	    annoNascita >= -500 and annoNascita <= 2100 and
	    annoMorte >= -500 and annoMorte <= 2100 and	
	   ((annoNascita >= 0 and annoMorte >= 0) or (annoNascita < 0 and annoMorte < 0)) then
	   	local sign = ''
	    if annoNascita < 0 then
	    	annoNascita, annoMorte = -annoNascita, -annoMorte
	    	sign = '-'
	    end
		local secoloNascita = math.floor((annoNascita - 1) / 100) * 100
		local secoloMorte = math.floor((annoMorte - 1) / 100) * 100
		ret = secoloNascita == secoloMorte and (sign .. secoloNascita) or nil
	end

	return ret
end

-- Aggiunge le categorie: Attività nazionalità [del XYZ secolo]
function CategoryManager:_addAttivita(plurals)
	local catname, epoca1, epoca2, added

	-- se Epoca e Epoca2 non sono stati inseriti dall'utente
	-- e AnnoNascita e AnnoMorte cadono nello stesso secolo
	-- calcola epoca1 automaticamente
	if not args.Epoca and not args.Epoca2 and args.AnnoNascita and args.AnnoMorte then
		epoca1 = self:_getEpoca()
		epoca1 = epoca1 and cfg.epoche[epoca1]
	else
		epoca1 = args.Epoca and cfg.epoche[args.Epoca]
		epoca2 = args.Epoca2 and cfg.epoche[args.Epoca2]
	end

	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
							self:_addCategory(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
						self:_addCategory(catname)
					end
				end
			end
		end
	end
end

-- Utilizzata da addNatiMorti, restituisce il nome della categoria
-- se titleLink o title sono nella lista di eccezioni Cat luoghi, altrimenti nil
function CategoryManager:_getCatLuoghi(titleLink, title, catPrefix)
	local cat

	self.catLuoghi = self.catLuoghi or mw.loadData("Modulo:Bio/Cat luoghi")
	if titleLink and title then
		cat = self.catLuoghi[titleLink]
	elseif title then
		cat = self.catLuoghi[title]
	end

	return cat and (catPrefix .. " " .. cat) or nil
end

-- Aggiunge le categorie: Nati/Morti nell'anno/giorno/luogo
function CategoryManager:_addNatiMorti()
	local cat1, cat2

	if args.AnnoNascita then
		cat1 = "Nati nel " .. args.AnnoNascita
		cat2 = "Nati nell'" .. args.AnnoNascita
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end
	end

	if args.AnnoMorte then
		if args.AnnoMorte == "?" then
			self:_addCategory(cfg.categorie["annomorte-punto-interrogativo"])
		else
			cat1 = "Morti nel " .. args.AnnoMorte
			cat2 = "Morti nell'" .. args.AnnoMorte
			if titleExists("Categoria:" .. cat1) then
				self:_addCategory(cat1)
			elseif titleExists("Categoria:" .. cat2) then
				self:_addCategory(cat2)
			end
		end
	else
		self:_addCategory(cfg.categorie["annomorte-assente"])
	end

	if args.GiornoMeseNascita then
		cat1 = "Nati il " .. fixFirstOfMonth(args.GiornoMeseNascita)
		cat2 = "Nati l'" .. args.GiornoMeseNascita
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end			   
	end
	
	if args.GiornoMeseMorte then
		cat1 = "Morti il " .. fixFirstOfMonth(args.GiornoMeseMorte)
		cat2 = "Morti l'" .. args.GiornoMeseMorte
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end			   
	end

	-- prima di verificare le categorie per LuogoNascitaLink e LuogoNascita
	-- viene controllata una lista di eccezioni
	cat1 = self:_getCatLuoghi(args.LuogoNascitaLink, args.LuogoNascita, "Nati")
	if cat1 then
		self:_addCategory(cat1)
	elseif args.LuogoNascitaLink then
		cat1 = "Nati a " .. args.LuogoNascitaLink
		cat2 = "Nati ad " .. args.LuogoNascitaLink
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end
	elseif args.LuogoNascita then
		cat1 = "Nati a " .. args.LuogoNascita
		cat2 = "Nati ad " .. args.LuogoNascita
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end
	end

	-- prima di verificare le categorie per LuogoMorteLink e LuogoMorte
	-- viene controllata una lista di eccezioni
	cat1 = self:_getCatLuoghi(args.LuogoMorteLink, args.LuogoMorte, "Morti")
	if cat1 then
		self:_addCategory(cat1)
	elseif args.LuogoMorteLink then
		cat1 = "Morti a " .. args.LuogoMorteLink
		cat2 = "Morti ad " .. args.LuogoMorteLink
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end
	elseif args.LuogoMorte then
		cat1 = "Morti a " .. args.LuogoMorte
		cat2 = "Morti ad " .. args.LuogoMorte
		if titleExists("Categoria:" .. cat1) then
			self:_addCategory(cat1)
		elseif titleExists("Categoria:" .. cat2) then
			self:_addCategory(cat2)
		end
	end
end

-- =============================================================================
--                           classe Incipit
-- =============================================================================

local Incipit = {}

function Incipit:new()
	local self = {}

	setmetatable(self, { __index = Incipit })
	self.textTable = {}
	self:_addImmagine()
	self:_addNomeCognome()
	self:_addNascitaMorte()
	if args.Pseudonimo or args.PostCognomeVirgola then
		self:_addText(",")
	end
	if args.FineIncipit then
		if self:_needSpace(args.FineIncipit) then
			self:_addText(' ')
		end
		self:_addText(args.FineIncipit)
	else
		self:_addAttivita()
	end
	if args.Punto ~= "no" then
		self:_addText((args.FineIncipit == "e" or
					  args.FineIncipit == "ed" or 
					  args.FineIncipit == ",") and
					  " " or ".")
	end

	return self
end

function Incipit:getIncipit()
	return table.concat(self.textTable)
end

-- Aggiunge testo alla risposta, svolge anche la funzione di concatenatore
function Incipit:_addText(...)
	local arg = {...}
	for _, val in ipairs(arg) do
		table.insert(self.textTable, val)
	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.
function Incipit:_addWlink(target, label, labelPrefix)
	if target and label and labelPrefix then
		local count
		label, count = label:gsub("^" .. labelPrefix .. " ", "")
		if count == 1 then
			self:_addText(labelPrefix, " ")
		end
	end

	if target and label then
		self:_addText("[[", target, "|", label, "]]")
	else
		self:_addText("[[", target or label, "]]")
	end
end

-- Aggiunge una immagine alla risposta, size e caption sono opzionali
function Incipit:_addImage(name, size, caption)
	self:_addText("[[File:", name, "|thumb")

	if size then
		self:_addText("|", size, "px")
	end
	if caption then
		self:_addText("|", caption)
	end

	self:_addText("]]", "\n")
end


-- Restituisce true se text (AttivitàAltre, PostNazionalità, PostCognome e FineIncipit) 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 "
	elseif attivita and attivita:match("^[aeiou]") then
		article = "un'"
	else
		article = "una "
	end
	return article
end

function Incipit:_addImmagine()
	local caption
	if args.Immagine then
		if args.Didascalia then
			caption = args.Didascalia
		elseif args.Pseudonimo and currentTitleEquals(args.Pseudonimo) then
			caption = args.Pseudonimo
		else
			if args.CognomePrima and args.Nome and args.Cognome then
				caption = args.Cognome .. " " .. args.Nome
			else
				if args.Nome then
					caption = args.Nome
				end
				if args.Cognome then
					caption = (caption or "") .. " " .. args.Cognome
				end
			end
		end
		if args.Didascalia2 then
			caption = (caption or "") .. "<hr />" .. args.Didascalia2
		end
		self:_addImage(args.Immagine, args.DimImmagine, caption)
	elseif args.Didascalia2 then
		-- parentesi () extra per non restituire anche il gsub.count
		self:_addText( (cfg.didascalia2:gsub("$1", args.Didascalia2)) )
	end
end

function Incipit:_addNomeCognome()
	if args.Titolo then
		self:_addText(args.Titolo, " ")
	end

	if args.Pseudonimo and currentTitleEquals(args.Pseudonimo) then
		self:_addText("'''", args.Pseudonimo, "'''")
		if args.PostPseudonimo then
			if self:_needSpace(args.PostPseudonimo) then
				self:_addText(" ")
			end
			self:_addText(args.PostPseudonimo)
		end
		self:_addText(", pseudonimo di ")
	end

	-- inizio grassetto
	self:_addText("'''")

	if args.CognomePrima and args.Nome and args.Cognome then
		self:_addText(args.Cognome, " ", args.Nome, mw.getCurrentFrame():expandTemplate{
			title = "Nota nome",
			args = { [1] = args.CognomePrima, [2] = args.Cognome }
			})
	else
		local no_space
		if args.Nome then
			self:_addText(args.Nome)
			-- niente spazio prima di Cognome se Nome termina con «d'»
			no_space = mw.ustring.match(args.Nome, " d'$") and ''
		end
		if args.Cognome then
			self:_addText(no_space or " ", args.Cognome)
		end
	end

	-- fine grassetto
	self:_addText("'''")

	if args.PostCognomeVirgola then
		self:_addText(", ", args.PostCognomeVirgola)
	elseif args.PostCognome then
		if self:_needSpace(args.PostCognome) then
			self:_addText(" ")
		end
		self:_addText(args.PostCognome)
	end

	if args.Pseudonimo and not currentTitleEquals(args.Pseudonimo) then
		self:_addText(", ", (not args.Sesso or args.Sesso == "M") and "noto" or "nota",
					  " anche con lo pseudonimo di ", "'''", args.Pseudonimo, "'''")
		if args.PostPseudonimo then
			if self:_needSpace(args.PostPseudonimo) then
				self:_addText(" ")
			end
			self:_addText(args.PostPseudonimo)
		end
	end
end

function Incipit:_addNascitaMorte()
	-- si apre la parentesi
	self:_addText(" (")

	if args.PreData then
		 self:_addText(args.PreData, "; ")
	end

	if args.LuogoNascita then
		self:_addWlink(args.LuogoNascitaLink, args.LuogoNascita)
		if args.LuogoNascitaAlt then
			self:_addText(" ", args.LuogoNascitaAlt)
		end
		self:_addText(", ")
	end

	if args.GiornoMeseNascita then
		if titleExists(args.GiornoMeseNascita) then
			self:_addWlink(args.GiornoMeseNascita)
		else
			self:_addText(args.GiornoMeseNascita)
		end
		self:_addText(" ")
	end

	if args.AnnoNascita then
		if titleExists(args.AnnoNascita) then
			self:_addWlink(args.AnnoNascita)
		else
			self:_addText(args.AnnoNascita)
		end
	else
		self:_addText("...")
	end

	if args.NoteNascita then
		self:_addText(args.NoteNascita)
	end

	if args.AnnoMorte then
		self:_addText(" – ")
		if args.LuogoMorte then
			self:_addWlink(args.LuogoMorteLink, args.LuogoMorte)
			if args.LuogoMorteAlt then
				self:_addText(" ", args.LuogoMorteAlt)
			end
			self:_addText(", ")
		end

		if args.GiornoMeseMorte then
			if titleExists(args.GiornoMeseMorte) then
				self:_addWlink(args.GiornoMeseMorte)
			else
				self:_addText(args.GiornoMeseMorte)
			end
			self:_addText(" ")
		end

		if args.AnnoMorte then
			if args.AnnoMorte == "?" then
				self:_addText("...")
			else
				if titleExists(args.AnnoMorte) then
					self:_addWlink(args.AnnoMorte)
				else
					self:_addText(args.AnnoMorte)
				end
			end
		end
	end

	if args.NoteMorte then
		self:_addText(args.NoteMorte)
	end

	-- si chiude la parentesi
	self:_addText(")")
end

function Incipit:_addAttivita()
	local link_attivita = mw.loadData("Modulo:Bio/Link attività")
	local link_nazionalita = mw.loadData("Modulo:Bio/Link nazionalità")	
	
	self:_addText(" ")
	if args["PreAttività"] then
		self:_addText(args["PreAttività"], " ")
	else
		self:_addText("è ")
		if args.AnnoMorte then
			self:_addText((not args.Sesso or args.Sesso == "M")
					 and "stato " or "stata ")
		end
		if not args.Sesso or args.Sesso == "M" then
			self:_addText(self:_getArticleMan(args["Attività"]), " ")
		else
			self:_addText(self:_getArticleWoman(args["Attività"]))
		end
	end

	self:_addWlink(link_attivita[args["Attività"]], args["Attività"] or "", "ex")

	if args["Attività2"] then
		if args["Attività3"] or args["AttivitàAltre"] then
			self:_addText(",")
		else
			self:_addText(" ", getEufonica(args["Attività2"]))
		end
		self:_addText(" ")
		self:_addWlink(link_attivita[args["Attività2"]], args["Attività2"], "ex")
	end

	if args["Attività3"] then
		if args["AttivitàAltre"] then
			self:_addText(",")
		else
			self:_addText(" ", getEufonica(args["Attività3"]))
		end
		self:_addText(" ")
		self:_addWlink(link_attivita[args["Attività3"]], args["Attività3"], "ex")
	end

	if args["AttivitàAltre"] then
		if self:_needSpace(args["AttivitàAltre"]) then
			self:_addText(" ")
		end
		self:_addText(args["AttivitàAltre"])
	end

	self:_addText(" ")
	self:_addWlink(link_nazionalita[args["Nazionalità"]], args["Nazionalità"] or "")

	if args.Cittadinanza then
		self:_addText(" con cittadinanza ")
		self:_addWlink(link_nazionalita[args.Cittadinanza], args.Cittadinanza)
	end

	if args["NazionalitàNaturalizzato"] then
		self:_addText(" ")
		self:_addWlink("Naturalizzazione",
				  (not args.Sesso or args.Sesso == "M" or
				  (args.Sesso == "F" and self:_getArticleWoman(args["Attività"]) == "un ")) and
				  "naturalizzato" or "naturalizzata")
		self:_addText(" ")
		self:_addWlink(link_nazionalita[args["NazionalitàNaturalizzato"]], args["NazionalitàNaturalizzato"])
	end

	if args["PostNazionalità"] then
		if self:_needSpace(args["PostNazionalità"]) then
			self:_addText(" ")
		end
		self:_addText(args["PostNazionalità"])
	end
end

-- =============================================================================
--                            Funzioni esportate
-- =============================================================================

local p = {}

-- Funzione per {{#invoke:Bio|categorie}} utilizzato da Modulo:Bio/test
function p.categorie(frame)
	args = ArgsParser:new():parse(frame.args)
	local categories = CategoryManager:new():getCategories()
	return table.concat(errorTable) ..
		   (args.Debug and ( table.concat(categories, '<br />'):gsub('%[%[', '[[:') ) .. '<br />' or
		   table.concat(categories))
end

-- Funzione per il template per {{Bio}}
function p.main(frame)
	-- gli errori generano avvisi, ma non interrompono l'esecuzione,
	-- come avveniva nel vecchio template.
	args = ArgsParser:new():parse(frame:getParent().args)
	local catTable = CategoryManager:new():getCategories()

	return table.concat(errorTable) ..
		   Incipit:new():getIncipit() ..
		   table.concat(catTable)
end

return p