Module:Quotations

Definition from Wiktionary, the free dictionary
Jump to: navigation, search

This module is used by Template:Q.

Coding[edit]

The intent of this module was to make a quotations library which can be built and used asynchronously. An editor can reference a work or author which is not yet coded, and the display will still look fine, and if the information is later coded, the template will make use of it at that time.

Language modules[edit]

Each language needs its own module, such as that found at Module:Quotations/grc, which can be used as a starting point in creating a language module which does not yet exist. This module takes the information and does the lookups in its data module (in this case Module:Quotations/grc/data), and returns the results to be formatted by the main module.

The data module should be a table, with entries for each author. Each author table should include information about the author, such as years active and the title of the Wikipedia article for the author, as well as tables for each of its works, with information such as the year the work was written, the title of its Wikipedia article, and the title of its Wiksource article. Alias tables can be used for convergence, such as when a work might have two common titles, as well as abbreviations.

Note that the module is designed to work with whatever data is available, and it should not be considered necessary to add all possible data; some is better than none.

Reference link[edit]

One of the more complex aspects of coding the data set lies in the reference link, which is meant to be a fairly dynamic link which formats itself to account for information given. It is formatted in the author's data table as a table of strings and tables. Strings not prefixed by a period are inserted as is. Strings prefixed with a period indicate variable references. Tables which begin with a function run that function with the table's following elements as parameters. Tables not beginning with a function are nested variable addresses. Take the reference link format table data.Plato.rlFormat2, which is used solely by Plato's Republic:

{'s:el:', '.rlTitle', '/', {'.chapterSelect', {'authorData', 'republicChapters'}, '.ref1'}, '#p', '.ref1', {'.lower', '.ref2'} }

The first element, 's:el', is a simple string, and will be inserted as-is into the link target; it is the prefix for the Greek Wikisource, where a native language version of The Republic is found.

The second element, '.rlTitle', begins with a period, indicating that it should be replaced by the variable 'rlTitle', which is given in The Republic’s data table as 'Πολιτεία'.

The third element, '/', is a standard string, and will be inserted into the link as-is.

The fourth element is a function call, using the function chapterSelect, which is called with two parameters. The second parameter is the variable ref1, which is the chapter given by the user. The first parameter is formatted as a nested reference, it will be the variable republicChapters, which is found within data.Plato.

There is no getting around the fact that it is not the easiest format in the world, but it does make for a powerful and flexible engine to interpret data and create the proper link.


local m_scriptutils = require("Module:script utilities")
local date_validation = require("Module:Quotations/date validation")
local loadModule = nil -- essentially a forward-declaration
local export = {}


local hasData = {
	['grc'] = true,
	['la'] = true,
	['xcl'] = true,
	['he'] = true,
	['hy'] = true,
	['axm'] = true,
	['en'] = true
}

function export.create(frame)
	local passed = true
	local results = ''
	passed, results = pcall(export.Create, frame)
	if passed then
		return results
	else
		return '<span class="wiktQuote previewonly" data-validation="red">'..results..'</span>'
	end
end

function export.Create(frame)
	-- Set up our initial variables; set empty parameters to false
	local args = {}
	for k, v in pairs(frame:getParent().args) do
		if v == '' then
			if k == "lang" then
				args[k] = nil
			else
				args[k] = false
			end
		else
			args[k] = v
		end
	end
	
	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 = loadModule(lang)
		ante = m_langModule.expand(args)
	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
		ante.ref = ''
		local dot = false
		for i = 4, 10 do
			if args[i] then
				ante.ref = ante.ref..(dot and '.' or '')..args[i]
				dot = true
			else
				break
			end
		end
	end
	
	for k,v in pairs(args) do
		if type(k) ~= 'number' then
			ante[k] = args[k]
		end
	end

	local penult = {['year'] = '', ['author'] = '', ['work'] = '', ['ref'] = '',
		['notes'] = '', ['otherLines'] = '', ['s1'] = '', ['s2'] = '',
		['s3'] = '', ['s4'] = ''}
	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'}
	for i = 1, 4 do
		ante[otherOtherLineStuff[i]] = ante[otherOtherLineStuff[i]] or args[otherOtherLineStuff[i]]
	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.ref then
		penult.s3 = (comma and ' ' or '')
		penult.ref = ante.ref
	end
	if ante.notes then
		penult.s4 = (comma and', ' or '')
		penult.notes = '('..ante.notes..')'
	end
	if ante.quote or ante.trans then
		penult.otherLines = '<dl><dd>'
		if ante.quote then
			local sc = require("Module:scripts").findBestScript(ante.quote, lang)
			penult.otherLines = penult.otherLines..m_scriptutils.tag_text(ante.quote, lang, sc)
		end
		if ante.trans then
			if ante.transyear or ante.transauthor then
				penult.otherLines = penult.otherLines..'<ul><li>'
				if ante.transyear then
					penult.otherLines = penult.otherLines.."'''"..ante.transyear.."''' translation"
				else
					penult.otherLines = penult.otherLines..'Translation'
				end
				if ante.transauthor then
					penult.otherLines = penult.otherLines..' by '..ante.transauthor
				end
				penult.otherLines = penult.otherLines..'<dl><dd>'..ante.trans..'</dd></dl></li></ul>'
			else
				if not ante.quote then
					penult.otherLines = penult.otherLines..ante.trans
				else
					penult.otherLines = penult.otherLines..'<dl><dd>'..ante.trans..'</dd></dl>'
				end
			end
		end
		penult.otherLines = penult.otherLines..'</dl></dd>'
	end
	local form = args['form'] or 'full'
	if form == 'full' then
		ultimate = '<div'..penult.elAttr..penult.year..penult.s1..penult.author..penult.s2..penult.work..penult.s3..penult.ref..penult.s4..penult.notes..penult.otherLines..'</div>'
	elseif form == 'inline' then
		ultimate = '<span'..penult.elAttr..penult.author..penult.s2..penult.work..penult.s3..penult.ref..'</span>'
	elseif form == 'work' then
		ultimate = '<span'..penult.elAttr..penult.work..penult.s3..penult.ref..'</span>'
	elseif form == 'ref' then
		ultimate = '<span'..penult.elAttr..penult.ref..'</span>'
	end
	return ultimate

end

function loadModule(lang)
	local sema = require('Module:Quotations/' .. lang:getCode())
	sema.library = sema.library or mw.loadData("Module:Quotations/" .. lang:getCode() .. "/data")
	
	if not sema.refCount then
		sema.refCount = 0
	end
	
	if not sema.refParamCount then
		sema.refParamCount = 0
	end
	
	sema.changeCode = sema.changeCode or function(color)
		if color == 'orange' then
			sema.code = 'orange'
		end
		if (color == 'yellow') and (sema.code == 'green') then
			sema.code = 'yellow'
		end
	end
	
	sema.reroute = sema.reroute or function(route)
		local temp = {}
		local data = sema.library.data
		
		if not sema.rerouteRefParamCount then
			sema.rerouteRefParamCount = 0
		end
		
		for k, v in pairs(route) do
			temp[k] = sema.interpret(v)
			if string.find(k, "^ref%d$") then
				number = tonumber(string.match(k, "%d$"))
				mw.log(number)
				
				if number > sema.rerouteRefParamCount then
					sema.rerouteRefParamCount = number
				end
			end
		end
		
		for k, v in pairs(temp) do
			sema[k] = v
		end
		
		if sema.author ~= nil and data[sema.author] then
			sema.aData = data[sema.author]
			if sema.work ~= nil and sema.aData.works[sema.work] then
				sema.wData = sema.aData.works[sema.work]
			end
		end
	end
	
	sema.choose = sema.choose or function(choice, optionA, optionB)
		optionB = optionB or ''
		choice = sema.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(sema.refLink, sema.interpret(current))
			else
				break
			end
		end
		local ongoingDecision
		decision = sema.interpret(decision)
		return decision
	end
	
	sema.isLetter = sema.isLetter or function(input)
		local isit = not tonumber(input)
		return isit
	end

	sema.lower = sema.lower or mw.ustring.lower

	sema.roundDown = sema.roundDown or function(period, verse)
		if not tonumber(verse) then
			sema.changeCode('orange')
		else
			local rounded = math.floor(verse/period) * period
			return rounded
		end
	end

	sema.chapterSelect = sema.chapterSelect or function(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
		sema.changeCode('orange')
	end

	sema.interpret = sema.interpret or function(item)
		local output, isRef
		
		if type(item) == 'string' then
			if string.len(item) > 1 and string.sub(item, 1, 1) == '.' then
				address = string.sub(item, 2)
				if string.find(address, "^ref") then
					isRef = true
					local number = string.match(address, "%d+")
					if number then
						number = tonumber(number)
						if number > sema.refCount then
							sema.refCount = number
						end
					end
				end
				local returnable = sema[address] or sema.library.data.Sundry and sema.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 = sema.interpret(item[1])
			if type(presumedFunction) == 'function' then
				local parameters = {}
				for i = 2, 30 do
					if item[i] ~= nil then
						local expanded = sema.interpret(item[i])
						table.insert(parameters, expanded)
					else
						break
					end
				end
				local result = presumedFunction(unpack(parameters))
				output = result
			else
				local nested = sema
				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
		
		
		if sema.rerouteRefParamCount and sema.refCount < sema.rerouteRefParamCount or sema.refCount < sema.refParamCount or isRef then
			return output
		else
			return ""
		end
	end

	sema.convert = sema.convert or function(scheme, initiate)
		if type(scheme) == "table" then
			local initiate = tonumber(initiate) or initiate
			local converted = scheme[initiate]
			if converted == nil then
				sema.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
				sema.changeCode('orange')
			end
			return converted
		end
		sema.changeCode('orange')
	end

	sema.numToRoman = sema.numToRoman or function(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

	sema.period = sema.period or '.'

	sema.expand = sema.expand or function(args)
		--Instantiate our variables.
		local results = {}
		sema.code = 'green'
		local data = sema.library.data
		local ultimate = ''

		sema.author = args['author'] or args[2]
		sema.work = args['work'] or args[3]
		
		for i = 1, 5 do
			local refName = 'ref' .. i
			local paramNumber = i + 3
			sema[refName] = args[refName] or args[paramNumber]
			
			if i == 1 or sema[refName] then
				sema.refParamCount = i
			end
		end
	
		--Check if we've been given an author alias.
		if data.authorAliases[sema.author] then
			sema.author = data.authorAliases[sema.author]
		end

		if not data[sema.author] then
			sema.changeCode('yellow')
		else
			sema.aData = data[sema.author]
			if sema.aData.reroute then
				sema.reroute(sema.aData.reroute)
			else
				if sema.aData.aliases and sema.aData.aliases[sema.work] then
					sema.work = sema.aData.aliases[sema.work]
				end
				if not sema.aData.works or not sema.aData.works[sema.work] then
					sema.changeCode('yellow')
				else
					sema.wData = sema.aData.works[sema.work]
					if sema.wData.reroute then
						sema.reroute(sema.wData.reroute)
					end
				end
			end
		end

		--Load all author-level data.
		if sema.aData and sema.aData.aLink then
			results.author = '[[w:'..sema.aData.aLink..'|'..sema.author..']]'
		else
			results.author = sema.author
		end
		if sema.aData and sema.aData.year then
			results.year = sema.aData.year
		end

		--If the database has a link for the work, incorporate it.
		if not sema.wData or not sema.wData['wLink'] then
			results.work = sema.work
		else
			results.work = '[[w:'..sema.wData['wLink']..'|'..sema.work..']]'
		end
		
		--Some works have info which overrides the author-level info.
		if sema.wData then
			if sema.wData['year'] then
			results.year = sema.wData.year
			end
			if sema.wData['author'] ~= nil then
				results.author = sema.wData.author
			end
		end
	
		--The displayed reference usually consists of all the ref argument(s) joined with a period.
		sema.refDisplay = sema.ref1 and '' or false
		for i = 1, 5 do
			local whichRef = 'ref' .. tostring(i)
			if sema[whichRef] then
				local ref = sema[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 sema.aData and sema.aData.rdFormat and sema.aData.rdFormat.separator then
					separator = sema.aData.rdFormat.separator
				else
					separator = "."
				end
				
				if i > 1 then
					sema.refDisplay = sema.refDisplay .. separator
				end
				sema.refDisplay = sema.refDisplay .. sema[whichRef]
			else
				break
			end
		end
		if args['through'] then
			args['thru'] = args['through']
		end
		if args['thru'] then
			sema.refDisplay = sema.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 sema.wData and sema.wData['rlFormat'] then
			sema.rlFormat = sema.aData['rlFormat'..tostring(sema.wData.rlFormat)]
			if sema.rlFormat then
				sema.rlTitle = sema.wData['rlTitle']
				sema.refLink = {}
				for i = 1, 30 do
					local current = sema.rlFormat[i]
					if current then
						table.insert(sema.refLink, sema.interpret(current))
					else
						break
					end
				end
				sema.refLink = table.concat(sema.refLink)
			end
		end
		if sema.refLink and sema.refDisplay then
			results.ref = '[['..sema.refLink..'|'..sema.refDisplay..']]'
		else
			results.ref = sema.refDisplay or ''
		end
		if args['notes'] then
			results.notes = args.notes
		end
		results.code = sema.code
		return results

	end

	return sema
end

return export