Vai al contenuto

Modulo:Fumetto e animazione

Questa pagina è semiprotetta. Può essere modificata solo da utenti registrati
Da Wikipedia, l'enciclopedia libera.

Modulo Lua di appoggio al template {{Fumetto e animazione}}, per gestirne le funzioni di categorizzazione automatica.

Ha una sottopagina di configurazione: Modulo:Fumetto e animazione/Configurazione.


--[[
Questo modulo è in appoggio al template Fumetto e animazione per gestirne
le funzioni di categorizzazione automatica
]]

require('strict')

local cfg = mw.loadData("Modulo:Fumetto e animazione/Configurazione")
local error_category = 'Errori di compilazione del template Fumetto e animazione'

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

-- Restituisce un valore senza grassetto e corsivo
local function unformat(value)
	return value:gsub("^''' *(.-) *'''$", '%1'):gsub("^'' *(.-) *''$", '%1')
end

-- Converte la lettera iniziale di una stringa da minuscola a maiuscola
local function ucfirst(str)
	return str:gsub('^%l', string.upper)
end

-- Restituisce il valore cercandolo in un'eventuale wikilink a inizio stringa
local function delink(value, strict)
	if not value then return end
	local pattern
	if value:find('|') then
		pattern = "^[' ]*%[%[ *([^%|%[%]]-) *%|.-%]%]"
	else
		pattern = "^[' ]*%[%[ *([^%[%]]-) *%]%]"
	end
	if strict then
		pattern = pattern .. '$'
	end
	return ucfirst(value:match(pattern) or unformat(value))
end

-- Restituisce un numero variabile di valori uno alla volta
local function extract_values(args, base_name)
	local index = 0
	return function()
			index = index + 1
			local value = args[base_name .. ' ' .. tostring(index)]
			if not value and index == 1 then
				value = args[base_name]
			end
			return value
		end
end

-- Restituisce un numero variabile di generi uno alla volta
local function extract_genres(value)
	local index = 0
	local substrings = mw.text.split(value, ' *, *')
	return function()
			index = index + 1
			if not substrings[index] then return end
			local genre
			local link, tail = substrings[index]:match("^[' ]*(%[%[.-%]%]%a*)'*(.*)$")
			if link then
				genre = link:gsub('^%[%[', ''):gsub('^[^%[%]%|]+%|', ''):gsub('%]%]', '')
			else
				genre, tail = substrings[index]:match("^([' ]*[%w%(%) ]+[%w%(%)])( *.*)$")
			end
			if genre then
				genre = unformat(mw.text.trim(genre:lower()))
				genre = cfg.alias_genere[genre] or genre
			end
			if cfg.genere_wikilink[genre] then
				return genre, tail
			else
				return substrings[index], ''
			end
		end
end

-- Parsifica i parametri rimuovendo le stringhe vuote
local function parse_args(args)
	local ret = {}
	for key, value in pairs(args) do
		value = mw.text.trim(value)
		if value ~= '' then
			ret[key] = value
		end
	end
	return ret
end

-- =============================================================================
--                           classe Media
-- =============================================================================

local Media = {}

-- Costruttore della classe Media
function Media:new(args)
	local self = {}
	setmetatable(self, { __index = Media })

	self.args = args
	self.tipo = self:_getType()
	self.sottotipo = self:_getSubtype()
	self.name = self:_getName()
	self.alias = self:_getAlias()

	return self
end

-- Verifica se il medium corrisponde a uno di quelli indicati
function Media:inArray(...)
	for _, value in ipairs({ ... }) do
		if self.tipo == value or self.sottotipo == value or self.name == value then
			return true
		end
	end
	return false
end

-- Ottiene l'alias del medium per le categorizzazioni più generiche
function Media:_getAlias()
	return self:_getValue('alias_tipo', 'alias_sottotipo')
end

-- Ottiene il nome del medium per le categorizzazioni più specifiche
function Media:_getName()
	return self:_getValue('tipo', 'sottotipo')
end

-- Verifica che il sottotipo di medium sia riconosciuto
function Media:_getSubtype()
	if cfg.sottotipo[self.tipo] and cfg.sottotipo[self.tipo][self.args.sottotipo] then
		return self.args.sottotipo
	end
end

-- Verifica che il tipo di medium sia riconosciuto
function Media:_getType()
	if cfg.tipo[self.args.tipo] or cfg.sottotipo[self.args.tipo] then
		return self.args.tipo
	end
end

-- Cerca il nome o l'alias del medium nella configurazione
function Media:_getValue(type_table_name, subtype_table_name)
	return cfg[subtype_table_name][self.tipo] and
		cfg[subtype_table_name][self.tipo][self.sottotipo] or
		cfg[type_table_name][self.tipo]
end

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

local CategoryManager = {}

-- Costruttore della classe CategoryManager
function CategoryManager:new(args)
	local self = {}
	setmetatable(self, { __index = CategoryManager })

	self.args = args
	self.media = Media:new(args)
	self.error_category = false
	self.year = self:_getYear()
	self.genre_table_name = self:_getGenreTableName()
	self.categories = {}
	self.tables_matched = {}

	self:_addCategories()

	return self
end

-- Restituisce l'elenco di categorie
function CategoryManager:listCategories()
	return table.concat(self.categories)
end

-- Avvia la categorizzazione automatica dell'opera
function CategoryManager:_addCategories()
	if self.genre_table_name then
		self:_addCategoriesBy('genere')
	end

	if self.media.name == "serie televisive d'animazione" then
		self
			:_addCategoriesBy('studio_serieTV')
			:_addCategoriesBy('paese', 'fp', self._categorizeTVSeriesByCountry)
	elseif self.media.name == "film d'animazione direct-to-video" then
		self:_addCategoriesBy('paese', 'mp', self._categorizeDTVFilmByCountry)
	elseif self.media.name == "webserie d'animazione" then
		self:_addCategoriesBy('paese', 'fp', self._categorizeWebseriesByCountry)
	elseif self.media.name == "film d'animazione per la televisione" then
		self
			:_addCategoriesBy('studio_film')
			:_addCategoriesBy('paese', 'mp', self._categorizeTVFilmByCountry)
	elseif self.media.tipo == 'manga' then
		self
			:_addCategoriesBy('editore_manga')
			:_addCategoriesBy('editore')
			:_addCategoriesBy('target')
	elseif self.media:inArray('manhua', 'manhwa') then
		self:_addCategoriesBy('editore')
	elseif self.media.tipo == 'light novel' then
		self
			:_addCategory(self.media.tipo)
			:_addCategory('Romanzi in giapponese')
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore')
	elseif self.media.tipo == 'fumetto' and self.media.sottotipo then
		self
			:_addCategoriesBy('etichetta_sottotipo', nil, self._substituteParameter)
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore_sottotipo', nil, self._substituteParameter)
			:_addCategoriesBy('editore')
			:_addCategoriesBy('paese', 'mp', self._categorizeComicByCountry)
		if not self.tables_matched['etichetta_sottotipo'] and
				not self.tables_matched['editore_sottotipo'] then
			self:_addCategory(self.media.name)
		end
	elseif self.media.tipo == 'fumetto' then
		self
			:_addCategoriesBy('etichetta')
			:_addCategoriesBy('editore')
			:_addCategoriesBy('paese', 'mp', self._categorizeComicByCountry)
	end

	if self.media:inArray('anime', 'cartone') then
		self:_addCategoriesBy('studio')
		local broadcaster
		local network = self.args['rete 1'] or self.args.rete
		if self.media:inArray('film TV', 'serie TV') then
			broadcaster = self:_getCategory(network, 'rete')
		elseif network and self.media:inArray('ONA', 'webserie') then
			self.error_category = true
		end
		if not network and self.media:inArray('film TV', 'ONA', 'serie TV', 'webserie') then
			local platform = self.args['streaming 1'] or self.args.streaming
			broadcaster = self:_getCategory(platform, 'streaming')
		end
		if broadcaster then
			self:_addCategory(mw.message.newRawMessage(broadcaster, self.media.alias):plain())
		end
	end

	-- categorizzazione per tipo delle opere non suddivise per anno
	if self.media:inArray('film direct-to-video', 'manhua', 'manhwa', 'ONA', 'webserie') then
		self:_addCategory(self.media.name)
	-- categorizzazione delle opere suddivise per anno
	elseif self.media.name and self.year then
		local media = self.media.sottotipo ~= 'serie TV' and
			self.media.alias or self.media.name
		self:_addCategory(string.format('%s del %s', media, self.year))
	end

	if self.error_category == true then
		self:_addCategory(error_category)
	end
end

-- Aggiunge categorie per una lista di valori
function CategoryManager:_addCategoriesBy(table_name, gender, callback)
	local base_name = table_name:match('[^_]+')
	for value in extract_values(self.args, base_name) do
		if table_name == 'genere' then
			self:_addCategoryByGenre(value)
		elseif table_name == 'paese' then
			self:_addCategoryByCountry(value, gender, callback)
		elseif self:_isCategoryRedundant(table_name) == false then
			local category = self:_getCategory(value, table_name)
			if category then
				if callback then
					category = callback(self, category)
				end
				self.tables_matched[table_name] = true
				self:_addCategory(category)
			end
		end
	end
	return self
end

-- Formatta e aggiunge una categoria
function CategoryManager:_addCategory(category)
	if self.args.categorie ~= 'no' or category == error_category then
		table.insert(self.categories, string.format('[[Categoria:%s]]', category))
	end
	return self
end

-- Aggiunge la categoria per Paese dopo aver individuato l'aggettivo
-- maschile o femminile, singolare o plurale adeguato
function CategoryManager:_addCategoryByCountry(country, gender, callback)
	local frame = mw.getCurrentFrame()
	local success, adj = pcall(frame.expandTemplate, frame, {
		title = 'Template:AggNaz/' .. delink(country, true),
		args = { gender }
	})
	if not success then
		self.error_category = true
	elseif self:_isCategoryRedundant('paese') == false then
		callback(self, adj)
	end
end

-- Aggiunge una o più categorie per genere dell'opera
function CategoryManager:_addCategoryByGenre(value)
	local position = self.args['posizione template']
	local top = self.media:inArray('anime', 'manga', 'manhua', 'manhwa', 'light novel')
	if top and (position == 'corpo' or position == 'coda') then return end
	for genre in extract_genres(value) do
		if cfg.genere_wikilink[genre] then
			local categories = cfg[self.genre_table_name][genre]
			if categories then
				for _, category in ipairs(categories) do
					self:_addCategory(category)
				end
			end
		else
			self.error_category = true
		end
	end
end

-- Categorizza un fumetto per Paese
function CategoryManager:_categorizeComicByCountry(adj)
	if adj == 'giapponesi' or adj == 'cinesi' or adj == 'coreani' then
		return
	elseif adj == 'belgi' or adj == 'francesi' then
		adj = 'franco-belgi'
	end
	self:_addCategory('Fumetti ' .. adj)
end

-- Categorizza una serie direct-to-video per Paese
function CategoryManager:_categorizeDTVFilmByCountry(adj)
	self:_addCategory("Film d'animazione " .. adj)
	if self.year then
		self:_addCategory(string.format('Film %s del %s', adj, self.year))
	end
end

-- Categorizza un film televisivo per Paese
function CategoryManager:_categorizeTVFilmByCountry(adj)
	self:_addCategory(string.format("Film d'animazione %s per la televisione", adj))
end

-- Categorizza una serie TV per Paese
function CategoryManager:_categorizeTVSeriesByCountry(adj)
	if adj ~= 'giapponesi' or self.args['paese 2'] then
		self:_addCategory("Serie televisive d'animazione " .. adj)
	end
end

-- Categorizza una webserie per Paese
function CategoryManager:_categorizeWebseriesByCountry(adj)
	self:_addCategory('Webserie ' .. adj)
end

-- Cerca il nome di una categoria nella configurazione
function CategoryManager:_getCategory(key, table_name)
	key = delink(key)
	key = cfg['alias_' .. table_name] and cfg['alias_' .. table_name][key] or key
	return cfg[table_name][key]
end

-- Individua la tabella dei generi idonei al tipo di opera nella configurazione
function CategoryManager:_getGenreTableName()
	local table_name
	if self.media:inArray('anime', 'manga') then
		table_name = 'genere_animanga'
	elseif self.media.tipo == 'light novel' then
		table_name = 'genere_light_novel'
	elseif self.media:inArray('fumetto', 'manhua', 'manhwa') then
		table_name = 'genere_fumetto'
	end
	return table_name
end

-- Ricava l'anno dell'opera dalle date di inizio e fine
function CategoryManager:_getYear()
	if not self.args['data inizio'] then return end
	local start_date = self.args['data inizio'] or ''
	local end_date = self.args['data fine'] or ''
	local year = (start_date .. ' ' .. end_date):match('%f[%w]%d%d%d%d%f[%W]')
	if year then
		return year
	else
		self.error_category = true
	end
end

-- Verifica che non sia già stata aggiunta una sottocategoria
function CategoryManager:_isCategoryRedundant(table_name)
	if cfg.sottocategorie[table_name] then
		for _, value in ipairs(cfg.sottocategorie[table_name]) do
			if self.tables_matched[value] then
				return true
			end
		end
	end
	return false
end

-- Popola l'argomento numerato in una stringa col tipo di opera
function CategoryManager:_substituteParameter(msg)
	return mw.message.newRawMessage(msg, self.media.name):plain()
end

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

local p = {}

-- Funzione per il template FeA, categorizza la voce
function p.categorie(frame)
	if mw.title.getCurrentTitle().namespace ~= 0 then return end
	local args = parse_args(frame:getParent().args)
	return CategoryManager:new(args):listCategories()
end

-- Funzione per il template FeA, elabora i wikilink dei generi
function p.generi(frame)
	local ret = {}
	local args = parse_args(frame.args)
	for genre, tail in extract_genres(args[1]) do
		local wikilink = cfg.genere_wikilink[genre]
		table.insert(ret, wikilink and (wikilink .. tail) or genre)
	end
	return table.concat(ret, ', ')
end

-- Funzione per il manuale, mostra l'elenco di valori e rispettivi alias per
-- una data tabella nella configurazione
function p.tabella_configurazione(frame)
	local table_name = parse_args(frame.args)[1]
	if not cfg[table_name] then return end
	local root = mw.html.create('table')
	root
		:addClass('wikitable sortable')
		:tag('tr')
			:tag('th'):wikitext('Valore'):done()
			:tag('th'):wikitext('Alias'):done()
			:tag('th'):wikitext('Risultato'):done()
	local keys, reverse_alias = {}, {}
	local alias_table_name = table_name:find('genere') and 'genere' or table_name
	for alias, name in pairs(cfg['alias_' .. alias_table_name] or {}) do
		if not reverse_alias[name] then reverse_alias[name] = {} end
		table.insert(reverse_alias[name], alias)
	end
	for key in pairs(cfg[table_name]) do table.insert(keys, key) end
	table.sort(keys)
	local pre_table_name
	if table_name == 'editore_sottotipo' or table_name == 'etichetta_sottotipo' then
		pre_table_name = cfg.sottotipo.fumetto
	elseif table_name == 'rete' or table_name == 'streaming' then
		pre_table_name = cfg.alias_sottotipo.cartone
	end
	for _, key in ipairs(keys) do
		local result, values = {}, {}
		reverse_alias[key] = reverse_alias[key] or {}
		if pre_table_name then
			for _, subtype in pairs(pre_table_name) do
				local msg = cfg[table_name][key]
				subtype = ucfirst(subtype)
				table.insert(values, mw.message.newRawMessage(msg, subtype):plain())
			end
		elseif type(cfg[table_name][key]) == 'string' then
			values = { cfg[table_name][key] }
		else
			values = cfg[table_name][key]
		end
		for _, str in ipairs(values) do
			if table_name ~= 'genere_wikilink' then
				str = string.format('[[:Categoria:%s]]', str)
			end
			table.insert(result, str)
		end
		table.sort(result)
		result = table.concat(result, '<br />')
		table.sort(reverse_alias[key])
		reverse_alias[key] = table.concat(reverse_alias[key], '<br />')
		root:tag('tr')
			:tag('td'):wikitext(key):done()
			:tag('td'):wikitext(reverse_alias[key]):done()
			:tag('td'):wikitext(result):done()
	end
	return tostring(root)
end

return p