Module:Technique

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua
CodeDiscussionEditHistoryLinksLink countNo test API Subpages:DocumentationTestsResultsSandboxLive code All modules

This module is intended to be the engine behind Template:Technique. It seems to work stable. However, language support is still work in progress in comparison to Template:Technique.

Template:Technique/sandbox uses this module. Because of the limited language support the module's main function p._technique() should not yet be used in production. The functions p.wikidataFallback() and p.wikidataLinkFallback() though can already be used to support Template:Technique and its language subtemplates.

Code

--[=[
This module is intended to be the engine behind "Template:Technique".
It can also be used directly from Lua using the function p._technique().

Setting "compat=yes" makes the functions p.technique() and p._technique()
behave more like the old "Template:Technique".
Differences to the old "Template:Technique" also in compat mode:
* if following non-fitting parameters are set without their predecessors
  special cases aren't used (unlike in "Template:Technique" as of 2019-08-11T12)
* no support for the "lenient" parameter,
  [[Special:Search/template: insource:lenient insource:technique -intitle:technique]]
  finds 4 uses of it (as of 2019-09-12).
]=]

local p = {}

local linguistic = require('Module:Linguistic')
local synonyms = require('Module:Technique/synonyms')
local declension = require('Module:Declension')
local fallback = require('Module:Fallback')

local function getCanonicTerm(term, termsTable)
	if synonyms[termsTable][term] then
		term = synonyms[termsTable][term]
	end
	return term
end

local function getWDmapping(term)
	local qid_LUT = require('Module:Technique/WikidataLUT').nouns
	return qid_LUT[term]
end

local function makeNoLinkNoLabelText(term, lang)
	local unsupportedText = mw.getCurrentFrame():expandTemplate{
		title = 'Please Translate',
		args = {term, 'Template:Technique', lang, demo='yes'}} -- TODO: rethink demo parameter!
	unsupportedText = unsupportedText .. '[[Category:Unsupported technique|' .. term .. ']]'
	return unsupportedText
end

local function makeQIDnoLabelText(term, qid)
	return '<span style="color:red">[[d:' .. qid .. '|' .. term .. ']]</span>'
end

-- @tparam[opt] string term the term for which a fallback translation shall be found
-- @tparam[opt='en'] string lang the language a fallback translation shall be found in (defaults to 'en')
-- @treturn ?string a translated wikitext string if found, otherwise nil
function p._wikidataFallback(term, lang)
	local map = getWDmapping(term)
	if map then
		local label
		-- following code copied from [[Module:Wikidata label]]
		local langList = mw.language.getFallbacksFor(lang)
		table.insert(langList, 1, lang)
		for _, language in ipairs(langList) do -- loop over language fallback list looking for label in the specific language
			label = mw.wikibase.getLabelByLang(map['qid'], language)
			if label then break end -- label found and we are done
		end
		
		return label or makeQIDnoLabelText(term, map['qid']) -- TODO: is this ok?
		-- somehow signify if the label isn't in the desired language?
	else
		return nil
	end
end

function p._wikidataLinkFallback(lang, term, wikitext)
	if not (mw.ustring.find(wikitext, '%[%[') or mw.ustring.find(wikitext, '//')) then
		term = getCanonicTerm(term, 'nouns')
		local map = getWDmapping(term)
		if map then
			local qids = {map['qid']}
			if map['altQids'] then
				for _,qid in ipairs(map['altQids']) do
					qids[#qids+1] = qid
				end
			end
			local sitelink = nil
			for _,qid in ipairs(qids) do
				sitelink = mw.wikibase.getSitelink(qid, lang .. 'wiki')
				if sitelink then
					break
				end
			end
			if sitelink then
				wikitext = '[[:' .. lang .. ':' .. sitelink .. '|' .. wikitext .. ']]'
			else
				wikitext = '[[d:' .. map['qid'] .. '|' .. wikitext .. ']]'
			end
		end
	end
	return wikitext
	-- TODO: implement fallback chain wikipedia -> commmons -> wikidata for links there?
end

-- @treturn string a wikitext string either translated or indicating missing
-- support for the given term
function p.wikidataFallback(frame)
	local term = mw.ustring.lower(frame.args.term) -- normalize term to lowercase
	term = getCanonicTerm(term, 'nouns')
	local lang = frame.args.lang
	
	local fallbackText = p._wikidataFallback(term, lang)
	if fallbackText then
		local resultText = p._wikidataLinkFallback(lang, term, fallbackText)
		-- TODO: messy approach to do this here
		return resultText
	else
		return makeNoLinkNoLabelText(term, lang)
	end
end

function p.wikidataLinkFallback(frame)
	local args = frame.args
	local wikitext = frame:preprocess('{{technique/' .. args.lang .. '|' ..
		args.term .. '|query={{technique/' .. args.lang .. '|case|query=' ..
		args.query .. '}} }}')
	wikitext = p._wikidataLinkFallback(args.lang, args.term, wikitext)
	return wikitext
end


-- turn a adj + noun group into a human-readable string
local function makegroup(noun, adj, langpage, lang, case)
	-- I preprocess parameters
	if noun == '' then noun = nil end
	if adj  == '' then adj  = nil end
	if not (noun or adj) then
		return nil
	end
	if noun then noun = string.lower(noun) end
	if adj  then adj  = string.lower(adj) end
	
	-- tansform the noun into another one supported by language specific lists
	noun = getCanonicTerm(noun, 'nouns')
	adj = getCanonicTerm(adj, 'adjectives')
	local normalizedNoun = noun
	
	local nounData = langpage.nouns[noun]
	local adjData = langpage.adjectives[adj]
	
	-- II error messages
	if noun and not langpage.nouns[noun] then
		nounData = '<span style="color:red">' .. noun .. '</span>' ..
			'[[Category:Unsupported technique|' .. noun .. ']]'
	end
	if adj and not langpage.adjectives[adj] then
		adjData = '<span style="color:red">' .. adj .. '</span>' ..
			'[[Category:Unsupported technique|!' .. adj .. ']]'
	end
	
	-- III process adj (before noun as it needs the original noun
	-- IIIa preprocessing
	local gender, number, decl = nil, nil, nil -- do not work if they are initialized in an if
	if adj then
	-- IIIb translation + declension
		-- languages with declension
		if type(adjData) == 'table' then
			if nounData then
				gender = nounData['gender']
				if gender == 'm' then gender = 1 end
				if gender == 'f' then gender = 2 end
				if gender == 'n' then gender = 3 end
				number = nounData['number']
				if not number then number = 's' end
				decl = langpage.declension[case]
			else
				gender = 1
				number = 's'
				decl = 'N' -- TODO: these three are not safe and maybe not the best default
			end
			
			if adjData['declension'] == 'regular' then
				adj = adjData['label']
				adj = declension.makeregular(lang, adj, 'adj', number, gender, decl)
			elseif langpage.adj[adj]['declension'] then -- irregular adjectives, delensions provided on the page
				adj = adjData[number][decl][gender]
			else -- languages with gender but no declensions
				adj = adjData['label']
			end
		-- languages with invariable adjectives
		elseif type(adjData) == 'string' then -- languages with invariable words
				adj = adjData
		else
				adj = 'something wrong with the datatype of adj:' .. adj
		end
	end
	
	-- IV process noun
	local group = ''
	if noun then
		if type(nounData) == 'table' then -- complex languages
			if langpage.declension[case] then -- with declension
				noun = nounData[langpage.declension[case]]
			elseif nounData['label'] then -- no declension
				noun = langpage.declension[noun]['label']
			else
				noun = 'error with noun: ' .. noun
			end
		elseif type(nounData) == 'string' then -- languages without declension
			noun = nounData
		else
			noun = 'error with the datatype of noun: ' .. noun
		end
		noun = p._wikidataLinkFallback(lang, normalizedNoun, noun) -- TODO: make work!
		group = linguistic.noungroup(noun, adj, lang)
	else
		group = adj .. '[[Category:Pages with incorrect template usage/Technique]]'
		-- add maintenance category if noun is missing
	end
	
	-- V  finalize
	return langpage.nomgroup(group, case, gender, case)
end

-- return the most approriate subpage for the language
local function findlang(lang)
	local langList = mw.language.getFallbacksFor(lang)
	table.insert(langList,1,lang)
	for _,language in ipairs(langList) do
		local page = mw.title.new('Technique/' .. language, 'Module')
		if page.exists then
			return 'Module:Technique/' .. language, lang
		end
	end
end

local function makeQSstatementCore(term, case, compat)
	-- currently only supports default and "on" cases
	local qid
	local map = getWDmapping(term)
	local core
	if compat == 'yes' then
		qid = mw.getCurrentFrame():expandTemplate{title='Technique/WikidataLUT', args={term}}
	elseif map then
		qid = map['qid']
	end
	if qid and qid ~= '' then
		local prop
		if map['material'] or compat == 'yes' then
			prop = 'P186'
		elseif map['process'] then
			prop = 'P2079'
		end
		if prop then
			core = prop .. ',' .. qid
			if case and case == 'on' then
				core = core .. ',P518,Q861259'
			end
		end
	end
	return core
end

local function makeQSstring(args, isSimple)
	local QScode = ''
	if not (isSimple and args.noun1) then -- TODO: noun1 could be not bound if not in compat mode
		return ''
	end
	local fragments = {'noun1', 'on'}
	local statements = {}
	local isAllFound = true
	for _,f in ipairs(fragments) do
		if args[f] then -- only continue if the term is given
--		do
			local case
			if f == 'on' then
				case = 'on'
			end
			local statement = makeQSstatementCore(args[f], case, args.compat)
			if statement and statement ~= '' then
				table.insert(statements, statement)
			else
				isAllFound = false -- TODO: simply return '' and get rid of this variable??
			end
		end
	end
	if isAllFound then
		QScode = '<div style="display: none;">medium QS:' .. table.concat(statements, ';') .. '</div>'
--		QScode = '<i>medium QS:' .. table.concat(statements, ';') .. '</i>' -- for debugging
	end
	return QScode
end

-- main function used by the module
function p._technique(args, lang)
	-- escape the rest for special values with separate translation
	local isSimple = true
	for k,_ in pairs(args) do
		if k ~= 'noun1' and k ~= 1 and k ~= 'on' and k ~= 2 and k ~= 'compat' and k ~= 'lang' then
			isSimple = false
		end
	end
	local test = string.lower((args.noun1 or '') .. (args.on or ''))
	if isSimple and test == 'oilcanvas' then
		local QScode = makeQSstring(args, isSimple)
		return fallback.translatelua({args={'I18n/oil on canvas', lang=lang}}) .. QScode
	elseif isSimple and (test == 'oilwood' or test == 'oilpanel') then
		local QScode = makeQSstring(args, isSimple)
		return fallback.translatelua({args={'I18n/oil on panel', lang=lang}}) .. QScode
	elseif isSimple and test == 'unknown' then
		return mw.getCurrentFrame():expandTemplate{title='unknown', args={'technique'}}
	end

	-- set language
	local langpagetitle, lang = findlang(lang)
	local langpage = require(langpagetitle)
	
	local nl = {}
	function nl.nomgroup(group, case)
		if case == 'on' then
			return 'op ' .. group
		elseif case == 'over' then
			return 'over ' .. group
		elseif case == 'mounted' then
			return 'bevestigd op ' .. group
		else 
			return group
		end
	end
	function nl.grouporder(maingroup, overgroup, ongroup, mountedgroup)
		return maingroup .. ' ' .. overgroup .. ' ' .. ongroup .. ' ' .. mountedgroup 
	end 
	nl.declension = {
		['default'] = nil,
		['on'] = nil,
		['over'] = nil,
		['mounted'] = nil,
	}
	nl.nouns = {
		['canvas'] = 'doek',
		['oil'] = 'olieverf',
		['porcelain'] = 'porselein',
		['wood'] = 'hout',
	} -- TODO: make possible to use p._wikidataFallback(term, 'nl')
	nl.adjectives = {}
	if lang == 'nl' then
		langpage = nl
	end

	
	-- group arguments
	local group1 = makegroup(args.noun1, args.adj1, langpage, lang, 'default')
	local group2 = makegroup(args.noun2, args.adj2, langpage, lang, 'default')
	local group3 = makegroup(args.noun3, args.adj3, langpage, lang, 'default')
	local group4 = makegroup(args.noun4, args.adj4, langpage, lang, 'default')
	local group5 = makegroup(args.noun5, args.adj5, langpage, lang, 'default')
	local maingroup = linguistic.conj({group1, group2, group3, group4, group5}, lang) -- technique without "on", "mounted" and "over"
	
	local overgroup = makegroup(args.over, args.adjover, langpage, lang, 'over')
	local ongroup = makegroup(args.on, args.adjon, langpage, lang, 'on')
	local mountedgroup = makegroup(args.mounted, args.adjmounted, langpage, lang, 'mounted')
	
	if not overgroup then overgroup = '' end -- groups set to '' so they can be concatenated in the /lang page
	if not ongroup then ongroup = '' end
	if not mountedgroup then mountedgroup = '' end
	local result = langpage.grouporder(maingroup, overgroup, ongroup, mountedgroup)
	
	local result = mw.text.trim(result)
	-- maybe useful for compatibility:
	local result = mw.ustring.gsub(result, '%s+', ' ')
	
	local QScode = makeQSstring(args, isSimple)
	local result = result .. QScode
	
	if not isSimple then
		result = result .. '[[Category:Pages with complex technique templates]]'
	end
	return result
end

-- function to be called from template namespace
function p.technique(frame) 
	local args = {}
	local templateargs = frame:getParent().args
	local compat = mw.text.trim(templateargs.compat or '')
	for name, value in pairs(templateargs) do
		if value ~= '' then -- nuke empty strings
			args[name] = mw.text.trim(value)
		end
	end
	-- resolve parameter names
	-- TODO: adapt to allow arbitrary number of terms and term combinations
	args.noun1 = args[1]
	args.noun2 = args['and']
	args.noun3 = args['and2']
	args.noun4 = args['and3']
	args.noun5 = args['and4']
	args.on = args[2] or args['on']
	args.over = args['over']
	args.mounted = args['mounted']
	args.adj1 = args['adj'] or args['color']
	args.adj2 = args['adjand'] or args['colorand']
	args.adj3 = args['adjand2'] or args['colorand2']
	args.adj4 = args['adjand3'] or args['colorand3']
	args.adj5 = args['adjand4'] or args['colorand4']
	args.adjon = args['adjon'] or args['coloron']
	args.adjover = args['adjover'] or args['colorover']
	args.adjmounted = args['adjmounted'] or args['colormounted']

	-- read values given with invoking module, needed e.g. for testing
	for name, value in pairs(frame.args) do
		if value ~= '' then -- nuke empty strings
			args[name] = value
		end
	end
	
	local lang = args.lang
	if not lang or lang == '' then
		lang = frame:callParserFunction('int', 'lang') -- get user's chosen language
	end
	
	return p._technique(args, lang)
end

return p