Module:Dane
From WikiZagłębie
Documentation for this module may be created at Module:Dane/doc
-- Moduł:Dane – wyświetlanie danych z dane.wikizaglebie.org w infoboksach.
--
-- Szablon infoboksu wywołuje {{#invoke:Dane|infobox|<nazwa szablonu>}}; moduł bierze parametry
-- z artykułu, uzupełnia puste wartościami z elementu powiązanego z artykułem i rozwija
-- „Szablon:<nazwa szablonu>/rdzeń” (dotychczasowa treść infoboksu).
--
-- Zasada: wartość wpisana w infoboksie ma pierwszeństwo. Z dane uzupełniany jest tylko parametr
-- oznaczony w mapie „uzupełniaj”: true, i tylko gdy żaden parametr tej samej właściwości
-- nie jest wypełniony (np. zdjęcie/grafika). Mapa: MediaWiki:WZBDataEditor-infobox-map.json
-- (ta sama, której używa zakładka „Edytuj dane”).
local p = {}
local MAP_PAGE = 'MediaWiki:WZBDataEditor-infobox-map.json'
local MONTHS = { 'stycznia', 'lutego', 'marca', 'kwietnia', 'maja', 'czerwca', 'lipca',
'sierpnia', 'września', 'października', 'listopada', 'grudnia' }
local MONTHS_NOM = { 'styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', 'lipiec',
'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień' }
-- jak EMPTY_VALUES w InfoboxReader.php
local EMPTY = {}
for _, v in ipairs( { '', '?', '??', '???', '-', '–', '—', 'brak', 'brak danych', 'b.d.', 'bd',
'nieznany', 'nieznana', 'nieustalony', 'nieustalona', 'nie było', 'n/d', 'od', '...', '…' } ) do
EMPTY[v] = true
end
local ustring = mw.ustring
-- Wikitekst → zwykły tekst (bez komentarzy, przypisów, nawiasów linków i pogrubień)
local function plain( s )
s = ustring.gsub( s, '<!%-%-.-%-%->', '' )
s = ustring.gsub( s, '<ref[^>]-/>', '' )
s = ustring.gsub( s, '<ref[^>]*>.-</ref>', '' )
s = ustring.gsub( s, '%[%[[^%]|]*|([^%]]*)%]%]', '%1' )
s = ustring.gsub( s, '%[%[([^%]]*)%]%]', '%1' )
s = ustring.gsub( s, "'''?", '' )
return mw.text.trim( s )
end
-- Zaślepki z pustych infoboksów: xxx, ****, 000, 00,0, „xxx (gm. xxx)” (jak PLACEHOLDER_PATTERN)
local function isPlaceholder( t )
if EMPTY[t] then
return true
end
local core = mw.text.trim( ( ustring.gsub( t, '%s*%(.*%)$', '' ) ) )
return ( ustring.match( core, '^xx+$' ) or ustring.match( core, '^%*%*+$' )
or ustring.match( core, '^0+$' ) or ustring.match( core, '^0+[.,]0+$' )
or ustring.match( core, '^00+%-000$' )
or ustring.match( ' ' .. t .. ' ', '[^%w]xxx+[^%w]' ) ) ~= nil
end
local function normalizeFile( name )
name = ustring.gsub( mw.text.trim( name ), '^[Pp]lik:', '' )
name = ustring.gsub( name, '^[Ff]ile:', '' )
return ustring.lower( ( ustring.gsub( name, '_', ' ' ) ) )
end
local function isEmpty( value, rule, skippedFiles )
if value == nil then
return true
end
local t = ustring.lower( plain( value ) )
if isPlaceholder( t ) then
return true
end
if rule['konwersja'] == 'plik' and skippedFiles[normalizeFile( t )] then
return true
end
-- „pozycja = top” itp. – współrzędne bez cyfr
return rule['konwersja'] == 'współrzędne' and not ustring.find( t, '%d' )
end
local function roman( n )
local out = ''
for _, pair in ipairs( { { 1000, 'M' }, { 900, 'CM' }, { 500, 'D' }, { 400, 'CD' }, { 100, 'C' },
{ 90, 'XC' }, { 50, 'L' }, { 40, 'XL' }, { 10, 'X' }, { 9, 'IX' }, { 5, 'V' }, { 4, 'IV' }, { 1, 'I' } } ) do
while n >= pair[1] do
out = out .. pair[2]
n = n - pair[1]
end
end
return out
end
-- Data w stylu artykułów WikiZagłębia: [[11 stycznia]] [[1932]]
local function dateText( v )
local y, mo, d = v.time:match( '^([+-]%d+)%-(%d%d)%-(%d%d)' )
y, mo, d = tonumber( y ), tonumber( mo ), tonumber( d )
if not y then
return nil
end
local era = y < 0 and ' p.n.e.' or ''
y = math.abs( y )
local precision = v.precision or 11
if precision >= 11 and mo > 0 and d > 0 then
return '[[' .. d .. ' ' .. MONTHS[mo] .. ']] [[' .. y .. ']]' .. era
elseif precision == 10 and mo > 0 then
return MONTHS_NOM[mo] .. ' [[' .. y .. ']]' .. era
elseif precision == 9 then
return '[[' .. y .. ']]' .. era
elseif precision == 8 then
local decade = y - y % 10
return 'lata ' .. decade .. '–' .. ( decade + 9 ) .. era
elseif precision == 7 then
return roman( math.floor( ( y - 1 ) / 100 ) + 1 ) .. ' wiek' .. era
end
return tostring( y ) .. era
end
local function numberText( v, rule )
local n = tonumber( v.amount )
if not n then
return nil
end
local suffix = rule['dopisek'] or ''
-- małe powierzchnie czytelniej w hektarach
if suffix == ' km²' and math.abs( n ) < 1 then
n, suffix = n * 100, ' ha'
end
n = math.floor( n * 1e6 + 0.5 ) / 1e6
return mw.language.getContentLanguage():formatNum( n ) .. suffix
end
local function dms( x, pos, neg )
local hemisphere = x < 0 and neg or pos
x = math.abs( x )
local deg = math.floor( x )
local minutes = ( x - deg ) * 60
local min = math.floor( minutes )
local sec = math.floor( ( minutes - min ) * 60 + 0.5 )
if sec == 60 then
sec, min = 0, min + 1
end
if min == 60 then
min, deg = 0, deg + 1
end
return string.format( '%d°%02d′%02d″%s', deg, min, sec, hemisphere )
end
-- Element: etykieta (bez części z „wzorca”, np. „gmina $1”) z linkiem do artykułu, jeśli jest
local function itemText( id, rule )
local label = mw.wikibase.getLabel( id ) or id
local shown = label
-- „gmina $1” → „Będzin”; przy „wyrażenie” (np. „Linia kolejowa nr $1”) pełna nazwa
local prefix, suffix = ( rule['wyrażenie'] and '' or rule['wzorzec'] or '' ):match( '^(.-)%$1(.*)$' )
if prefix then
local lower = ustring.lower( label )
if prefix ~= '' and ustring.sub( lower, 1, ustring.len( prefix ) ) == ustring.lower( prefix ) then
shown = ustring.sub( shown, ustring.len( prefix ) + 1 )
end
if suffix ~= '' and ustring.sub( lower, -ustring.len( suffix ) ) == ustring.lower( suffix ) then
shown = ustring.sub( shown, 1, -ustring.len( suffix ) - 1 )
end
shown = mw.text.trim( shown )
end
local page = mw.wikibase.getSitelink( id )
if rule['bez linku'] then
-- szablon sam robi [[…]] – nazwa artykułu, gdy jest
return page or shown
end
if page then
return page == shown and ( '[[' .. page .. ']]' ) or ( '[[' .. page .. '|' .. shown .. ']]' )
end
return shown
end
local function snakText( snak, rule )
if not snak or snak.snaktype ~= 'value' or not snak.datavalue then
return nil
end
local v = snak.datavalue.value
local valueType = snak.datavalue.type
if valueType == 'wikibase-entityid' then
return itemText( v.id or ( 'Q' .. v['numeric-id'] ), rule )
elseif valueType == 'time' then
return dateText( v )
elseif valueType == 'quantity' then
return numberText( v, rule )
elseif valueType == 'globecoordinate' then
return dms( v.latitude, 'N', 'S' ) .. ' ' .. dms( v.longitude, 'E', 'W' )
elseif valueType == 'monolingualtext' then
return v.text
elseif valueType == 'string' then
return v
end
return nil
end
-- Pierwsza wartość, a przy „wiele”: true wszystkie (np. zawody, kluby) rozdzielone „separatorem”;
-- przy „do” (zakres dat, np. okres budowy) – „początek–koniec”; przy „takNie” – „tak” albo nic
local function valueText( entityId, property, rule )
local statements = mw.wikibase.getBestStatements( entityId, property )
if rule['konwersja'] == 'takNie' then
-- pole tak/nie („zabytek”): „tak”, gdy wśród wartości jest element z „wartość”
for _, statement in ipairs( statements ) do
local snak = statement.mainsnak
if snak.snaktype == 'value' and snak.datavalue and snak.datavalue.value.id == rule['wartość'] then
return 'tak'
end
end
return nil
end
if rule['do'] then
local endStatements = mw.wikibase.getBestStatements( entityId, rule['do'] )
local startText = snakText( statements[1] and statements[1].mainsnak, rule )
local endText = snakText( endStatements[1] and endStatements[1].mainsnak, rule )
if startText and endText then
return startText .. '–' .. endText
end
return startText or ( endText and ( 'do ' .. endText ) )
end
if not rule['wiele'] then
return snakText( statements[1] and statements[1].mainsnak, rule )
end
local texts = {}
for _, statement in ipairs( statements ) do
local text = snakText( statement.mainsnak, rule )
if text and text ~= '' then
table.insert( texts, text )
end
end
return #texts > 0 and table.concat( texts, rule['separator'] or ', ' ) or nil
end
--- Parametry infoboksu uzupełnione z dane (do testów: p._uzupelnij( 'Miasto infobox', {}, 'Q263' ))
-- @param map tabela mapy zamiast strony MediaWiki:… (tylko do testów)
-- @return table args, table lista uzupełnionych parametrów
function p._uzupelnij( templateName, args, entityId, map )
local filled = {}
entityId = entityId or mw.wikibase.getEntityIdForCurrentPage()
if not entityId then
return args, filled
end
map = map or mw.loadJsonData( MAP_PAGE )
local config = map[templateName]
if not config or not config['pola'] then
return args, filled
end
local skippedFiles = {}
for _, name in ipairs( map['_pomijane pliki'] or {} ) do
skippedFiles[normalizeFile( name )] = true
end
-- parametry według właściwości
local byProperty = {}
for param, rule in pairs( config['pola'] ) do
local property = rule['właściwość']
byProperty[property] = byProperty[property] or {}
table.insert( byProperty[property], param )
end
local result = {}
for k, v in pairs( args ) do
result[k] = v
end
for property, params in pairs( byProperty ) do
local target, anyFilled = nil, false
for _, param in ipairs( params ) do
local rule = config['pola'][param]
if not isEmpty( args[param], rule, skippedFiles ) then
anyFilled = true
break
end
if rule['uzupełniaj'] then
target = param
end
end
if target and not anyFilled then
local text = valueText( entityId, property, config['pola'][target] )
if text and text ~= '' then
result[target] = text
table.insert( filled, target )
end
end
end
return result, filled
end
--- {{#invoke:Dane|infobox|Biogram infobox}} w Szablon:Biogram infobox
function p.infobox( frame )
local templateName = mw.text.trim( frame.args[1] or '' )
local core = frame.args['rdzeń'] or ( 'Szablon:' .. templateName .. '/rdzeń' )
local parent = frame:getParent()
local args = {}
for k, v in pairs( parent and parent.args or {} ) do
args[k] = v
end
-- błąd odczytu z dane nie może zepsuć infoboksu – wtedy tylko parametry z artykułu
local ok, filledArgs = pcall( p._uzupelnij, templateName, args )
return frame:expandTemplate{ title = core, args = ok and filledArgs or args }
end
return p
