local m_scriptutils = require("Module:script utilities")
local date_validation = require("Module:Quotations/date validation")
local loadModule -- a forward declaration
local export = {}
local LanguageModule = {}
LanguageModule.__index = LanguageModule
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-ogu'] = true,
['inc-mgu'] = true,
['inc-ohi'] = true,
['inc-opa'] = true,
['inc-pra'] = true,
['la'] = true,
['lzz'] = true,
['mt'] = true,
['mxi'] = true,
['oge'] = true,
['omr'] = true,
['ota'] = true,
['peo'] = true,
['pmh'] = true,
['sa'] = true,
['scn'] = true,
['sv'] = true,
['vah'] = true,
['xcl'] = true,
}
export.hasData = hasData
function export.create(frame)
local passed, results = pcall(function () return export.Create(frame:getParent().args) end)
if passed then
return results
else
--[[Special:WhatLinksHere/Template:tracking/Quotations/error]]
require('Module:debug').track('Quotations/error')
return '<span class="wiktQuote previewonly error" data-validation="red">'..results..'</span>'
end
end
local function warn_about_unrecognized_args(unrecognized_args)
require('Module:debug').track('Quotations/param error')
mw.addWarning('Unrecognized parameters in '
.. mw.text.nowiki('{{Q}}: '
.. table.concat(require('Module:table').keysToList(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,
['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
args = processed_args -- Overwrite original args.
local lang = args[1]
lang = require("Module:languages").getByCode(lang) or require("Module:languages").err(lang, 1)
local ante = {}
if hasData[lang:getCode()] then
local m_langModule = LanguageModule.new(lang)
ante = m_langModule:expand(args)
else
require("Module:debug").track {
'Quotations/no data',
'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
table.insert(ref, args[i])
else
break
end
end
ante.ref = table.concat(ref, '.')
end
for k,v 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'] = '', ['style1'] = '', ['style2'] = ''}
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.main(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
penult.style1 = ''
penult.style2 = ''
elseif ante.style == 'q' then
penult.style1 = '“'
penult.style2 = '”'
else
penult.style1 = "''"
penult.style2 = "''"
end
if ante.termlang then
ante.termlang = require("Module:languages").getByCode(ante.termlang) or require("Module:languages").err(ante.termlang, 1)
penult.termlang = ' (in '..lang:getCanonicalName()..')'
end
local form = args['form'] or 'full'
local ultimate
if form == 'full' then
local categories = {}
local namespace = mw.title.getCurrentTitle().nsText
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
table.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 quote:find("[[", 1, true) then
quote = require("Module:links").language_link({term = quote, lang = lang}, false)
end
table.insert(penult.otherLines, m_scriptutils.tag_text(quote, lang, sc, nil, "e-quotation"))
if (namespace == "" or namespace == "Reconstruction") and not args.nocat then
if ante.termlang then
table.insert(categories, ante.termlang:getCanonicalName() .. " terms with quotations")
else
table.insert(categories, lang:getCanonicalName() .. " terms with quotations")
end
end
if not m_scriptutils.is_Latin_script(sc) or lang:getCode() == "egy" then
-- Handle subst=
local subbed_quote = require("Module:links").remove_links(quote)
if args.subst then
local substs = mw.text.split(args.subst, ",")
for _, subpair in ipairs(substs) do
local subsplit = mw.text.split(subpair, mw.ustring.find(subpair, "//") and "//" or "/")
subbed_quote = mw.ustring.gsub(subbed_quote, subsplit[1], subsplit[2])
end
end
local transliteration = args.tr or (lang:transliterate(subbed_quote, sc))
if transliteration then
transliteration = "<dd>" .. m_scriptutils.tag_translit(transliteration, lang, "usex") .. "</dd>"
end
local transcription = args.ts and "<dd>/" .. m_scriptutils.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
table.insert(penult.otherLines, "<dl>" .. (transliteration or "") .. (transcription or "") .. translitend)
end
end
end
if ante.trans == "-" then
ante.trans = nil
table.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
table.insert(penult.otherLines, "<ul><li>")
if ante.transyear then
table.insert(penult.otherLines, "'''" .. ante.transyear .. "''' translation")
else
table.insert(penult.otherLines, "Translation")
end
if ante.transauthor then
table.insert(penult.otherLines, " by " .. ante.transauthor)
end
table.insert(penult.otherLines, "<dl><dd>" .. ante.trans .. "</dd>" .. litline .. "</dl></li></ul>")
else
if not ante.quote then
table.insert(penult.otherLines, ante.trans)
else
local transstart = "<dl><dd>"
if translitwithtrans then
transstart = "<dd>"
end
table.insert(penult.otherLines, transstart .. ante.trans .. "</dd>" .. litline .. "</dl>")
end
end
elseif lang:getCode() ~= "en" and lang:getCode() ~= "und" then
-- add trreq category if translation is unspecified and language is not English or undetermined
table.insert(categories, "Requests for translations of " .. lang:getCanonicalName() .. " quotations")
end
table.insert(penult.otherLines, "</dd></dl>")
end
penult.otherLines = table.concat(penult.otherLines)
ultimate = '<div'..penult.elAttr..penult.year..penult.s1..penult.author..penult.s2..penult.style1..penult.work..penult.termlang..penult.style2..penult.s5..penult.object..penult.s3..penult.ref..penult.s4..penult.notes..penult.refn..penult.otherLines..'</div>'..require("Module:utilities").format_categories(categories, lang)
elseif form == 'inline' then
ultimate = '<span'..penult.elAttr..penult.author..penult.s2..penult.style1..penult.work..penult.termlang..penult.style2..penult.s5..penult.object..penult.s3..penult.ref..'</span>'
elseif form == 'work' then
ultimate = '<span'..penult.elAttr..penult.style1..penult.work..penult.termlang..penult.style2..penult.s5..penult.object..penult.s3..penult.ref..'</span>'
elseif form == 'ref' then
ultimate = '<span'..penult.elAttr..penult.ref..'</span>'
end
return ultimate
end
local function add_self(func)
return function(self, ...)
return func(...)
end
end
function LanguageModule.new(lang)
local sema = require('Module:Quotations/' .. lang:getCode())
sema.library = mw.loadData("Module:Quotations/" .. lang:getCode() .. "/data")
setmetatable(sema, LanguageModule)
-- Have to insert unused self parameter or Lua–PHP interface complains about
-- table with boolean keys.
sema.lower = add_self(mw.ustring.lower)
sema.period = '.'
return sema
end
setmetatable(LanguageModule, { __index = function (self, key)
if key == 'numToIndian' then
-- This should only be loaded if needed so that [[Module:foreign numerals]]
-- is not transcluded on every page that [[Module:Quotations]] is.
local func = require('Module:foreign numerals').to_Indian
self.numToIndian = func
return func
end
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:choose(choice, optionA, optionB)
optionB = optionB or ''
choice = self:interpret(choice)
local chosenPath = {}
if choice then
chosenPath = optionA
else
chosenPath = optionB
end
for j=1, 30 do
local innerCurrent = chosenPath[j]
if innerCurrent then
table.insert(self.refLink, self:interpret(current))
else
break
end
end
local ongoingDecision
decision = self:interpret(decision)
return decision
end
function LanguageModule:isLetter(input)
local isit = not tonumber(input)
return isit
end
function LanguageModule:digits(width, num)
local decimal = '%' .. width .. 'd'
return string.format(decimal, num)
end
function LanguageModule:separ(values, separator)
return table.concat(values, separator)
end
function LanguageModule:roundDown(period, verse)
if not tonumber(verse) then
self:changeCode('orange')
else
local rounded = math.floor(verse/period) * period
return rounded
end
end
function LanguageModule:chapterSelect(rubric, verse)
verse = tonumber(verse)
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 string.len(item) > 1 and string.sub(item, 1, 1) == '.' then
local address = string.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
table.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(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 = math.floor(j / ints[k])
result = result .. string.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 val:find(valToFind) then
lastIndex = i
if self[val:sub(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
local ultimate = ''
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
--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 mw.ustring.match(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 args['through'] then
args['thru'] = args['through']
end
if args['thru'] then
self.refDisplay = self.refDisplay..'–'..args['thru']
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)]
if 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.
local indexToStopAt
if lastIndex and lastValidIndex and lastIndex > lastValidIndex then
indexToStopAt = lastValidIndex
else
indexToStopAt = math.huge
end
self.refLink = {}
for i, current in ipairs(self.rlFormat) do
if i > indexToStopAt then
break
end
table.insert(self.refLink, self:interpret(current))
end
self.refLink = table.concat(self.refLink)
end
end
if self.wData and self.wData['xrlFormat'] then
self.xrlFormat = self.aData['xrlFormat'..tostring(self.wData.xrlFormat)]
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.
local indexToStopAt
if lastIndex and lastValidIndex and lastIndex > lastValidIndex then
indexToStopAt = lastValidIndex
else
indexToStopAt = math.huge
end
self.xrefLink = {}
for i, current in ipairs(self.xrlFormat) do
if i > indexToStopAt then
break
end
table.insert(self.xrefLink, self:interpret(current))
end
self.xrefLink = table.concat(self.xrefLink)
end
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