Modul:Quotations
Robâ
Dokumentasi untuk modul ini dapat dibuat di Modul:Quotations/doc
-- Prevent substitution.
if mw.isSubsting() then
return require("Module:unsubst")
end
local export = {}
local debug_track_module = "Module:debug/track"
local foreign_numerals_module = "Module:foreign numerals"
local languages_module = "Module:languages"
local links_module = "Module:links"
local load_module = "Module:load"
local quotations_date_validation_module = "Module:Quotations/date validation"
local script_utilities_module = "Module:script utilities"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local add_warning = mw.addWarning
local byte = string.byte
local concat = table.concat
local find = string.find
local floor = math.floor
local format = string.format
local insert = table.insert
local match = string.match
local nowiki = mw.text.nowiki
local rep = string.rep
local require = require
local sub = string.sub
local tonumber = tonumber
local tostring = tostring
local umatch = mw.ustring.match
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function date_validation(...)
date_validation = require(quotations_date_validation_module).main
return date_validation(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function is_Latin_script(...)
is_Latin_script = require(script_utilities_module).is_Latin_script
return is_Latin_script(...)
end
local function keys_to_list(...)
keys_to_list = require(table_module).keysToList
return keys_to_list(...)
end
local function lang_err(...)
lang_err = require(languages_module).err
return lang_err(...)
end
local function language_link(...)
language_link = require(links_module).language_link
return language_link(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
local function remove_links(...)
remove_links = require(links_module).remove_links
return remove_links(...)
end
local function safe_require(...)
safe_require = require(load_module).safe_require
return safe_require(...)
end
local function split(...)
split = require(string_utilities_module).split
return split(...)
end
local function tag_text(...)
tag_text = require(script_utilities_module).tag_text
return tag_text(...)
end
local function tag_transcription(...)
tag_transcription = require(script_utilities_module).tag_transcription
return tag_transcription(...)
end
local function tag_translit(...)
tag_translit = require(script_utilities_module).tag_translit
return tag_translit(...)
end
local function to_Devanagari(...)
to_Devanagari = require(foreign_numerals_module).to_Devanagari
return to_Devanagari(...)
end
local function track(...)
track = require(debug_track_module)
return track(...)
end
local function ugsub(...)
ugsub = require(string_utilities_module).gsub
return ugsub(...)
end
local function ulower(...)
ulower = require(string_utilities_module).lower
return ulower(...)
end
local function uupper(...)
uupper = require(string_utilities_module).upper
return uupper(...)
end
local LanguageModule = {}
LanguageModule.__index = LanguageModule
LanguageModule.period = "."
local hasData = {
['ae'] = true,
['ar'] = true,
['axm'] = true,
['az'] = true,
['bra'] = true,
['chg'] = true,
['cy'] = true,
['egy'] = true,
['en'] = true,
['fa'] = true,
['fro'] = true,
['gmq-ogt'] = true,
['gmq-pro'] = true,
['grc'] = true,
['he'] = true,
['hi'] = true,
['hy'] = true,
['inc-apa'] = true,
['inc-ash'] = true,
['inc-mgu'] = true,
['inc-oaw'] = true,
['inc-ogu'] = true,
['inc-ohi'] = true,
['inc-opa'] = true,
['pra'] = true,
['la'] = true,
['lzz'] = true,
['mt'] = true,
['mxi'] = true,
['oge'] = true,
['okm'] = true,
['omr'] = true,
['ota'] = true,
['peo'] = true,
['pmh'] = true,
['sa'] = true,
['scn'] = true,
['sd'] = true,
['sv'] = true,
['vah'] = true,
['xce'] = true,
['xcl'] = true,
}
export.hasData = hasData
function export.create(frame)
return export.Create(frame:getParent().args)
end
local function warn_about_unrecognized_args(unrecognized_args)
track("Quotations/param error")
add_warning('Unrecognized parameters in ' .. nowiki('{{Q}}: ' .. concat(keys_to_list(unrecognized_args), ', ')))
end
function export.Create(args)
-- Set up our initial variables; set empty parameters to false
local processed_args = {}
local unrecognized_args = {}
local params = {
['thru'] = true,
['quote'] = true,
['text'] = true,
['passage'] = true,
['trans'] = true,
['transauthor'] = true,
['transyear'] = true,
['t'] = true,
['lit'] = true,
['style'] = true,
['object'] = true,
['notes'] = true,
['refn'] = true,
['form'] = true,
['year'] = true,
['termlang'] = true,
['tr'] = true, -- This is simply ignored if quote is in Latin script.
['ts'] = true, -- This is simply ignored if quote is in Latin script.
['subst'] = true, -- This is simply ignored if quote is in Latin script.
['nocat'] = true,
}
local max_numbered_param = 4
for k, v in pairs(args) do
if type(k) == 'number' then
if k > max_numbered_param then
max_numbered_param = k
end
elseif not params[k] then
unrecognized_args[k] = v
end
if v == '' then
if k == "lang" then
processed_args[k] = nil
else
processed_args[k] = false
end
else
processed_args[k] = v
end
end
if next(unrecognized_args) then
warn_about_unrecognized_args(unrecognized_args)
end
-- Ensure that all numbered parameters up to the greatest numbered parameter
-- are not nil.
for i = 1, max_numbered_param do
processed_args[i] = processed_args[i] or false
end
-- Apply aliases.
-- This should be cleaned up when more robust parameter parsing is implemented.
processed_args['quote'] = processed_args['quote'] or processed_args['text'] or processed_args['passage']
args = processed_args -- Overwrite original args.
local lang = args[1]
lang = get_lang(lang) or lang_err(lang, 1)
local ante = {}
if hasData[lang:getCode()] then
local m_langModule = LanguageModule.new(lang)
ante = m_langModule:expand(args)
else
track("Quotations/no data")
track("Quotations/no data/" .. lang:getCode())
end
if ante.author == nil then
ante.author = args[2]
end
if ante.work == nil then
ante.work = args[3]
end
if ante.ref == nil then
local ref = {}
for i = 4, 10 do
if args[i] then
insert(ref, args[i])
else
break
end
end
ante.ref = concat(ref, '.')
end
for k in pairs(args) do
if type(k) ~= "number" then
ante[k] = args[k]
end
end
local penult = {['year'] = '', ['author'] = '', ['work'] = '', ['object'] = '', ['ref'] = '', ['termlang'] = '',
['notes'] = '', ['refn'] = '', ['otherLines'] = {}, ['s1'] = '', ['s2'] = '',
['s3'] = '', ['s4'] = '', ['s5'] = ''}
local comma = false
--Language specific modules are responsible for first line parameters.
--Base formatting module will poll for other parameters,
--pulling them only if the language module hasn't returned them.
local otherOtherLineStuff = {'quote', 'transyear', 'transauthor', 'trans', 'termlang'}
for _, item in ipairs(otherOtherLineStuff) do
ante[item] = ante[item] or args[item]
end
if not ante.code then
penult.elAttr = ' class="wiktQuote" data-validation="white">'
else
penult.elAttr = ' class="wiktQuote" data-validation="' .. ante.code .. '">'
end
if ante.year then
penult.year = "'''" .. date_validation(ante.year) .. "'''"
comma = true
end
if ante.author then
penult.s1 = (comma and ', ' or '')
penult.author = ante.author
comma = true
end
if ante.work then
penult.s2 = (comma and ', ' or '')
penult.work = ante.work
comma = true
end
if ante.object then
penult.s5 = (comma and ' ' or '')
penult.object = '(' .. ante.object .. ')'
comma = true
end
if ante.ref then
penult.s3 = (comma and ' ' or '')
penult.ref = ante.ref
end
if ante.style == 'no' or penult.work == '' then
-- Leave as-is
elseif ante.style == 'q' then
penult.work = '“' .. penult.work .. '”'
else
penult.work = "''" .. penult.work .. "''"
end
if ante.termlang then
ante.termlang = get_lang(ante.termlang) or lang_err(ante.termlang, 1)
penult.termlang = ' (in ' .. ante.termlang:getCanonicalName() .. ')'
end
local form = args['form'] or 'full'
local ultimate
if form == 'full' then
local categories = {}
if ante.notes then
penult.s4 = (comma and ', ' or '')
penult.notes = '(' .. ante.notes .. ')'
end
if ante.refn then
penult.refn = ante.refn
end
if ante.t then
ante.trans = ante.t
end
if ante.quote or (ante.trans and ante.trans ~= "-") then
penult.refn = ":" .. penult.refn
local translitwithtrans = false
insert(penult.otherLines, "<dl><dd>")
if ante.quote then
local sc = lang:findBestScript(ante.quote)
local quote = ante.quote
-- fix up links with accents/macrons/etc.
if find(quote, "[[", 1, true) then
quote = language_link{term = quote, lang = lang}
end
insert(penult.otherLines, tag_text(quote, lang, sc, nil, "e-quotation"))
if args.nocat then
track("Quotations/nocat")
elseif ante.termlang then
insert(categories, ante.termlang:getCanonicalName() .. " terms with quotations")
else
insert(categories, lang:getCanonicalName() .. " terms with quotations")
end
if not is_Latin_script(sc) or lang:getCode() == "egy" then
-- Handle subst=
local subbed_quote = remove_links(quote)
if args.subst then
local substs = split(args.subst, ",")
for _, subpair in ipairs(substs) do
local subsplit = split(subpair, find(subpair, "//", 1, true) and "//" or "/")
subbed_quote = ugsub(subbed_quote, subsplit[1], subsplit[2])
end
end
local transliteration = args.tr or (lang:transliterate(subbed_quote, sc))
if transliteration then
transliteration = "<dd>" .. tag_translit(transliteration, lang, "usex") .. "</dd>"
end
local transcription = args.ts and "<dd>/" .. tag_transcription(args.ts, lang, "usex") .. "/</dd>"
if transliteration or transcription then
local translitend = "</dl>"
if ante.trans and ante.trans ~= "-" and not ante.transyear and not ante.transauthor then
translitwithtrans = true
translitend = ""
end
insert(penult.otherLines, "<dl>" .. (transliteration or "") .. (transcription or "") .. translitend)
end
end
end
if ante.trans == "-" then
ante.trans = nil
insert(categories, "Omitted translation in the main namespace")
elseif ante.trans then
local litline = ""
if ante.lit then
litline = "<dd>(literally, “" .. ante.lit .. "”)</dd>"
end
if ante.transyear or ante.transauthor then
insert(penult.otherLines, "<ul><li>")
if ante.transyear then
insert(penult.otherLines, "'''" .. ante.transyear .. "''' translation")
else
insert(penult.otherLines, "Translation")
end
if ante.transauthor then
insert(penult.otherLines, " by " .. ante.transauthor)
end
insert(penult.otherLines, "<dl><dd>" .. ante.trans .. "</dd>" .. litline .. "</dl></li></ul>")
else
if not ante.quote then
insert(penult.otherLines, ante.trans)
else
local transstart = "<dl><dd>"
if translitwithtrans then
transstart = "<dd>"
end
insert(penult.otherLines, transstart .. ante.trans .. "</dd>" .. litline .. "</dl>")
end
end
elseif lang:getCode() ~= "en" and lang:getCode() ~= "und" then
insert(penult.otherLines, "<dl><dd><small>(please [[Wiktionary:Quotations#Adding_translations_to_quotations|add an English translation]] of this quotation)</small>")
-- add trreq category if translation is unspecified and language is not English or undetermined
insert(categories, "Requests for translations of " .. lang:getCanonicalName() .. " quotations")
end
insert(penult.otherLines, "</dd></dl>")
end
penult.otherLines = concat(penult.otherLines)
ultimate = '<div' .. penult.elAttr .. penult.year .. penult.s1 .. penult.author .. penult.s2 .. penult.work .. penult.termlang .. penult.s5 .. penult.object .. penult.s3 .. penult.ref .. penult.s4 .. penult.notes .. penult.refn .. penult.otherLines .. '</div>' .. format_categories(categories, lang)
elseif form == 'inline' then
ultimate = '<span' .. penult.elAttr .. penult.author .. penult.s2 .. penult.work .. penult.termlang .. penult.s5 .. penult.object .. penult.s3 .. penult.ref .. '</span>'
elseif form == 'work' then
ultimate = '<span' .. penult.elAttr .. penult.work .. penult.termlang .. penult.s5 .. penult.object .. penult.s3 .. penult.ref .. '</span>'
elseif form == 'ref' then
ultimate = '<span' .. penult.elAttr .. penult.ref .. '</span>'
end
return ultimate
end
function LanguageModule.new(lang)
local sema = safe_require("Module:Quotations/" .. lang:getCode()) or {}
sema.library = load_data("Module:Quotations/" .. lang:getCode() .. "/data")
return setmetatable(sema, LanguageModule)
end
function LanguageModule:changeCode(color)
if color == 'orange' then
self.code = 'orange'
end
if (color == 'yellow') and (self.code == 'green') then
self.code = 'yellow'
end
end
function LanguageModule:reroute(route)
local temp = {}
local data = self.library.data
for k, v in pairs(route) do
temp[k] = self:interpret(v)
end
for k, v in pairs(temp) do
self[k] = v
end
if self.author ~= nil and data[self.author] then
self.aData = data[self.author]
if self.work ~= nil and self.aData.works[self.work] then
self.wData = self.aData.works[self.work]
end
end
end
function LanguageModule:lower(input)
return input ~= nil and ulower(input) or nil
end
function LanguageModule:upper(input)
return input ~= nil and uupper(input) or nil
end
function LanguageModule:isLetter(input)
return not tonumber(input)
end
function LanguageModule:digits(width, num)
return format('%' .. width .. 'd', num)
end
function LanguageModule:numToDeva(input)
return input ~= nil and to_Devanagari(input) or nil
end
function LanguageModule:separ(values, separator)
return concat(values, separator)
end
function LanguageModule:roundDown(period, verse)
if tonumber(verse) then
return floor(verse / period) * period
end
self:changeCode('orange')
end
function LanguageModule:chapterSelect(rubric, verse)
verse = tonumber(match(verse, "^%d+"))
for k, v in pairs(rubric) do
if v[1] <= verse and verse <= v[2] then
return k
end
end
self:changeCode('orange')
end
function LanguageModule:interpret(item)
local output
if type(item) == 'string' then
if #item > 1 and byte(item) == 0x2E then
local address = sub(item, 2)
local returnable = self[address] or self.library.data.Sundry and self.library.data.Sundry[address]
output = returnable
else
output = item
end
elseif type(item) == 'table' then
--If it's a table, it's either a function call or a nested address.
local presumedFunction = self:interpret(item[1])
if type(presumedFunction) == 'function' then
local parameters = {}
for i = 2, 30 do
if item[i] ~= nil then
insert(parameters, self:interpret(item[i]))
else
break
end
end
output = presumedFunction(self, unpack(parameters))
else
local nested = self
for i = 1, 30 do
local address = item[i]
if address and nested then
nested = nested[address]
else
break
end
end
output = nested
end
else
output = item
end
return output
end
function LanguageModule:convert(scheme, initiate)
if type(scheme) == "table" then
local initiate = tonumber(initiate) or initiate
local converted = scheme[initiate]
if converted == nil then
self:changeCode('orange')
end
return converted
end
if type(scheme) == "function" then
local initiate = tonumber(initiate) or initiate
local converted = scheme(self, initiate)
if converted == nil then
self:changeCode('orange')
end
return converted
end
self:changeCode('orange')
end
function LanguageModule:numToRoman(item)
local j = tonumber(item)
if (j == nil) then
return item
end
if (j <= 0) then
return item
end
local ints = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
local nums = {'M', 'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I'}
local result = ""
for k = 1, #ints do
local count = floor(j / ints[k])
result = result .. rep(nums[k], count)
j = j - ints[k] * count
end
return result
end
-- Iterate through "array" and its sublevels. Find indices in "array" that
-- contain a string matching "valToFind". Return last index where that string
-- (minus its first letter) is the key for a field in "self", as well as last
-- index where that string was found.
-- Used to locate the place where the "rlformat" should be skipped out of,
-- because there's no ".ref" value supplied for what comes next.
-- For instance, if book but not line number has been supplied.
local function findLastValidRefIndex(self, array, valToFind)
local lastValidIndex, lastIndex
for i, val in ipairs(array) do
if type(val) == 'table' then
local res1, res2 = findLastValidRefIndex(self, val, valToFind)
if res1 then
lastValidIndex = i
end
if res2 then
lastIndex = i
end
elseif type(val) == "string" and match(val, valToFind) then
lastIndex = i
if self[sub(val, 2)] then
lastValidIndex = i
end
end
end
return lastValidIndex, lastIndex
end
function LanguageModule:expand(args)
--Instantiate our variables.
local results = {}
self.code = 'green'
local data = self.library.data
self.author = args['author'] or args[2]
self.work = args['work'] or args[3]
for i = 1, 5 do
local refName = 'ref' .. i
local paramNumber = i + 3
self[refName] = args[refName] or args[paramNumber]
end
--Check if we've been given an author alias.
if data.authorAliases[self.author] then
self.author = data.authorAliases[self.author]
end
if not data[self.author] then
self:changeCode('yellow')
else
self.aData = data[self.author]
if self.aData.reroute then
self:reroute(self.aData.reroute)
else
if self.aData.aliases and self.aData.aliases[self.work] then
self.work = self.aData.aliases[self.work]
end
if not (self.aData.works and self.aData.works[self.work]) then
self:changeCode('yellow')
else
self.wData = self.aData.works[self.work]
if self.wData.reroute then
self:reroute(self.wData.reroute)
end
end
end
end
--Load all author-level data.
if self.aData and self.aData.aLink then
results.author = '[[w:' .. self.aData.aLink .. '|' .. self.author .. ']]'
else
results.author = self.author
end
if self.aData and self.aData.year then
results.year = self.aData.year
end
--If the database has a link for the work, incorporate it.
if not self.wData or not self.wData['wLink'] then
results.work = self.work
else
results.work = '[[w:' .. self.wData['wLink'] .. '|' .. self.work .. ']]'
end
--Some works have info which overrides the author-level info or fills other parameters.
if self.wData then
if self.wData['year'] then
results.year = self.wData.year
end
if self.wData['author'] ~= nil then
results.author = self.wData.author
end
if self.wData['object'] then
results.object = self.wData.object
end
if self.wData['style'] then
results.style = self.wData.style
end
end
self.thru = args['through'] or args['thru']
--Custom formatter for displayed reference
if self.aData and self.aData.rdFormat and self.aData.rdFormat.custom then
local formatted = {}
for _, current in ipairs(self.aData.rdFormat.custom) do
insert(formatted, self:interpret(current))
end
self.refDisplay = concat(formatted)
else
--The displayed reference usually consists of all the ref argument(s) joined with a period.
self.refDisplay = self.ref1 and '' or (self.wData and self.wData['refDefaultDisplay'] or false)
local separator_num = 1
for i = 1, 5 do
local whichRef = 'ref' .. tostring(i)
if self[whichRef] then
local ref = self[whichRef]
local separator
-- no separator before a letter
if umatch(ref, "^%a$") then
separator = ""
-- to allow colon between biblical chapter and verse
elseif self.aData and self.aData.rdFormat and self.aData.rdFormat.separator then
separator = self.aData.rdFormat.separator
elseif self.aData and self.aData.rdFormat and self.aData.rdFormat.separators then
separator = self.aData.rdFormat.separators[separator_num]
else
separator = "."
end
if i > 1 then
self.refDisplay = self.refDisplay .. separator
separator_num = separator_num + 1
end
self.refDisplay = self.refDisplay .. self[whichRef]
else
break
end
end
if self.thru then
self.refDisplay = self.refDisplay .. '–' .. self.thru
end
end
-- Apply custom work formatting, if specified
if self.wData and self.wData['workFormat'] then
self.workFormat = self.aData['workFormat' .. tostring(self.wData.workFormat)]
elseif self.aData and self.aData['workFormat'] then
self.workFormat = self.aData['workFormat']
end
if self.workFormat then
results.style = 'no' -- Ignore/override style parameter
local formatted = {}
for _, current in ipairs(self.workFormat) do
insert(formatted, self:interpret(current))
end
results.work = concat(formatted)
end
--[[ If the work is not in the database,
or we don't have a source text link,
the ref is simply the display.
Otherwise, we have to create a reference link,
easily the most challenging function of this script. ]]
if self.wData and self.wData['rlFormat'] then
self.rlFormat = self.aData['rlFormat' .. tostring(self.wData.rlFormat)]
elseif self.aData and self.aData['rlFormat'] then
self.rlFormat = self.aData['rlFormat']
end
if self.wData and self.rlFormat then
self.rlTitle = self.wData['rlTitle']
-- Go through indices in "self.rlFormat" that contain a string
-- beginning in ".ref" (either in the first level of "self.rlFormat"
-- or a sublevel). Return the index of the string that has a
-- corresponding field in "self", as well as the index of the last
-- such string.
local lastValidIndex, lastIndex = findLastValidRefIndex(self, self.rlFormat, '^%.ref(%d+)$')
-- If there isn't another ".ref" string after the last valid index,
-- then there is no need to cut short the rlFormat.
self.refLink = {}
if lastIndex and lastValidIndex and lastIndex > lastValidIndex then
for i, current in ipairs(self.rlFormat) do
if i > lastValidIndex then
break
end
insert(self.refLink, self:interpret(current))
end
else
for _, current in ipairs(self.rlFormat) do
insert(self.refLink, self:interpret(current))
end
end
self.refLink = concat(self.refLink)
end
if self.wData and self.wData['xrlFormat'] then
self.xrlFormat = self.aData['xrlFormat' .. tostring(self.wData.xrlFormat)]
elseif self.aData and self.aData['xrlFormat'] then
self.xrlFormat = self.aData['xrlFormat']
end
if self.xrlFormat then
self.xurl = self.wData['xurl']
-- Go through indices in "self.xrlFormat" that contain a string
-- beginning in ".ref" (either in the first level of "self.xrlFormat"
-- or a sublevel). Return the index of the string that has a
-- corresponding field in "self", as well as the index of the last
-- such string.
local lastValidIndex, lastIndex = findLastValidRefIndex(self, self.xrlFormat, '^%.ref(%d+)$')
-- If there isn't another ".ref" string after the last valid index,
-- then there is no need to cut short the rlFormat.
self.xrefLink = {}
if lastIndex and lastValidIndex and lastIndex > lastValidIndex then
for i, current in ipairs(self.xrlFormat) do
if i > lastValidIndex then
break
end
insert(self.xrefLink, self:interpret(current))
end
else
for _, current in ipairs(self.xrlFormat) do
insert(self.xrefLink, self:interpret(current))
end
end
self.xrefLink = concat(self.xrefLink)
end
if self.refLink and self.refDisplay then
results.ref = '[[' .. self.refLink .. '|' .. self.refDisplay .. ']]'
elseif self.xrefLink and self.refDisplay then
results.ref = '[' .. self.xrefLink .. ' ' .. self.refDisplay .. ']'
else
results.ref = self.refDisplay or ''
end
if args['notes'] then
results.notes = args.notes
end
results.code = self.code
return results
end
return export