Модуль:Wikidata/chronology
Documentation for this module may be created at Модуль:Wikidata/chronology/doc
local p = {} -- p stands for package
-- technical functions for takeAdjacentNumbersFromStrings
function isYear(number) -- TODO: settings not hardcoded
return number >= 1800 and number <= 2100
end
function isAr(number)
return number >= 0 and number < 100
end
function notTooFar(number1, number2, maximalPeriod)
return number2 - number1 > 0 and number2 - number1 <= maximalPeriod
end
-- takes two strings and returns either two substrings that form the only difference between them or nil
-- considers several possible formats of substrings given in options.formats
function takeAdjacentNumbersFromStrings(options, string1, string2)
if type(string1) == 'string' and type(string2) == 'string' then
local formats = options.formats or '' -- TODO: not repeat with properties
if formats == '' then
formats = 'year, year/ar, year/year'
elseif formats == '-' then
formats = ''
end
local formats = mw.text.split(formats, ', ?')
local line = '!' .. string1 .. '!!' .. string2.. '!' -- -- TODO: without hacks
local maximalPeriod = tonumber(options.maximalPeriod) or 4 -- TODO: here or in main function?
for _, format in pairs(formats) do
if format == 'year' or format == 'number' then
local match = {mw.ustring.match(line, '^(.*%D)(%d+)(%D.*)%1(%d+)%3$')}
if #match > 0 then
local number1 = tonumber(match[2])
local number2 = tonumber(match[4])
if notTooFar(number1, number2, maximalPeriod) then
if (format == 'year' and isYear(number1) and isYear(number2))
or (format == 'number')
then
return {number1, number2}
end
end
end
elseif format == 'year/ar' or format == 'year/year' then
local match = {mw.ustring.match(line, '^(.*%D)((%d+)[-–\/](%d+))(%D.*)%1((%d+)\/(%d+))%5$')}
if #match > 0 then
local period1 = match[2]
local period1_start = tonumber(match[3])
local period1_end = tonumber(match[4])
local period2 = match[6]
local period2_start = tonumber(match[7])
local period2_end = tonumber(match[8])
if notTooFar(period1_start, period2_start, maximalPeriod) and isYear(period1_start) and isYear(period2_start)
and period2_start - period1_start == period2_end - period1_end
then
if (format == 'year/ar' and isAr(period1_end) and isAr(period2_end))
or (format == 'year/year' and isYear(period1_end) and isYear(period2_end))
then
return {period1, period2}
end
end
end
end
end
end
return nil
end
-- takes two Wikidata entities and returns either two strings or nil
-- considers sitelinks and labels in the local langauge and in English
function takeAdjacentNumbersFromEntities(options, entity1, entity2)
if entity1 and entity2 then
local adjacentNumbers = nil
local languageCodes = {mw.getContentLanguage():getCode(), 'en'}
for _, languageCode in pairs(languageCodes) do
local wikiCode = languageCode .. 'wiki'
local sitelink1 = entity1:getSitelink(wikiCode)
local sitelink2 = entity2:getSitelink(wikiCode)
adjacentNumbers = takeAdjacentNumbersFromStrings(options, sitelink1, sitelink2)
if adjacentNumbers then
return adjacentNumbers
end
local label1 = entity1:getLabel(languageCode) -- TODO: get rid of fallback?
local label2 = entity2:getLabel(languageCode)
adjacentNumbers = takeAdjacentNumbersFromStrings(options, label1, label2)
if adjacentNumbers then
return adjacentNumbers
end
end
end
return nil
end
function formatAdjacentSnak(context, options, snak)
local direction = options.direction or ''
if snak and snak.datavalue and snak.datavalue.value and snak.datavalue.value.id then
local mainText = nil
local adjacentEntity = mw.wikibase.getEntity(snak.datavalue.value.id)
if direction == 'P155' then -- previous
local adjacentNumbers = takeAdjacentNumbersFromEntities(options, adjacentEntity, options.entity) -- or reverse order
if adjacentNumbers then
mainText = adjacentNumbers[1] -- or 2
end
elseif direction == 'P156' then -- next
local adjacentNumbers = takeAdjacentNumbersFromEntities(options, options.entity, adjacentEntity)
if adjacentNumbers then
mainText = adjacentNumbers[2]
end
end
-- options should not be changed, as they are reused for P155 and P156
local optionsCopy = {}
for k, v in pairs(options) do
optionsCopy[k] = v
end
if (not optionsCopy.text) and mainText then
local prefix = options.prefix or ''
local postfix = options.postfix or ''
optionsCopy.text = prefix .. mainText .. postfix
end
local link = context.formatSnak(optionsCopy, snak)
return link
end
return nil
end
-- function is available from outside, see documentation
function p.formatAdjacentProperty(context, options)
if (not context) then error('context not specified'); end;
if (not options) then error('options not specified'); end;
if (not options.entity) then error('options.entity missing'); end;
if options.value == '-' then
return ''
end
if options.value then
return options.value
end
local properties = options.property or '' -- TODO: properties vs. property?
if properties == '' then
properties = 'Q'
elseif properties == '-' then
properties = ''
end
local properties = mw.text.split(properties, ', ?')
local direction = options.direction or '' -- TODO: not repeating inside
local formattedClaims = {}
for _, property in pairs(properties) do
if property == 'Q' then
local claims = context.selectClaims(options, direction)
if claims then
for _, claim in pairs(claims) do
if claim.mainsnak then
local link = formatAdjacentSnak(context, options, claim.mainsnak)
if link and link ~= '' then
local formattedClaim = '<span class="wikidata-claim" data-wikidata-property-id="' .. direction .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. link .. '</span>'
table.insert(formattedClaims, formattedClaim)
end
end
end
end
elseif mw.ustring.match(property, '^P%d+$') then
local claims = context.selectClaims(options, property)
if claims then
for _, claim in pairs(claims) do
if claim.qualifiers and claim.qualifiers[direction] then
for _, snak in pairs(claim.qualifiers[direction]) do
local link = formatAdjacentSnak(context, options, snak)
if link and link ~= '' then
local formattedClaim = '<span class="wikidata-claim" data-wikidata-property-id="' .. property .. '" data-wikidata-claim-id="' .. claim.id .. '">' .. link .. '</span>'
table.insert(formattedClaims, formattedClaim)
end
end
end
end
end
end
end
-- from wikidata.selectClaims
if options.limit and options.limit ~= '' and options.limit ~= '-' then
local limit = tonumber(options.limit, 10);
while #formattedClaims > limit do
table.remove(formattedClaims);
end
end
-- from wikidata.formatPropertyDefault
local out = mw.text.listToText(formattedClaims, options.separator, options.conjunction)
if out ~= '' then
if options.before then
out = options.before .. out
end
if options.after then
out = out .. options.after
end
end
return out
end
-- function is available from outside, see documentation
function p.formatChronologyProperty(context, options)
if (not context) then error('context not specified'); end;
if (not options) then error('options not specified'); end;
if (not options.entity) then error('options.entity missing'); end;
options.value = options.previousLink -- TODO: copy?
options.separator = '<br>← '
options.conjunction = '<br>← '
options.direction = 'P155'
local previousLink = p.formatAdjacentProperty(context, options)
options.value = options.nextLink
options.separator = ' →<br>'
options.conjunction = ' →<br>'
options.direction = 'P156'
local nextLink = p.formatAdjacentProperty(context, options)
local res = ''
if previousLink and previousLink ~= '' then
res = res .. '<div style="width:50%; float: left; text-align: left;">← ' .. previousLink .. '</div>'
end
if nextLink and nextLink ~= '' then
res = res .. '<div style="width:50%; float: right; text-align: right;">' .. nextLink .. ' →</div>'
end
return res
end
return p