ମଡ୍ୟୁଲ:Author
Documentation for this module may be created at ମଡ୍ୟୁଲ:Author/doc
-- Global variables.
dateModule = require( "Module:Date" )
tableToolsModule = require( "Module:TableTools" )
categories = {} -- List of categories to add page to.
--------------------------------------------------------------------------------
-- Get a given or family name property. This concatenates (with spaces) all
-- statements of the given property in order of the series ordinal (P1545)
-- qualifier. @TODO fix this.
function getNameFromWikidata( item, property )
local statements = item:getBestStatements( property )
local out = {}
if statements[1] ~= nil and statements[1].mainsnak.datavalue ~= nil then
local itemId = statements[1].mainsnak.datavalue.value.id
table.insert( out, mw.wikibase.label( itemId ) or '' )
end
return table.concat( out, ' ' )
end
--------------------------------------------------------------------------------
function getPropertyValue( item, property )
local statements = item:getBestStatements( property )
if statements[1] ~= nil and statements[1].mainsnak.datavalue ~= nil then
return statements[1].mainsnak.datavalue.value
end
end
--------------------------------------------------------------------------------
-- The 'Wikisource' format for a birth or death year is as follows:
-- "?" or empty for unknown (or still alive)
-- Use BCE for years before year 1
-- Approximate dates:
-- Decades or centuries: "1930s" or "20th century"
-- Circa: "c/1930" or "c. 1930" or "ca 1930" or "circa 1930"
-- Tenuous year: "1932/?"
-- Choice of two or more years: "1932/1933"
-- This is a slightly overly-complicated function, but one day will be able to be deleted.
-- @param string type Either 'birth' or 'death'
-- @return string The year to display
function formatWikisourceYear( year, type )
if year == nil or year == '' then
return ''
end
local yearParts = mw.text.split( year, '/', true )
-- Ends in a question mark.
if yearParts[2] == '?' then
addCategory( 'Authors with unknown ' .. type .. ' dates' )
if tonumber( yearParts[1] ) == nil then
addCategory( 'Authors with non-numeric ' .. type .. ' dates' )
else
addCategory( dateModule.era( yearParts[1] ) .. ' authors' )
addCategory( yearParts[1] .. ' ' .. type .. 's' )
end
return yearParts[1] .. '?'
end
-- Starts with one of the 'circa' abbreviations
local circaNames = { 'c', 'c.', 'ca', 'ca.', 'circa' }
for _, circaName in pairs( circaNames ) do
if yearParts[1] == circaName then
addCategory( 'Authors with approximate ' .. type .. ' dates' )
local out = 'c. ' .. yearParts[2]
if tonumber( yearParts[2] ) == nil then
addCategory( 'Authors with non-numeric ' .. type .. ' dates' )
else
addCategory( dateModule.era( yearParts[2] ) .. ' authors' )
addCategory( yearParts[2] .. ' ' .. type .. 's' )
end
return out
end
end
-- If there is more than one year part, and they're all numbers, add categories.
local allPartsAreNumeric = true
if #yearParts > 1 then
for _, yearPart in pairs( yearParts ) do
if tonumber( yearPart ) ~= nil then
addCategory( yearPart .. ' ' .. type .. 's' )
addCategory( dateModule.era( yearPart ) .. ' authors' )
else
allPartsAreNumeric = false
end
end
if allPartsAreNumeric then
addCategory( 'Authors with approximate birth dates' )
end
end
-- Otherwise, just use whatever's been given
if #yearParts == 1 and tonumber( year ) == nil then
addCategory( 'Authors with non-numeric ' .. type .. ' dates' )
end
if #yearParts == 1 or allPartsAreNumeric == false then
addCategory( year .. ' ' .. type .. 's' )
end
return year
end
--------------------------------------------------------------------------------
-- Get a formatted year of the given property and add to the relevant categories.
-- P569 date of birth
-- P570 date of death
-- P1317 floruit
function formatWikidataYear( item, property )
-- Check sanity of inputs.
if item == nil or string.sub( property, 1, 1 ) ~= 'P' then
return ''
end
local type = 'birth'
if property == 'P570' then
type = 'death'
end
-- Get this property's statements.
local statements = item:getBestStatements( property )
if #statements == 0 then
-- If there are no statements of this type, add to 'missing' category.
if type == 'birth' or type == 'death' then
addCategory( 'Authors with missing ' .. type .. ' dates' )
end
local isHuman = item:formatPropertyValues( 'P31' ).value == 'human'
if type == 'death' and isHuman then
-- If no statements about death, assume to be alive.
addCategory( 'Living authors' )
end
end
-- Compile a list of years, one from each statement.
local years = {}
for _, statement in pairs( statements ) do
local year = getYearStringFromSingleStatement( statement, type )
table.insert( years, year )
end
years = tableToolsModule.removeDuplicates( tableToolsModule.compressSparseArray( years ) )
-- If no year found yet, try for a floruit date.
if #years == 0 or table.concat( years, '/' ) == '?' then
floruitStatements = item:getBestStatements( 'P1317' )
for _, statement in pairs( floruitStatements ) do
-- If all we've got so far is 'unknown', replace it.
if table.concat( years, '/' ) == '?' then
years = {}
end
addCategory( 'Authors with floruit dates' )
year = getYearStringFromSingleStatement( statement, 'floruit' )
table.insert( years, year )
end
end
years = tableToolsModule.removeDuplicates( tableToolsModule.compressSparseArray( years ) )
-- table.sort( years );
return table.concat( years, '/' )
end
--------------------------------------------------------------------------------
-- Take a statement of a given property and make a human-readable year string
-- out of it, adding the relevant categories as we go.
-- @param table statement The statement.
-- @param string type One of 'birth' or 'death'.
function getYearStringFromSingleStatement( statement, type )
local snak = statement.mainsnak
-- We're not using mw.wikibase.formatValue because we only want years.
-- No value. This is invalid for birth dates (should be 'somevalue'
-- instead), and indicates 'still alive' for death dates.
if snak.snaktype == 'novalue' and type == 'birth' then
addCategory( 'Authors with missing birth dates' )
return ''
end
if snak.snaktype == 'novalue' and type == 'death' then
addCategory( 'Living authors' )
return ''
end
-- Unknown value.
if snak.snaktype == 'somevalue' then
addCategory( 'Authors with unknown ' .. type .. ' dates' )
return '?'
end
-- Extract year from the time value.
local _,_, extractedYear = string.find( snak.datavalue.value.time, '([%+%-]%d%d%d+)%-' )
year = math.abs( tonumber( extractedYear ) )
addCategory( dateModule.era( extractedYear ) .. ' authors' )
-- Century & millennium precision.
if snak.datavalue.value.precision == 6 or snak.datavalue.value.precision == 7 then
local ceilfactor = 100
local precisionName = 'century'
if snak.datavalue.value.precision == 6 then
ceilfactor = 1000
precisionName = 'millennium'
end
local cent = math.max( math.ceil( year / ceilfactor ), 1 )
-- @TODO: extract this to use something like [[en:wikipedia:Module:Ordinal]]
local suffix = 'th'
if string.sub( cent, -1 ) == '1' and string.sub( cent, -2 ) ~= '11' then
suffix = 'st'
elseif string.sub( cent, -1 ) == '2' and string.sub( cent, -2 ) ~= '12' then
suffix = 'nd'
elseif string.sub( cent, -1 ) == '3' and string.sub( cent, -2 ) ~= '13' then
suffix = 'rd'
end
year = cent .. suffix .. ' ' .. precisionName
addCategory( 'Authors with approximate ' .. type .. ' dates' )
end
if snak.datavalue.value.precision == 8 then -- decade precision
year = math.floor( tonumber( year ) / 10 ) * 10 .. 's'
addCategory( 'Authors with approximate ' .. type .. ' dates' )
end
if tonumber( extractedYear ) < 0 then
year = year .. ' BCE'
end
-- Remove from 'Living authors' if that's not possible.
if tonumber( extractedYear ) < tonumber( os.date( '%Y' ) - 110 ) then
removeCategory( 'Living authors' )
end
-- Add to e.g. 'YYYY births' category (before we add 'c.' or 'fl.' prefixes).
if type == 'birth' or type == 'death' then
addCategory( year .. ' ' .. type .. 's' )
end
-- Extract circa (P1480 = sourcing circumstances, Q5727902 = circa)
if statement.qualifiers ~= nil and statement.qualifiers.P1480 ~= nil then
for _,qualifier in pairs(statement.qualifiers.P1480) do
if qualifier.datavalue.value.id == 'Q5727902' then
addCategory( 'Authors with approximate ' .. type .. ' dates' )
year = 'c. ' .. year
end
end
end
-- Add floruit abbreviation.
if type == 'floruit' then
year = 'fl. ' .. year
end
return year
end
--------------------------------------------------------------------------------
-- Add a category to the current list of categories. Do not include the Category prefix.
function addCategory( category )
for _, cat in pairs( categories ) do
if cat == category then
-- Already present
return
end
end
table.insert( categories, category )
end
--------------------------------------------------------------------------------
-- Remove a category. Do not include the Category prefix.
function removeCategory( category )
for catPos, cat in pairs( categories ) do
if cat == category then
table.remove( categories, catPos )
end
end
end
--------------------------------------------------------------------------------
-- Get wikitext for all categories added using addCategory.
function getCategories()
table.sort( categories )
local out = ''
for _, cat in pairs( categories ) do
out = out .. '[[Category:' .. cat .. ']]'
end
return out
end
--------------------------------------------------------------------------------
-- Check a given title as having the appropriate dates as a disambiguating suffix.
function checkTitleDatesAgainstWikidata( title, wikidata_id )
-- All disambiguated author pages have parentheses in their titles.
local titleHasParentheses = string.find( tostring( title ), '%d%)' )
if titleHasParentheses == nil then
return
end
-- The title should end with years in the same format as is used in the page
-- header but with a normal hyphen instead of an en dash.
local birthYear = date( { type = 'birth'; wikidata_id = wikidata_id } )
local deathYear = date( { type = 'death'; wikidata_id = wikidata_id } )
local dates = '(' .. birthYear .. '-' .. deathYear .. ')'
if string.sub( tostring( title ), -string.len( dates ) ) ~= dates then
addCategory( 'Authors with title-date mismatches' )
end
end
--------------------------------------------------------------------------------
-- Get a formatted string of the years that this author lived,
-- and categorise in the appropriate categories.
-- The returned string starts with a line break (<br />).
function dates( args )
local item = mw.wikibase.getEntity()
if args.wikidata_id ~= nil and args.wikidata_id ~= '' then
-- This check required because getEntity can't copy with empty strings.
item = mw.wikibase.getEntity( args.wikidata_id )
end
local outHtml = mw.html.create()
-- Check disambiguated page titles for accuracy.
checkTitleDatesAgainstWikidata( args.pagetitle or mw.title.getCurrentTitle(), args.wikidata_id )
-- Get the dates (do death first, so birth can override categories if required):
-- Death.
local wikidataDeathyear = formatWikidataYear( item, 'P570' ) -- P570 Date of death
local wikisourceDeathyear = formatWikisourceYear( args.deathyear, 'death' )
if args.deathyear == nil or args.deathyear == '' then
args.deathyear = wikidataDeathyear
else
-- For Wikisource-supplied death dates.
args.deathyear = wikisourceDeathyear
addCategory( 'Authors with override death dates' )
if item ~= nil and wikisourceDeathyear ~= wikidataDeathyear then
addCategory( 'Authors with death dates differing from Wikidata' )
end
if tonumber( args.deathyear ) ~= nil then
addCategory( dateModule.era( args.deathyear ) .. ' authors' )
end
end
if args.deathyear == '' and item == nil then
addCategory( 'Authors with missing death dates' )
end
-- Birth.
local wikidataBirthyear = formatWikidataYear( item, 'P569' ) -- P569 Date of birth
local wikisourceBirthyear = formatWikisourceYear( args.birthyear, 'birth' )
if args.birthyear == nil or args.birthyear == '' then
args.birthyear = wikidataBirthyear
else
-- For Wikisource-supplied birth dates.
args.birthyear = wikisourceBirthyear
addCategory( 'Authors with override birth dates' )
if item ~= nil and wikisourceBirthyear ~= wikidataBirthyear then
addCategory( 'Authors with birth dates differing from Wikidata' )
end
if tonumber( args.birthyear ) ~= nil then
addCategory( dateModule.era( args.birthyear ) .. ' authors' )
end
end
if args.birthyear == '' then
addCategory( 'Authors with missing birth dates' )
end
-- Put all the output together, including manual override of the dates.
local dates = ''
if args.dates ~= nil and args.dates ~= '' then
-- The parentheses are repeated here and in getFormattedDates()
addCategory( 'Authors with override dates' )
dates = '<br />(' .. args.dates .. ')'
else
dates = getFormattedDates( args.birthyear, args.deathyear )
end
outHtml:wikitext( dates .. getCategories() )
return tostring( outHtml )
end
--------------------------------------------------------------------------------
-- Get a single formatted date, with no categories.
-- args.year, args.type, args.wikidata_id
function date( args )
if args.type == nil then
args.type = 'birth'
end
if args.year == nil or args.year == '' then
if args.wikidata_id == '' then
-- Nillify if not given.
args.wikidata_id = nil
end
local item = mw.wikibase.getEntity( args.wikidata_id )
local property = 'P570' -- P570 Date of death
if args.type == 'birth' then
property = 'P569' -- P569 Date of birth
end
return formatWikidataYear( item, property )
else
return formatWikisourceYear( args.year, args.type )
end
end
--------------------------------------------------------------------------------
-- Get the actual parentheses-enclosed HTML string that shows the dates.
function getFormattedDates( birthyear, deathyear )
local dates = ''
if birthyear ~= '' or deathyear ~= '' then
dates = dates .. '<br />('
end
if birthyear ~= '' then
dates = dates .. birthyear
end
if ( birthyear ~= '' or deathyear ~= '' ) and birthyear ~= deathyear then
-- Add spaces if there are spaces in either of the dates.
local spaces = ''
if string.match( birthyear .. deathyear, ' ' ) then
spaces = ' '
end
dates = dates .. spaces .. '–' .. spaces
end
if deathyear ~= '' and birthyear ~= deathyear then
dates = dates .. deathyear
end
if birthyear ~= '' or deathyear ~= '' then
dates = dates .. ')'
end
return dates
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Export all public functions.
return {
header = function( frame ) return header( frame.args ) end;
dates = function( frame ) return dates( frame.args ) end;
date = function( frame ) return date( frame.args ) end;
}