Module:Quotations

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/Wiktionary:Tracking/Quotations/error require('Module:debug').track('Quotations/error') return ' '..results..' ' 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(': ' .. 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, "") 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}				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 = "" .. m_scriptutils.tag_translit(transliteration, lang, "usex") .. "" end local transcription = args.ts and "/" .. m_scriptutils.tag_transcription(args.ts, lang, "usex") .. "/" if transliteration or transcription then local translitend = "" if ante.trans and ante.trans ~= "-" and not ante.transyear and not ante.transauthor then translitwithtrans = true translitend = "" end table.insert(penult.otherLines, "" .. (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 = "(literally, “"..ante.lit.."”)" end if ante.transyear or ante.transauthor then table.insert(penult.otherLines, "") 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, "" .. ante.trans .. "" .. litline .. "</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..' '..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..' ' elseif form == 'work' then ultimate = '<span'..penult.elAttr..penult.style1..penult.work..penult.termlang..penult.style2..penult.s5..penult.object..penult.s3..penult.ref..' ' elseif form == 'ref' then ultimate = '<span'..penult.elAttr..penult.ref..' ' 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 = ..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 = ..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.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