Module:Technique

From The Goon Show Depository

Revision as of 21:52, 19 May 2022 by commons>Marsupium (fix bug in function getIsSimple(): don't produce error if first element in a list of 'default' or 'on' groups is left out like Special:Permalink/532561043)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

--[=[
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)
* use of [[Module:Linguistic]] with different order of words in
  function p.noungroup() in gl, vi
]=]

local p = {}

require('Module:No globals') -- used for debugging purposes as it detects cases of unintended global variables
local linguistic = require('Module:Linguistic')
local synonyms = mw.loadData('Module:Technique/synonyms')
local declension = require('Module:Declension')
local fallback = require('Module:Fallback')

local function getSingularTerm(term)
	local plurals = mw.loadData('Module:Technique/WikidataLUT').plurals
	local singular = nil
	if plurals[term] then
		singular = plurals[term]
	end
	return singular
end

local function getWDmapping(term, wordtype)
	term = synonyms[wordtype][term] or term
	local qid_LUT = mw.loadData('Module:Technique/WikidataLUT')[wordtype]
	return qid_LUT[term]
end

-- return table of fallback languages and their existing subpages for the language
local function findLang(lang)
	local langList = mw.language.getFallbacksFor(lang)
	table.insert(langList,1,lang)
	for i,l in ipairs(langList) do
		local page = mw.title.new('Technique/' .. l, 'Module')
		langList[i] = {lang = l, subpage = page.exists and 'Module:Technique/' .. l or nil}
		--[[page.exist checking could be swapped out from here to reduce
		its expensive access ]]
	end
	return langList
end

local function getDeprecatedCat(term, wordtype)
	local result = ''
	local map = getWDmapping(term, wordtype)
	if map and map.deprecated then
		local sortTerm = synonyms[wordtype][term] or term
		local adjectiveMark = (wordtype == 'adjectives') and '!' or ''
		result = ('[[Category:Pages using Template:Technique with deprecated term|'
			.. adjectiveMark .. sortTerm .. ']]')
	end
	return result
end

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

-- if the reference item isn't a material, but is a technique and has one
-- valid product statement then return the qid of the product (for labeling)
function p.getProductQid(map)
	local productQid
	if not map.material and map.process then
		local productStatements = mw.wikibase.getBestStatements(map.qid, 'P1056')
		if #productStatements == 1 then
			productQid = productStatements[1].mainsnak.datavalue.value.id
		end
	end
	return productQid
end

local function getUnsupportedLink(lang, wordtype, label, system)
	local translationTargetPage
	if system == 'modules' then
		if wordtype == 'adjectives' then
			translationTargetPage = 'Module:Technique/' .. lang
		else
			translationTargetPage = 'Module:Technique/WikidataLUT'
		end
	else
		translationTargetPage = 'Template:Technique/' .. lang .. ((wordtype == 'adjectives') and '/adjectives' or '')
	end
	local url = ('//commons.wikimedia.org/w/index.php?title='
		.. translationTargetPage
		.. '&action=edit')
	local link = mw.ustring.format(
		'<span class="plainlinks">[%s <span style="color:red">%s</span>]</span>',
		url, label)
	return link
end

local function getUnsupportedCatLink(term, wordtype)
	local adjectiveMark = (wordtype == 'adjectives') and '!' or ''
	return '[[Category:Unsupported technique|' .. adjectiveMark .. term .. ']]'
end

local function getUnsupportedString(term, wordtype, lang, label, system, compat)
	local compatUnsupportedLinkSystem = system
	if compat == 'yes' then
		compatUnsupportedLinkSystem = 'templates'
	end
	local unsupportedLink = getUnsupportedLink(lang, wordtype, label, compatUnsupportedLinkSystem)
	local unsupportedCatLink = (lang == 'en') and getUnsupportedCatLink(term, wordtype) or ''
	return unsupportedLink .. unsupportedCatLink
end

-- fallback chain: Commons module subpage -> Wikidata label -> those two in next language -> …
function p.getLabelTranslationFallback(term, wordtype, langList, system, compat, lenient)
	local labelQid, map, label, usedLang
	if string.find(term, '^q%d+$') then
		-- term is a Wikidata Q-ID, so we use it to create the label
		labelQid = string.upper(term)
	end
	local canonizedTerm = synonyms[wordtype][term]
	-- loop over language fallback list looking for label in the specific language
	for i,langTable in pairs(langList) do
		if langTable.subpage and i>1 then
			local langData = require(langTable.subpage)
			local termData = langData[wordtype][term] or (canonizedTerm and langData[wordtype][canonizedTerm])
			label = termData and ((type(termData) == 'string') and termData or termData.default or termData.n)
			-- TODO: adapt for adjectives
			if label then
				usedLang = langTable.lang
				break -- label found and we are done
			end
		end
		-- if for given language no noun label was found on Commons look at Wikidata:
		if wordtype == 'nouns' and not labelQid then
			-- only run if not already run before
			map = getWDmapping(term, 'nouns')
			labelQid = (map and map['qid']) and (p.getProductQid(map) or map['qid']) or '0'
		end
		if wordtype == 'nouns' and labelQid ~= 0 then
			label = mw.wikibase.getLabelByLang(labelQid, langTable.lang)
			-- gives nil if not found
			if label then
				usedLang = langTable.lang
				break -- label found and we are done
			end
		end
	end
	if label and usedLang ~= langList[1].lang then
		label = mw.ustring.format('<span lang="%s">\'\'%s\'\'</span>', usedLang, label)
	end
	if not label and map and map['qid'] then
		label = makeQIDnoLabelText(term, map['qid'])
		return label
	end
	if not label or (usedLang ~= langList[1].lang and wordtype == 'adjectives') then
		if lenient then
			label = term
		else
			label = getUnsupportedString(term, wordtype, langList[1].lang,
				label or term, system, compat)
		end
	end
	-- now label should always be a string
	return label
end


function p._wikidataLinkFallback(lang, term, wikitext)
	if not (mw.ustring.find(wikitext, '%[%[') or mw.ustring.find(wikitext, '//')) then
		local map
		if string.find(term, '^q%d+$') then
			-- term is a Wikidata Q-ID, so we use it to create the link
			map = {qid = string.upper(term)}
		else
			term = synonyms.nouns[term] or term
			term = getSingularTerm(term) or term
			map = getWDmapping(term, 'nouns')
		end
		if map and map['qid'] then
			local qids = {map['qid']}
			if map['altQids'] then
				for _,qid in ipairs(map['altQids']) do
					qids[#qids+1] = qid
				end
			end
			-- use primary language subtag for link creation
			local lang = mw.text.split(lang, '-', true)[1]
			local sitelink, interwikiPrefix
			for _,qid in ipairs(qids) do
				sitelink = mw.wikibase.getSitelink(qid, lang .. 'wiki')
				if sitelink then
					interwikiPrefix = lang .. ':'
					break
				end
			end
			if not sitelink then
				for _,qid in ipairs(qids) do
					sitelink = mw.wikibase.getSitelink(qid, 'commonswiki')
					if sitelink then
						interwikiPrefix = ''
						break
					end
				end
			end
			if sitelink then
				wikitext = '[[:' .. interwikiPrefix .. sitelink .. '|' .. wikitext .. ']]'
			else
				wikitext = '[[d:' .. map['qid'] .. '|' .. wikitext .. ']]'
			end
		end
	end
	return wikitext
end

local function getAgreement(lang, noun, query, caseletter)
	local agreement = mw.getCurrentFrame():expandTemplate{
		title = 'technique/' .. lang,
		args={noun, query = 'gender'}}
	-- give a default gender and number agreement for some languages
	-- that don't specify a default ending
	-- [[Template:Technique/ca]] doesn't have switches for all terms which means
	-- translations are given as agreement
	-- --> also give a default gender and number agreement for
	-- cases where the agreement doesn't match '^[a-z][a-z]$'
	local defaultGenderNumber = {
		ca='m', da='c', fr='m', gl='m', it='m', pl='ms', pt='m', ro='m', scn='m'}
	if defaultGenderNumber[lang] and not string.find(agreement, '^[a-z][a-z]?$') then
		agreement = defaultGenderNumber[lang]
	end
	agreement = agreement .. (caseletter or '')
	return agreement
end

local function termToTemplateWikitext(wordtype, lang, term, query, agreement)
	local frame = mw.getCurrentFrame()
	local wikitext
	if wordtype == 'nouns' then
		local queryCase = frame:expandTemplate{title='technique/' .. lang, args={'case', query=query}}
		wikitext = frame:expandTemplate{title='technique/' .. lang, args={term, query=queryCase}}
	elseif wordtype == 'adjectives' then
		wikitext = frame:expandTemplate{title='technique/' .. lang .. '/adjectives', args={term, agreement=agreement}}
	end
	return wikitext
end

local function getTermWikitextWithTemplates(term, wordtype, lang, query, noun, caseletter, lenient)
	if term == '' then
		return ''
	end
	-- find out agreement for adjectives:
	local agreement = noun and getAgreement(lang, noun, query, caseletter)
	local wikitext = termToTemplateWikitext(wordtype, lang, term, query, agreement)
	
	if wikitext == '' then
		local canonicalTerm = synonyms[wordtype][term]
		if canonicalTerm then
			local agreement = getAgreement(lang, canonicalTerm, query, caseletter)
			wikitext = termToTemplateWikitext(wordtype, lang, canonicalTerm, query, agreement)
		end
		if wikitext == '' then
			local langList = findLang(lang)
			local lenient = lenient ~= '' and lenient
			wikitext = p.getLabelTranslationFallback(
				term, wordtype, langList, 'templates', 'yes', lenient)
		end
	end
	if wordtype == 'nouns' then
		wikitext = p._wikidataLinkFallback(lang, term, wikitext)
	end
	wikitext = wikitext .. getDeprecatedCat(term, wordtype)
	return wikitext
end

function p.wikidataLinkFallback(frame)
	local args = frame.args
	local wordtype = args.wordtype or 'nouns'
	return getTermWikitextWithTemplates(args.term, wordtype, args.lang,
		args.query, args.noun, args.caseletter, args.lenient)
end


local function processAdjective(adj, nounData, langData, langList, case, compat, lenient)
	if adj == '' then
		return nil
	end
	
	local adj0 = adj
	adj = synonyms.adjectives[adj] or adj
	local adjData = langData.adjectives[adj0] or langData.adjectives[adj]
	if adj and not langData.adjectives[adj] then
		adjData = p.getLabelTranslationFallback(adj0, 'adjectives', langList, 'modules', compat, lenient)
	end
	
	local gender, number, decl
	-- adjectives with declension
	if type(adjData) == 'table' then
		local genderConvert = {m=1, f=2, n=3, c=1}
		gender = nounData and nounData.gender and genderConvert[nounData.gender]
		number = nounData and nounData.number
		decl = langData.declension[case]
		
		local parts = adjData.parts
		for i = 1,#parts do
			if type(parts[i]) == 'table' then
				parts[i] = declension.selectAdjectiveForm(parts[i], {number=number, case=decl, gender=gender})
			end
		end
		adj = table.concat(parts)
	-- invariable adjectives
	elseif type(adjData) == 'string' then
		adj = adjData
	end
	adj = adj .. getDeprecatedCat(adj0, 'adjectives')
	return adj
end

local function makegroupWithTemplates(noun, adj, lang, case, compat, lenient, beforeMountedGrammarNoun)
	local frame = mw.getCurrentFrame()
	if case == 'default' then
		case = 'basic'
	end
	local caseletter = frame:expandTemplate{title='technique/' .. lang, args={
		'case', query=case}}
	adj = adj or {}
	local adjCounter = #adj
	while adjCounter > 0 do
		adj[adjCounter] = getTermWikitextWithTemplates(adj[adjCounter], 'adjectives', lang, case, noun, caseletter, lenient)
		adjCounter = adjCounter - 1
	end
	adj = linguistic.conj(adj, lang)
	local noun = noun and getTermWikitextWithTemplates(noun, 'nouns', lang, case, nil, caseletter, lenient)
	local gender_on = (case == 'mounted' and beforeMountedGrammarNoun
		and frame:expandTemplate{title='technique/' .. lang, args={
		beforeMountedGrammarNoun, query='gender'}})
	local wikitext = frame:expandTemplate{title='technique/' .. lang, args={
		case, noun = noun, adj = adj, ['gender on'] = gender_on}}
	return wikitext
end

-- turn a adj + noun group into a human-readable string
local function makegroup(noun, adj, langData, lang, case, compat, lenient, beforeMountedGrammar)
	local langList = findLang(lang)
	
	if noun == '' then noun = nil end
	noun = synonyms.nouns[noun] or noun
	local canonicalNoun = noun
	local nounData = langData.nouns[noun] or noun and p.getLabelTranslationFallback(
		noun, 'nouns', langList, 'modules', compat, lenient)
	
	adj = adj or {}
	local adjCounter = #adj
	while adjCounter > 0 do
		adj[adjCounter] = processAdjective(
			adj[adjCounter], nounData, langData, langList, case, compat, lenient)
		adjCounter = adjCounter - 1
	end
	
	if not noun and #adj == 0 then
		return nil
	end
	
	adj = linguistic.conj(adj, lang)
	
	-- process noun
	local group = ''
	if noun then
		local nounLabel
		if type(nounData) == 'table' then -- complex languages
			nounLabel = nounData[langData.declension[case] or 'default']
				or nounData.default
				or nounData.n
			-- if langData doesn't define cases use 'default'
			-- if desired case isn't available, fall back to 'default' then nominative
			-- TODO: use nominative/'n' at all (here)?
			if not nounLabel then
				-- TODO: cleanup! ((use for adjectives and nouns or neither?))
				nounLabel = p.getLabelTranslationFallback(noun, 'nouns', langList, 'modules', compat)
			end
		elseif type(nounData) == 'string' then -- languages without declension
			nounLabel = nounData
		end
		local outNoun
		if nounData.link and not mw.ustring.find(nounLabel, '%[%[') then
		-- TODO: remove the 2nd part! (once not needed any more)
			outNoun = '[[:' .. nounData.link .. '|' .. nounLabel .. ']]'
		else
			outNoun = p._wikidataLinkFallback(lang, canonicalNoun, nounLabel)
		end
		outNoun = outNoun .. getDeprecatedCat(canonicalNoun, 'nouns')
		group = linguistic.noungroup(outNoun, adj, lang)
	else
		group = adj .. '[[Category:Pages with incorrect template usage/Technique]]'
		-- add maintenance category if noun is missing
	end
	
	-- finalize
	return langData.nomgroup(group, case, beforeMountedGrammar)
end

local function makeQSstatementCore(term, case, compat)
	-- currently only supports default and "on" cases
	local qid
	local map = getWDmapping(term, 'nouns')
	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.caseGroups.default[1]) then
		return ''
	end
	local fragments = {'default', 'on'}
	local statements = {}
	local isAllFound = true
	for _,f in ipairs(fragments) do
		if args.caseGroups[f][1] and args.caseGroups[f][1].noun then
			-- only continue if the term is given
			local case
			if f == 'on' then
				case = 'on'
			end
			local statement = makeQSstatementCore(args.caseGroups[f][1].noun, 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

local function getBeforeMountedGrammarNoun(caseGroups)
	local cases = {'on', 'over', 'default'}
	for _,case in ipairs(cases) do
		if #caseGroups[case] > 0 then
			return caseGroups[case][#caseGroups[case]].noun
		end
	end
end

local function getBeforeMountedGrammar(noun, langData)
	local beforeMountedData
	if noun then
		beforeMountedData = langData.nouns[noun]
	end
	local beforeMountedGender = beforeMountedData and beforeMountedData.gender
	local genderConvert = {m=1, f=2, n=3, c=1}
	beforeMountedGender = beforeMountedGender and genderConvert[beforeMountedGender]
	local beforeMountedNumber = beforeMountedData and beforeMountedData.number
	local beforeMountedGrammar = {
		gender = beforeMountedGender,
		number = beforeMountedNumber}
	return beforeMountedGrammar
end

local function getIsSimple(caseGroups)
	local isSimple = false
	local defaultOnOnly = (#caseGroups.over == 0 and #caseGroups.mounted == 0)
	local defaultOnOneGroup = (#caseGroups.default <= 1 and #caseGroups.on <=1)
	local default1NoAdj = (#caseGroups.default == 0 or caseGroups.default[1] and caseGroups.default[1].adj == nil)
	local on1NoAdj = (#caseGroups.on == 0 or caseGroups.on[1] and caseGroups.on[1].adj == nil)
	return defaultOnOnly and defaultOnOneGroup and default1NoAdj and on1NoAdj
end

local function getQidToEnglishTermTable(wordtype)
	local qid_LUT = mw.loadData('Module:Technique/WikidataLUT')[wordtype]
	local QidToEnglishTermTable = {}
	for term,termTable in pairs(qid_LUT) do
		if termTable.qid then
			QidToEnglishTermTable[termTable.qid] = term
		end
	end
	return QidToEnglishTermTable
end

local function qidsToEnglishTerms(caseGroups)
	local qidToNounTable = getQidToEnglishTermTable('nouns')
	local qidToAdjTable = getQidToEnglishTermTable('adjectives')
	for case,caseGroup in pairs(caseGroups) do
		for i,group in pairs(caseGroup) do
			if group.noun then
				caseGroups[case][i].noun = qidToNounTable[string.upper(group.noun)] or group.noun
			end
			if group.adj then
				for j,adj in ipairs(group.adj) do
					caseGroups[case][i].adj[j] = qidToAdjTable[string.upper(adj)] or adj
				end
			end
		end
	end
	return caseGroups
end

local function getCaseStrings(caseGroups, langData, lang, system, compat, lenient, beforeMountedGrammar, beforeMountedGrammarNoun)
	local caseStrings = {}
	for case, caseData in pairs(caseGroups) do
		local caseGroupStrings = {}
		for _,group in pairs(caseData or {}) do
			-- caseData should always be a table, "or" just for compatibility with noncompliant input
			local groupString = nil
			if system == 'modules' then
				groupString = (
					makegroup(group.noun, group.adj, langData, lang, case,
						compat, lenient, beforeMountedGrammar))
			elseif system == 'templates' then
				groupString = (
					makegroupWithTemplates(group.noun, group.adj, lang, case,
						compat, lenient, beforeMountedGrammarNoun))
			end
			caseGroupStrings[#caseGroupStrings+1] = groupString
		end
		caseStrings[case] = linguistic.conj(caseGroupStrings, lang)
	end
	return caseStrings
end

-- main function used by the module
function p._technique(args)
	local lang = args.lang
	local caseGroups = qidsToEnglishTerms(args.caseGroups)
	local isSimple = getIsSimple(caseGroups)
	
	local function vOrNil(tab, key)
		return tab and tab[key]
	end
	if isSimple and vOrNil(caseGroups.default[1], 'noun') == 'oil' and vOrNil(caseGroups.on[1], 'noun') == 'canvas' then
		local QScode = makeQSstring(args, isSimple)
		return fallback.translatelua({args={'I18n/oil on canvas', lang=lang}}) .. QScode
	elseif isSimple and vOrNil(caseGroups.default[1], 'noun') == 'oil' and (vOrNil(caseGroups.on[1], 'noun') == 'wood' or vOrNil(caseGroups.on[1], 'noun') == 'panel') then
		local QScode = makeQSstring(args, isSimple)
		return fallback.translatelua({args={'I18n/oil on panel', lang=lang}}) .. QScode
	elseif isSimple and vOrNil(caseGroups.default[1], 'noun') == 'unknown' and not vOrNil(caseGroups.on[1], 'noun') then
		return mw.getCurrentFrame():expandTemplate{title='unknown', args={'technique'}}
	end
	
	local beforeMountedGrammarNoun = getBeforeMountedGrammarNoun(caseGroups)
	
	local result = nil
	if args.system == 'templates' then
		local frame = mw.getCurrentFrame()
		local templateLang = frame:expandTemplate{title='fallback', args={'Technique', lang}}
		local caseStrings = getCaseStrings(caseGroups, nil, templateLang,
			'templates', args.compat, args.lenient, nil, beforeMountedGrammarNoun)
		result = frame:expandTemplate{title='technique/' .. templateLang, args={
			'order', A=caseStrings.default, over=caseStrings.over,
			on=caseStrings.on, mounted=caseStrings.mounted}}
	else -- system 'modules'
		local langData
		local langList = findLang(lang)
		for _,t in pairs(langList) do
			if t.subpage then
				langData = require(t.subpage)
				break
			end
		end
		local beforeMountedGrammar = getBeforeMountedGrammar(beforeMountedGrammarNoun, langData)
		local caseStrings = getCaseStrings(caseGroups, langData, lang,
			'modules', args.compat, args.lenient, beforeMountedGrammar, nil)
		result = langData.grouporder(caseStrings.default, caseStrings.over, caseStrings.on, caseStrings.mounted)
		
		result = mw.text.trim(result)
		-- maybe useful for compatibility:
		result = mw.ustring.gsub(result, '%s+', ' ')
		-- the following should improve word order in some situations with mixed
		-- RTL and LTR text
		if mw.language.new(lang):isRTL() then
			result = string.format('<span dir="rtl">%s</span>', result)
		end
	end
	
	local QScode = makeQSstring(args, isSimple)
	result = result .. QScode
	
	if not isSimple then
		result = result .. '[[Category:Pages with complex technique templates]]'
	end
	return result
end

local function stringStartsWith(inString, matchTable)
	local start
	local rest = inString
	for _,m in ipairs(matchTable) do
		if string.sub(inString, 1, #m) == m then
			start = m
			rest = string.sub(inString, #m + 1)
			break
		end
	end
	return start, rest
end

-- provide the keys of a table in sorted order
local function getSortedKeys(inTable)
	local sortedKeys = {}
	for key,_ in pairs(inTable) do
		table.insert(sortedKeys, key)
	end
	table.sort(sortedKeys)
	return sortedKeys
end

function p.read_input_parameters(templateargs)
	local templateargs2 = {}
	for key, value in pairs(templateargs) do
		if value ~= '' then -- nuke empty strings
			templateargs2[key] = string.lower(mw.text.trim(value))
		end
	end
	
	-- move keys without explicit case
	templateargs2.and0 = templateargs2[1]
	templateargs2.adjand0 = templateargs2.adj or templateargs2.color
	templateargs2.and1 = templateargs2['and']
	templateargs2.adjand1 = templateargs2.adjand or templateargs2.colorand
	templateargs2.on1 = templateargs2.on or templateargs2[2]
	templateargs2[1] = nil
	templateargs2.adj = nil
	templateargs2.color = nil
	templateargs2['and'] = nil
	templateargs2.adjand = nil
	templateargs2.colorand = nil
	templateargs2.on = nil
	templateargs2[2] = nil
	
	local args2 = {
		compat = templateargs2.compat,
		system = templateargs2.system,
		lang = templateargs2.lang,
		lenient = templateargs2.lenient,
		-- a search for "all: insource:/\| *lenient/ insource:technique -intitle:technique"
		-- finds the templates where this parameter is used
		caseGroups = {default = {}, over = {}, on = {}, mounted = {}}}
	for key, value in pairs(templateargs2) do
		if value == '' then
			break
		end
		value = mw.text.trim(value)
		local adjPart, rest = stringStartsWith(key, {'adj', 'color'})
		local casePart, rest = stringStartsWith(rest, {'and', 'on', 'over', 'mounted'})
		if casePart and rest == nil or rest == '' then rest = 1 end
		local counter = tonumber(rest)
		if casePart and type(counter) == 'number' and counter == math.abs(math.floor(counter)) then
			-- counter is 0 or positive integer
			if casePart == 'and' then
				casePart = 'default'
				counter = counter + 1
			end
			local wordtype = 'noun'
			if adjPart == 'adj' or adjPart == 'color' then
				wordtype = 'adj'
				value = mw.text.split(value, ';')
			end
			if not args2.caseGroups[casePart][counter] then
				args2.caseGroups[casePart][counter] = {}
			end
			args2.caseGroups[casePart][counter][wordtype] = value
		end
	end
	
	-- TODO: is the following needed? for coping with left out numbers? write test case
	for _,case in ipairs(args2.caseGroups) do
		local sortedCaseKeys = getSortedKeys(case)
		local gaplessCaseGroups = {}
		for _,key in ipairs(sortedCaseKeys) do
			table.insert(gaplessCaseGroups, case[key])
		end
		args2.caseGroups[case] = gaplessCaseGroups
	end
	
	return args2
end

-- function to be called from template namespace
function p.technique(frame)
	local templateargs = frame:getParent().args
	local args = p.read_input_parameters(templateargs)
	args.system = frame.args.system
	args.compat = args.compat or frame.args.compat
	if not args.lang or args.lang == '' then
		-- get user's chosen language
		args.lang = frame:callParserFunction('int', 'lang')
	end
	return p._technique(args)
end

return p