Modulo:Collegamenti esterni: differenze tra le versioni
Vai alla navigazione
Vai alla ricerca
[[w:]]>Sakretsu (implemento vincoli negativi come da discussione e miglioro la rilevazione automatica nel manuale delle proprietà duplicate) |
m (una versione importata) |
Versione attuale delle 18:13, 19 set 2020
La documentazione per questo modulo può essere creata in Modulo:Collegamenti esterni/man
--[[ * Modulo che implementa il template Collegamenti esterni. ]]-- require('Modulo:No globals') local getArgs = require('Modulo:Arguments').getArgs local mWikidata = require('Modulo:Wikidata') local mCitazione = require('Modulo:Citazione') local mEditAtWikidata = require('Modulo:Modifica su Wikidata') -- Permette di definire l'ordine di visualizzazione dei vari gruppi local orderedGroupNames = { 'Ufficiali', 'Enciclopedie', 'Biografie', 'Testi', 'Letteratura', 'Politica', 'Religione', 'Architettura', 'Astronomia', 'Biologia', 'Editoria', 'Geografia', 'Linguistica', 'MAB', 'Software', 'Calcio', 'Sci', 'Sport', 'Videogiochi', 'Musica', 'Fumetti', 'Cinema' } -- Soglie di attenzione sul numero elevato di collegamenti, per categorie di controllo local MOLTI_LINK = 12 local MOLTI_LINK_2 = 15 -- Categorie di servizio local catLetta = ' letta da Wikidata' local catMultiSenzaQual = ' multipla letta da Wikidata senza qualificatore' local catMoltiLink = 'Voci con template Collegamenti esterni e molti collegamenti' local catMoltiLink2 = 'Voci con template Collegamenti esterni e molti collegamenti (soglia maggiore)' local catEmpty = 'Voci con template Collegamenti esterni senza dati da Wikidata' local catUnknownQual = 'Voci con template Collegamenti esterni e qualificatori sconosciuti' local catExcessiveLoad = 'Voci con template Collegamenti esterni e molte entità Wikidata caricate' local catDuplicates = 'Voci con template Collegamenti esterni e doppioni disattivati' -- Tabella dei qualificatori riconosciuti -- Qui vanno elencati solo quelli generali, validi per tutte le dichiarazioni -- Ciascuno ha la sua configurazione che può essere lasciata vuota o compilata come di seguito: -- "par" è il parametro del modulo Citazione da compilare col qualificatore -- "fallback" è una sottotabella coi qualificatori di ripiego in ordine di priorità -- "restricted" è true se il qualificatore impone una citazione diversa per url a stesso sito local knownQualifiers = { -- item o stringa dell'autore P50 = { par = 'autore', fallback = { 'P2093' }, restricted = true }, P478 = { par = 'volume', restricted = true }, P304 = { par = 'pagina', restricted = true }, P577 = { par = 'data', restricted = true }, -- titolo o "indicato come" o "riferito come" o pseudonimo P1476 = { par = 'titolo', fallback = { 'P1810', 'P1932', 'P742', 'P554' } }, -- url archiviato, solo per le proprietà di tipo URL P1065 = { par = 'archivio' }, P2960 = { par = 'dataarch' }, P582 = { par = 'urlmorto' }, P407 = {}, P813 = {} } -- I qualificatori di ripiego sono automaticamente aggiunti con configurazione vuota for _, t in pairs(knownQualifiers) do if t.fallback then for _, v in ipairs(t.fallback) do knownQualifiers[v] = {} end end end -- ============================================================================= -- Funzioni di utilità -- ============================================================================= -- Restituisce la configurazione delle sottopagine. -- -- @return {table} local function readConfig() local ret = {} for _, groupName in ipairs(orderedGroupNames) do ret[groupName] = mw.loadData('Modulo:Collegamenti esterni/' .. groupName) end return ret end -- Restituisce il titolo della pagina corrente rimuovendo eventuale testo tra parentesi. -- Se l'etichetta dell'elemento Wikidata contiene delle parentesi, -- non le rimuove perché significa che fanno parte del titolo. -- Con il parametro "from" (elemento Wikidata arbitrario) usa sempre l'etichetta. -- -- @param {string} from -- @return {string} local function getCurrentTitle(from) local ret local label = mWikidata._getLabel({ from }) if from then ret = label else ret = mw.title.getCurrentTitle().text if not (label and string.find(label, ' %(')) then ret = mw.text.split(ret, ' %(')[1] end end return ret end -- Restituisce il dominio dell'URL specificato. -- -- @param {string} url -- @return {string} local function getDomain(url) return mw.uri.new(url).host:gsub('^www.', '') end -- Restituisce true se l'elemento collegato alla pagina o quello specificato in from -- ha tra i valori (o loro sottoclassi) della proprietà indicata uno degli elementi specificati. -- Restituisce come secondo valore una tabella con gli ID delle entità caricate ai fini della ricerca. -- @param {string} prop - codice della proprietà 'Pxxx' -- @param {table} [entityIds] - array dei valori (strighe 'Qxxx') che può avere -- @param {string} from -- @return {boolean} -- @return {table} local function checkEntity(prop, entityIds, from) local args = { from = from, recursion = 8 } for _, entityId in ipairs(entityIds) do table.insert(args, entityId) end return mWikidata._propertyHasEntity(prop, args) end -- Converte un parametro ricevuto come 'Xxx, yyy' in { 'Xxx'=true, 'Yyy'=true } local function ParametroElenco(param) local chiavi = {} local valori = param and mw.text.split(param, ',') if valori then for _, str in ipairs(valori) do chiavi[mw.getContentLanguage():ucfirst(mw.text.trim(str))] = true end end return chiavi end -- ============================================================================= -- Classe ExtLink -- ============================================================================= -- La classe ExtLink rappresenta un singolo collegamento esterno. -- Al suo interno ha un riferimento alla propria configurazione (linkConf) -- e nel caso la proprietà Wikidata abbia più valori li raccoglie tutti. local ExtLink = {} -- Costruttore della classe ExtLink. -- -- @param {table} [url] - uno o più URL, quanti sono i valori della proprietà Wikidata -- @param {table} linkConf - la configurazione fissa per questo collegamento esterno -- @param {table} extraConf - altri elementi di configurazione ricavati dall'item -- @param {string} from - entityId se diverso da quello collegato alla pagina corrente -- @return {table} un nuovo oggetto ExtLink function ExtLink:new(url, linkConf, extraConf, from) local self = {} setmetatable(self, { __index = ExtLink }) self.url = url self.linkConf = linkConf self.extraConf = extraConf self.from = from self.title = getCurrentTitle() self.title = self.from and mWikidata._getLabel({ self.from }) or self.title self.extraConf.medium = self.linkConf.medium or 'web' return self end -- Restituisce il parametro titolo per il modulo Citazione. -- -- @param {number} i - il numero dell'URL a cui assegnare il titolo. -- @return {string} function ExtLink:_getTitolo(i) local titolo = self.extraConf.titolo[i] or self.title if self.linkConf.titolo then titolo = self.linkConf.titolo:gsub('$1', titolo) end if #self.url > 1 and self.extraConf.titolodis[i] then return string.format('%s (%s)', titolo, self.extraConf.titolodis[i]) else return titolo end end -- Restituisce il parametro altrilink per il modulo Citazione. -- Nel caso di valori multipli, genera quelli successivi al primo. -- -- @return {table} function ExtLink:_getAltriLink() if self.extraConf.restrictedData then return end local tbl = {} local titolo, specifica for i = 2, #self.url do titolo = self.extraConf.titolo[i] or self.title if self.extraConf.titolodis[i] then specifica = self.extraConf.titolodis[i] else local ripetuto = false -- controllo se stesso titolo già usato for j = 1, i - 1 do if titolo == (self.extraConf.titolo[j] or self.title) then ripetuto = true break end end if ripetuto then specifica = 'altra versione' else specifica = nil -- se titoli diversi, va bene anche senza specificazione end end if specifica then titolo = string.format('%s (%s)', titolo, specifica) end table.insert(tbl, { self.url[i], titolo }) end return tbl end -- Formatta il collegamento esterno quando la proprietà è di tipo URL. -- -- @return {string} function ExtLink:_formatPropertyURL() local formattedLinks = {} local currTitle = getCurrentTitle(self.from) local claims = mWikidata._getClaims(self.linkConf.pid, { from = self.from }) or {} for idx, claim in ipairs(claims) do local langs = mWikidata._formatQualifiers(claim, 'P407', { formatting = 'raw' }, true) langs = (#langs == 1 and langs[1] == 'Q652') and {} or langs for i, lang in ipairs(langs) do langs[i] = mw.wikibase.getLabel(lang) or lang end local formattedLink = mCitazione.cita_da_modulo( self.extraConf.medium, { url = mWikidata._formatStatement(claim), titolo = self.linkConf.titolo:gsub('$1', currTitle), lingua = table.concat(langs, ','), cid = self.linkConf.cid, urlarchivio = self.extraConf.archivio[idx], dataarchivio = self.extraConf.dataarch[idx], urlmorto = self.extraConf.urlmorto[idx] and 'sì' or (self.extraConf.archivio[idx] and 'no') }) table.insert(formattedLinks, '* ' .. formattedLink .. mEditAtWikidata._showMessage({ pid = self.linkConf.pid, qid = self.from })) end return table.concat(formattedLinks, '\n') end -- Formatta il collegamento esterno come elemento di un elenco puntato. -- -- @return {string} function ExtLink:getListItem() -- se è specificato l'URL di formattazione è una -- proprietà di tipo "identificativo esterno" altrimenti di tipo URL if not self.linkConf.url then return self:_formatPropertyURL() end local formattedLinks = {} for i = 1, self.extraConf.restrictedData and #self.url or 1 do local formattedLink = mCitazione.cita_da_modulo( self.extraConf.medium, { url = self.url[i], titolo = self:_getTitolo(i), altrilink = self:_getAltriLink(), sito = self.linkConf.opera and '' or self.extraConf.sito, opera = self.linkConf.opera, editore = self.linkConf.editore, lingua = self.linkConf.lingua, cid = self.extraConf.restrictedData and self.extraConf.cid[i] or self.linkConf.cid, autore = self.linkConf.autore or self.extraConf.autore[i], volume = self.extraConf.volume[i], p = self.extraConf.pagina[i], data = self.linkConf.data or self.extraConf.data[i], tipo = self.linkConf.tipo or self.sitodis }) table.insert(formattedLinks, '* ' .. formattedLink .. mEditAtWikidata._showMessage({ pid = self.linkConf.pid, qid = self.from })) end return table.concat(formattedLinks, '\n') end -- ============================================================================= -- Classe LinksManager -- ============================================================================= -- La classe LinksManager è la classe principale del modulo. -- Al suo interno ha un riferimento a tutti collegamenti esterni (extLinks) -- presenti in un dato elemento Wikidata e li può restituire tutti formattati -- nel modo più appropriato. local LinksManager = {} -- Costruttore della classe LinksManager. -- -- @param {table} args -- @return {table} un nuovo oggetto LinksManager function LinksManager:new(args) local self = {} setmetatable(self, { __index = LinksManager }) self.numExtLinks = 0 self.categories = {} self.catColon = '' self.from = args.from self.escludi = ParametroElenco(args.escludi) self.soloprop = ParametroElenco(args.prop) self.sologruppi = ParametroElenco(args.gruppo) self.solomedium = ParametroElenco(args.medium) -- la pagina dei test utilizza uno stub del modulo Wikidata if mw.title.getCurrentTitle().prefixedText == 'Discussioni modulo:Collegamenti esterni/test' then self:_setStubWikidata(args.stubwd) end self.extLinks = self:_getExtLinks() return self end -- Formatta e carica una o più categorie di servizio. function LinksManager:_addCategory(...) for _, category in pairs({ ... }) do if category then category = string.format('[[%sCategoria:%s]]', self.catColon, category) table.insert(self.categories, category) end end end -- Permette di specificare uno stub del modulo Wikidata -- in modo da ottenere i valori delle proprietà in modo deterministico, -- non dipendenti da modifiche utente a un elemento Wikidata. -- -- @param {table} stubwd function LinksManager:_setStubWikidata(stubwd) mEditAtWikidata = { _showMessage = function(frame) return '' end } mWikidata = stubwd self.catColon = ':' self.debug = true end -- Controlla se un elemento fa parte delle esclusioni richieste dall'utente function LinksManager:_Escluso(elemento, inclusi) return self.escludi[elemento] or (next(inclusi) and inclusi[elemento] ~= true) end -- Ottiene tutti i collegamenti esterni (configurati) presenti in un dato elemento Wikidata -- suddivisi per gruppo. -- -- @return {table} function LinksManager:_getExtLinks() local ret, groupSites = {}, {} local cfg = readConfig() local lang = mw.language.getContentLanguage() local pageContent = mw.title.getCurrentTitle():getContent() :gsub('<!%-%-.-%-%->', '') :gsub('<[Rr][Ee][Ff]%s.-/>', '') -- facilita la ricerca successiva :gsub('<[Rr][Ee][Ff].->.-</[Rr][Ee][Ff]%s*>', '') local loadedEntities = setmetatable({}, { __newindex = function(t1, key, t2) if not t2 then return end for k, v in pairs(t2) do rawset(t1, k, v) end end }) local duplicates = false -- per categoria temporanea for _, groupName in ipairs(orderedGroupNames) do -- per ogni gruppo tematico groupSites[groupName] = {} ret[groupName] = {} -- controlla se è un gruppo escluso manualmente dall'utente if self:_Escluso(groupName, self.sologruppi) then cfg[groupName] = {} end for _, linkConf in ipairs(cfg[groupName]) do -- per ogni sito configurato local claims, valido -- se il sito non è escluso manualmente dall'utente, avviene la lettura da Wikidata if not (self:_Escluso(linkConf.pid, self.soloprop) or self:_Escluso(lang:ucfirst(linkConf.medium or 'web'), self.solomedium)) then claims = mWikidata._getClaims(linkConf.pid, { from = self.from }) end -- controlla se ci sono valori if claims and #claims > 0 then -- controlla se è un sito da escludere per soggetto non pertinente if type(linkConf.vincolo) == 'table' then local vincolo, tipo = {}, 'neg' for i, v in ipairs(linkConf.vincolo) do if i % 2 ~= 0 then local ms, k = v:match('^(%-?)(.+)$') if tipo == 'neg' and ms == '' then vincolo, tipo = {}, 'pos' end if not (tipo == 'pos' and ms == '-') then vincolo[k] = linkConf.vincolo[i + 1] end end end for k, v in pairs(vincolo) do valido, loadedEntities[k] = checkEntity(k, v, self.from) if tipo == 'neg' then valido = not valido if not valido then break end elseif valido then break end end else valido = true end -- controlla che il link non sia generato da altri template in voce if valido and linkConf.template then for template in mw.text.gsplit(linkConf.template, ',') do template = mw.text.trim(template):gsub(' +', ' +') local first_char = template:match('^.') first_char = string.format('[%s%s]', first_char:upper(), first_char:lower()) if pageContent:match('{{%s*' .. template:gsub('^.', first_char) .. '%s*[|}]') then valido, duplicates = false, true break end end end end -- se il sito è abilitato, viene aggiunto a quelli da mostrare if valido then local url = {} local extraConf = { cid = {}, titolodis = { count = 0 } } -- per ogni dichiarazione for i, claim in ipairs(claims) do for k, t in pairs(knownQualifiers) do if t.par then extraConf[t.par] = extraConf[t.par] or { count = 0 } local properties = { k, unpack(t.fallback or {}) } -- ricava i qualificatori generali e ne tiene il conto for _, v in ipairs(properties) do extraConf[t.par][i] = mWikidata._formatQualifiers(claim, v) or t.par == 'urlmorto' and mWikidata._formatQualifiers(claim, v, { snaktype = 'somevalue' }) or nil if extraConf[t.par][i] then extraConf[t.par].count = extraConf[t.par].count + 1 if t.restricted then extraConf.restrictedData = true end break end end end end if claim.qualifiers then knownQualifiers.multi = {} -- cerca un disambiguante per il titolo di ogni url for qualifierId in mw.text.gsplit(linkConf.multi or '', ',') do if qualifierId == '' then break end qualifierId = mw.text.trim(qualifierId):upper() if extraConf.titolodis[i] == nil and claim.qualifiers[qualifierId] then local args = { nq = '1', formatting = 'raw' } local formattedQualifier = mWikidata._formatQualifiers(claim, qualifierId, args) if formattedQualifier then extraConf.titolodis[i] = mw.wikibase.getLabel(formattedQualifier) if extraConf.titolodis[i] then extraConf.titolodis.count = extraConf.titolodis.count + 1 end end end knownQualifiers.multi[qualifierId] = true end -- categoria di servizio in presenza di qualificatori non riconosciuti for qualifierId in pairs(claim.qualifiers) do if not (knownQualifiers[qualifierId] or knownQualifiers.multi[qualifierId]) then self:_addCategory(catUnknownQual) break end end end -- crea l'url claim = mWikidata._formatStatement(claim) if linkConf.cid then extraConf.cid[i] = linkConf.cid .. ' ' .. (extraConf.data[i] or claim) end if linkConf.url then -- se proprietà di tipo id, il valore viene sostituito a "$1" claim = mw.message.newRawMessage(linkConf.url, claim):plain() end table.insert(url, (claim:gsub(' ', '%%20'))) end -- nome sito, di default il dominio estratto dall'url extraConf.sito = linkConf.sito or getDomain(linkConf.url) -- creazione dell'oggetto collegamento esterno, con l'url (o gli url) e gli altri dati raccolti table.insert(ret[groupName], ExtLink:new(url, linkConf, extraConf, self.from)) -- categoria per proprietà letta; se multipla e indistinguibile, usa categoria di avviso local tail = #url > 1 and linkConf.url and extraConf.titolodis.count ~= #url and extraConf.titolo.count < #url - 1 and catMultiSenzaQual or catLetta self:_addCategory(linkConf.pid .. tail) -- per verificare se un sito è ripetuto nel gruppo groupSites[groupName][extraConf.sito] = (groupSites[groupName][extraConf.sito] or 0) + 1 -- conteggio complessivo dei collegamenti self.numExtLinks = self.numExtLinks + 1 end end end -- verifica se un sito è ripetuto nel gruppo for _, groupName in ipairs(orderedGroupNames) do for _, extLink in ipairs(ret[groupName]) do -- necessaria la disambiguazione (più URL con lo stesso dominio nel gruppo), -- se configurata tramite "sitodis" nella configurazione. if groupSites[groupName][extLink.extraConf.sito] > 1 then extLink.sitodis = extLink.linkConf.sitodis end end end -- categorie di servizio su numero di link/entità caricate e doppioni omessi local catnumero = self.numExtLinks == 0 and catEmpty or self.numExtLinks > MOLTI_LINK_2 and catMoltiLink2 or self.numExtLinks > MOLTI_LINK and catMoltiLink self:_addCategory(catnumero, #loadedEntities > 100 and catExcessiveLoad, duplicates and catDuplicates) return ret end -- Formatta i collegamenti esterni come elenco puntato. -- -- @param {table} [groupNames] -- @return {string} function LinksManager:_formatList(groupNames) local formattedLinks = {} for _, groupName in ipairs(groupNames) do for _, extLink in ipairs(self.extLinks[groupName]) do table.insert(formattedLinks, extLink:getListItem()) end end return table.concat(formattedLinks, '\n') end -- Restituisce tutti i collegamenti esterni formattandoli come elenco puntato -- -- @return {string} function LinksManager:getList() local categories, links -- categorie di servizio categories = (mw.title.getCurrentTitle().namespace == 0 or self.debug) and table.concat(self.categories) or '' -- collegamenti links = self:_formatList(orderedGroupNames) return links .. categories end -- ============================================================================= -- Funzioni esportate -- ============================================================================= local p = {} -- Funzioni di utilità per il manuale, danno la soglia di attenzione sul n° di link. function p.threshold(frame) return MOLTI_LINK end function p.threshold2(frame) return MOLTI_LINK_2 end -- Funzione di utilità per il manuale, restituisce un elenco -- delle proprietà supportate, divise per gruppo. function p.properties(frame) local res = {} local cfg = readConfig() table.sort(orderedGroupNames) for _, groupName in ipairs(orderedGroupNames) do local wdLinks = {} for _, linkConf in ipairs(cfg[groupName]) do local wdLink = string.format('<tr><td>[[d:Property:%s|%s (%s)]]</td><td>%s</td><td>%s</td><td>%s</td></tr>', linkConf.pid, mWikidata._getLabel({ linkConf.pid }), linkConf.pid, linkConf.url or '', linkConf.cid or '', linkConf.template and ('[[t:'..linkConf.template..'|'..linkConf.template..']]') or '') table.insert(wdLinks, wdLink) end local group = frame.args[1] == 'modulo' and string.format('* [[Modulo:Collegamenti esterni/%s]] (%s)', groupName, #wdLinks) or mw.getCurrentFrame():expandTemplate { title = 'Cassetto', args = { titolo = string.format('[[Modulo:Collegamenti esterni/%s|%s]] (%s)', groupName, groupName, #wdLinks), testo = '<table class="wikitable sortable plainlinks"><tr><th>Proprietà</th><th>Formato URL</th><th>cid</th><th>Template singolo</th></tr>' .. table.concat(wdLinks, '') .. '</table>' } } table.insert(res, group) end return table.concat(res, '\n') end -- Funzione di utilità per il manuale, verifica l'assenza di proprietà duplicate. function p.checkdup(frame) local ids, vin, res = {}, {}, {} local cfg = readConfig() for _, groupName in ipairs(orderedGroupNames) do for _, linkConf in ipairs(cfg[groupName]) do local duplicate = ids[linkConf.pid] if vin[linkConf.pid] == nil then vin[linkConf.pid] = { pos = {}, neg = {} } end if vin[linkConf.pid] ~= false and linkConf.vincolo then duplicate, vin[linkConf.pid].cur = false, {} local tipo = 'neg' for i, v in ipairs(linkConf.vincolo) do if i % 2 ~= 0 then local ms, p = v:match('^(%-?)(.+)$') if tipo == 'neg' and ms == '' then vin[linkConf.pid].cur, tipo = {}, 'pos' end if not (tipo == 'pos' and ms == '-') then for _, q in ipairs(linkConf.vincolo[i + 1]) do vin[linkConf.pid].cur[p .. q] = true end end end end if tipo == 'pos' then for k in pairs(vin[linkConf.pid].cur) do for _, t in ipairs(vin[linkConf.pid].pos) do if t[k] then duplicate = true break end end for _, t in ipairs(vin[linkConf.pid].neg) do if not t[k] then duplicate = true break end end end else if #vin[linkConf.pid].neg == 1 then duplicate = true else for _, t in ipairs(vin[linkConf.pid].pos) do for k in pairs(t) do if not vin[linkConf.pid].cur[k] then duplicate = true break end end end end end table.insert(vin[linkConf.pid][tipo], vin[linkConf.pid].cur) end if duplicate then table.insert(res, linkConf.pid) else ids[linkConf.pid] = true if not linkConf.vincolo then vin[linkConf.pid] = false end end end end return #res == 0 and 'nessun duplicato' or string.format('<span class="error">%s</span>', table.concat(res, ', ')) end -- Funzione per l'utilizzo da un altro modulo. function p._main(args) return LinksManager:new(args):getList() end -- Funzione per il template {{Collegamenti esterni}}. function p.main(frame) return p._main(getArgs(frame, { parentOnly = true })) end return p