Add centuries.lua
This commit is contained in:
304
centuries/centuries.lua
Normal file
304
centuries/centuries.lua
Normal file
@ -0,0 +1,304 @@
|
||||
-- centuries.lua
|
||||
-- A Pandoc Lua filter that automatically formats references to centuries.
|
||||
-- Copyright 2022 Bastien Dumont (bastien.dumont [at] posteo.net)
|
||||
-- This file is under the MIT License: see LICENSE for more details
|
||||
|
||||
local ERROR_CONTENT_SPAN =
|
||||
'The content of a span of class ".cty" must be a positive or negative ' ..
|
||||
'number other than 0 possibly preceded by "+" or "-".'
|
||||
|
||||
local eras = {}
|
||||
function eras:new(name, before, after, unspecified)
|
||||
self[name] = {
|
||||
before = before,
|
||||
after = after,
|
||||
unspecified = unspecified
|
||||
}
|
||||
end
|
||||
eras:new('christian', ' BC', ' AD', '')
|
||||
|
||||
local default_config = {
|
||||
-- default_unit_form is one of 'sg', 'pl' or 'none'
|
||||
default_unit_form = 'sg',
|
||||
-- default_era, unit_sg and unit_pl are plain strings
|
||||
default_era = 'christian',
|
||||
unit_sg = ' century',
|
||||
unit_pl = ' centuries',
|
||||
-- numeral_type is one of 'roman', 'arabic', 'spelled_out'
|
||||
numeral_type = 'arabic',
|
||||
-- numeral_style is one of 'lowercase', 'uppercase', 'smallcaps'
|
||||
numeral_style = 'lowercase',
|
||||
-- ordinal_suffix_pos is one of 'normal', 'exponent'
|
||||
ordinal_suffix_pos = 'normal',
|
||||
--[[
|
||||
ordinal_suffixes is a table of ordinal suffixes
|
||||
corresponding to their index number.
|
||||
If the requested number is superior to the length of the table or 0,
|
||||
the last entry will be used.
|
||||
--]]
|
||||
ordinal_suffixes = { 'rst', 'nd', 'rd', 'th' },
|
||||
-- spelled_out_ordinals is an array of strings
|
||||
spelled_out_ordinals = { 'first', 'second', 'third', 'fourth',
|
||||
'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'eleventh',
|
||||
'twelfth', 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth',
|
||||
'seventeenth', 'eighteenth', 'nineteenth', 'twentieth',
|
||||
'twenty-first' },
|
||||
-- number_part_for_ordinal_suffix is one of 'whole' or 'last_digit'
|
||||
number_part_for_ordinal_suffix = 'last_digit',
|
||||
-- parts_order is an array containing
|
||||
-- "number", "suffix", "unit" and "era" in any order
|
||||
parts_order = { "number", "suffix", "unit", "era" }
|
||||
}
|
||||
|
||||
local config = {}
|
||||
|
||||
local function restore_default_config(to_restore)
|
||||
if to_restore == nil then
|
||||
for key, value in pairs(default_config) do
|
||||
config[key] = value
|
||||
end
|
||||
else
|
||||
config[to_restore] = default_config[to_restore]
|
||||
end
|
||||
end
|
||||
|
||||
local function metadata_to_config(key)
|
||||
return string.gsub(string.gsub(key, '^cty%-', ''), '%-', '_')
|
||||
end
|
||||
|
||||
local function inlines_to_string(inlines)
|
||||
result = ''
|
||||
for i = 1, #inlines do
|
||||
if inlines[i].t == 'Str' then
|
||||
result = result .. inlines[i].text
|
||||
elseif inlines[i].t == 'Space' then
|
||||
result = result .. ' '
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function metalist_to_configlist(metalist)
|
||||
-- metalist is a MetaList of MetaInlines containing Str and Space
|
||||
-- objects.
|
||||
-- Returns an array containing the values of the corresponding
|
||||
-- strings.
|
||||
configlist = {}
|
||||
for i = 1, #metalist do
|
||||
configlist[i] = inlines_to_string(metalist[i])
|
||||
end
|
||||
return configlist
|
||||
end
|
||||
|
||||
local function get_metamap_entry_value(entry)
|
||||
if entry[1] then
|
||||
return entry[1].text
|
||||
else
|
||||
return ''
|
||||
end
|
||||
end
|
||||
|
||||
local function process_metadata_to_config(key, value)
|
||||
local config_key = metadata_to_config(key)
|
||||
if key == 'cty-ordinal-suffixes' then
|
||||
config.ordinal_suffixes = {}
|
||||
for i = 1, #value do
|
||||
table.insert(config.ordinal_suffixes, value[i][1].text)
|
||||
end
|
||||
elseif key == 'cty-other-eras' then
|
||||
for i = 1, #value do
|
||||
local this_era = value[i]
|
||||
eras:new(
|
||||
get_metamap_entry_value(this_era['name']),
|
||||
get_metamap_entry_value(this_era['before-zero']),
|
||||
get_metamap_entry_value(this_era['after-zero']),
|
||||
get_metamap_entry_value(this_era['unspecified'])
|
||||
)
|
||||
end
|
||||
elseif key == 'cty-number-part-for-ordinal-suffix'
|
||||
or key == 'cty-numeral-type' then
|
||||
config[config_key] = metadata_to_config(value[1].text)
|
||||
elseif key == 'cty-parts-order' then
|
||||
config[config_key] = metalist_to_configlist(value)
|
||||
else
|
||||
config[config_key] = value[1].text
|
||||
end
|
||||
end
|
||||
|
||||
local function set_config(meta)
|
||||
restore_default_config()
|
||||
for key, value in pairs(meta) do
|
||||
if string.match(key, '^cty%-') then
|
||||
process_metadata_to_config(key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_abs_number(number)
|
||||
return math.ceil(math.abs(tonumber(number)))
|
||||
end
|
||||
|
||||
local function number_to_numeral(unformatted_century)
|
||||
if not tonumber(unformatted_century) or unformatted_century == '0' then
|
||||
error(ERROR_CONTENT_SPAN, 2)
|
||||
end
|
||||
local abs_century_number = get_abs_number(unformatted_century)
|
||||
local century_numeral
|
||||
if config.numeral_type == 'roman' then
|
||||
century_numeral = pandoc.utils.to_roman_numeral(abs_century_number)
|
||||
elseif config.numeral_type == 'arabic' then
|
||||
century_numeral = abs_century_number
|
||||
elseif config.numeral_type == 'spelled_out' then
|
||||
century_numeral = config.spelled_out_ordinals[abs_century_number]
|
||||
else
|
||||
error('"numeral_type" must be set either to "arabic", "roman" ' ..
|
||||
'or "spelled_out". Here set to "' .. config.numeral_type .. '".')
|
||||
end
|
||||
return century_numeral
|
||||
end
|
||||
|
||||
local function format_numeral(numeral)
|
||||
local formatted_numeral
|
||||
if config.numeral_style == 'lowercase' then
|
||||
formatted_numeral = pandoc.Str(string.lower(numeral))
|
||||
elseif config.numeral_style == 'uppercase' then
|
||||
formatted_numeral = pandoc.Str(string.upper(numeral))
|
||||
elseif config.numeral_style == 'smallcaps' then
|
||||
formatted_numeral = pandoc.SmallCaps(pandoc.Str(string.lower(numeral)))
|
||||
else
|
||||
error('"numeral_style" must be set either to "lowercase", "uppercase" ' ..
|
||||
'or "smallcaps". Here set to "' .. config.numeral_style .. '".')
|
||||
end
|
||||
return formatted_numeral
|
||||
end
|
||||
|
||||
local function format_century_number(unformatted_century)
|
||||
century_numeral = number_to_numeral(unformatted_century)
|
||||
formatted_century_number = format_numeral(century_numeral)
|
||||
return formatted_century_number
|
||||
end
|
||||
|
||||
local function get_last_digit(number)
|
||||
return tonumber(string.sub(tostring(number), -1))
|
||||
end
|
||||
|
||||
local function get_ordinal_suffix_for_number(raw_number)
|
||||
if config.numeral_type == 'spelled_out' then
|
||||
return ''
|
||||
else
|
||||
local abs_number = get_abs_number(raw_number)
|
||||
return config.ordinal_suffixes[abs_number]
|
||||
or config.ordinal_suffixes[#config.ordinal_suffixes]
|
||||
end
|
||||
end
|
||||
|
||||
local function get_raw_ordinal_suffix(unformatted_century)
|
||||
-- unformatted_century is an integer
|
||||
local raw_ordinal_suffix
|
||||
if config.number_part_for_ordinal_suffix == 'whole' then
|
||||
raw_ordinal_suffix = get_ordinal_suffix_for_number(unformatted_century)
|
||||
elseif config.number_part_for_ordinal_suffix == 'last_digit' then
|
||||
local the_last_digit = get_last_digit(unformatted_century)
|
||||
raw_ordinal_suffix = get_ordinal_suffix_for_number(the_last_digit)
|
||||
else
|
||||
error('"number_part_for_ordinal_suffix" must be set either to "whole" ' ..
|
||||
'or to "last_digit". Here set to "' ..
|
||||
config.number_part_for_ordinal_suffix .. '".')
|
||||
end
|
||||
return raw_ordinal_suffix
|
||||
end
|
||||
|
||||
local function format_ordinal_suffix(raw_ordinal_suffix)
|
||||
local formatted_ordinal_suffix
|
||||
if config.ordinal_suffix_pos == 'normal' then
|
||||
formatted_ordinal_suffix = pandoc.Str(raw_ordinal_suffix)
|
||||
elseif config.ordinal_suffix_pos == 'exponent' then
|
||||
formatted_ordinal_suffix = pandoc.Superscript(pandoc.Str(raw_ordinal_suffix))
|
||||
else
|
||||
error('"ordinal_suffix_pos" must be set either to "normal" ' ..
|
||||
'or to "exponent". Here set to "' .. config.ordinal_suffix_pos .. '".')
|
||||
end
|
||||
return formatted_ordinal_suffix
|
||||
end
|
||||
|
||||
local function create_ordinal_suffix(unformatted_century)
|
||||
-- unformatted_century is an integer
|
||||
local raw_ordinal_suffix = get_raw_ordinal_suffix(unformatted_century)
|
||||
local formatted_ordinal_suffix = format_ordinal_suffix(raw_ordinal_suffix)
|
||||
return formatted_ordinal_suffix
|
||||
end
|
||||
|
||||
local function format_unit(unit_form)
|
||||
local unit
|
||||
if unit_form == 'sg' then
|
||||
unit = config.unit_sg
|
||||
elseif unit_form == 'pl' then
|
||||
unit = config.unit_pl
|
||||
elseif unit_form == 'none' then
|
||||
unit = ''
|
||||
else
|
||||
error('Attribute "ctyunit" and "config.default_unit_form" must be set ' ..
|
||||
'to either "sg", "pl" or "none". Here set to "' .. unit_form .. '".')
|
||||
end
|
||||
return pandoc.Str(unit)
|
||||
end
|
||||
|
||||
local function get_era_indication(unformatted_century, era)
|
||||
local first_char = string.sub(unformatted_century, 1, 1)
|
||||
local era_indication
|
||||
if eras[era] == nil then
|
||||
error('Use of undefined era "' .. era .. '".')
|
||||
end
|
||||
if first_char == '+' then
|
||||
era_indication = eras[era].after
|
||||
elseif first_char == '-' then
|
||||
era_indication = eras[era].before
|
||||
else
|
||||
era_indication = eras[era].unspecified
|
||||
end
|
||||
return pandoc.Str(era_indication)
|
||||
end
|
||||
|
||||
local function reorder_parts(parts_array)
|
||||
local part_name_to_value = {
|
||||
number = parts_array[1],
|
||||
suffix = parts_array[2],
|
||||
unit = parts_array[3],
|
||||
era = parts_array[4]
|
||||
}
|
||||
local reordered_array = {}
|
||||
for i = 1, #config.parts_order do
|
||||
reordered_array[i] = part_name_to_value[config.parts_order[i]]
|
||||
end
|
||||
return reordered_array
|
||||
end
|
||||
|
||||
local function format_century(unformatted_century, unit_form, era)
|
||||
-- unformatted_century is an integer possibly preceded by '+' or '-'
|
||||
local century_number = format_century_number(unformatted_century)
|
||||
local ordinal_suffix = create_ordinal_suffix(unformatted_century)
|
||||
local unit = format_unit(unit_form)
|
||||
local era_indication = get_era_indication(unformatted_century, era)
|
||||
local formatted_century = reorder_parts(
|
||||
{ century_number, ordinal_suffix, unit, era_indication })
|
||||
return formatted_century
|
||||
end
|
||||
|
||||
function Span(span)
|
||||
if span.classes:includes('cty', 1) then
|
||||
if #span.content == 1 and span.content[1].t == 'Str' then
|
||||
local unformatted_century = span.content[1].text
|
||||
local unit_form = span.attributes.ctyunit or config.default_unit_form
|
||||
local era = span.attributes.ctyera or config.default_era
|
||||
span.content = format_century(unformatted_century, unit_form, era)
|
||||
return span
|
||||
else
|
||||
error(ERROR_CONTENT_SPAN)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
{ Meta = set_config },
|
||||
{ Span = Span }
|
||||
}
|
||||
Reference in New Issue
Block a user