Calamity Mod Wiki
Register
Advertisement
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Lua logo Documentation The documentation below is transcluded from Module:Recipes/doc. (edit | history)

This module provides the functionality of {{Recipes}}. See that template's page for documentation.

This module was originally written by Westgrass for the Vanilla Terraria Wiki. See the original Module.


------- l10n info --------------
local l10n_info = mw.loadData('Module:Recipes/l10n')

------- The following is not related to l10n. --------------


local item_link = require('Module:Item').go
local trim = mw.text.trim
local cargo = mw.ext.cargo
local cache = require 'mw.ext.LuaCache'

local currentFrame -- global cache for current frame object.
local inputArgs -- global args cache.
local lang -- cache current lang.
local l10n_table

local resultanchor

local l10n = function(key)
	return l10n_table[key] or l10n_info['en'][key]
end

local extCols_stationBefore = nil
local extCols_stationAfter = nil

local extCols_A = nil
local extCols_B = nil
local extCols_C = nil
local extCols_D = nil

function getArg(key)
	local v = trim(inputArgs[key] or '')
	if v=='' then
		return nil
	else
		return v
	end
end

local itemLink = (function()
	local cache = {}
	return function(name, args)
		local key = name.."|"
		if args then
			for k, v in pairs(args) do
				key = key..k..'='..tostring(v)..'|'
			end
		end
		if not cache[key] then
			local args = args and mw.clone(args) or {}
			args['mode'] = args['mode'] or nil
			args[1] = name
			if (not args[2]) or args[2] == '' then
				args[2] = currentFrame:expandTemplate{ title = 'tr', args = {name, lang=lang} }
			end
			args['small'] = 'y'
			args['lang'] = lang or 'en'
			args['nolink'] = args['nolink'] and 'y' or nil
			args['class'] = args['class'] or nil
			cache[key] = item_link(currentFrame, args)
		end
		return cache[key]
	end
end)()


-- credit: http://richard.warburton.it
-- this version is with trim.
local explode = function(div,str)
	if (div=='') then return false end
	local pos,arr = 0,{}
	-- for each divider found
	for st,sp in function() return string.find(str,div,pos,true) end do
		table.insert(arr, trim(string.sub(str,pos,st-1))) -- Attach chars left of current divider
		pos = sp + 1 -- Jump past current divider
	end
	table.insert(arr, trim(string.sub(str,pos))) -- Attach chars right of last divider
	return arr
end

-- retuan a array of itemname, split xxx/yyy to item1=xxx, item2=yyy. If it's something like "Lead/Iron Bar", it will normalize as item1 = Iron Bar, item2 = Lead Bar.
local split = (function()
	local metals = {
		['Copper/Tin'] = 1,
		['Silver/Tungsten'] = 1,
		['Gold/Platinum'] = 1,
		['Iron/Lead'] = 1,
		['Cobalt/Palladium'] = 1,
		['Mythril/Orichalcum'] = 1,
		['Adamantite/Titanium'] = 1,
		['Tin/Copper'] = 2,
		['Tungsten/Silver'] = 2,
		['Platinum/Gold'] = 2,
		['Lead/Iron'] = 2,
		['Palladium/Cobalt'] = 2,
		['Orichalcum/Mythril'] = 2,
		['Titanium/Adamantite'] = 2,
	}
	return function(name)
		local count = select(2, name:gsub("/", "/", 2))
		if count == 0 then
			-- only 1 item
			return { trim(name) }
		elseif count == 1 then
			-- 2 items
			local item1a, item1b, item2a, item2b = name:match("^%s*(%S+)%s*(.-)/%s*(%S+)%s*(.-)$")
			local x = metals[item1a..'/'..item2a]
			if tostring(item1b) == '' and x then
				item1b = item2b
			end
			if x == 2 then
				return {trim(item2a..' '..item2b), trim(item1a..' '..item1b)}
			else
				return {trim(item1a..' '..item1b), trim(item2a..' '..item2b)}
			end
		else
			-- 3 or more items
			return explode('/', name)
		end
	end
end)()

-- return 1 or 2 value(s), when input is name[note], return item, note.
local itemname = function(str)
		local item, note = str:match("^(.-)(%b[])$")
		if item then
			return item, note
		else
			return str
		end
end

-- normalize ingredient name input, Lead Bar=>‡Lead Bar‡, Iron/Lead Bar => ‡Iron Bar‡Lead Bar‡, Lead/Iron Bar => ‡Iron Bar‡Lead Bar‡ ....
local normalize = function(name)
	local result = '‡'
	for k, v in ipairs(split(name)) do
		result = result .. itemname(v) .. '‡'
	end
	return result
end

local escape = function(str)
	return str:gsub("'", "\\'"):gsub("'", "\\'")
end
local enclose = function(str)
	return "'" .. escape(str) .. "'"
end

local getItemGroupName = function(item)
	if item == 'Wood' or item == 'Ebonwood' or item == 'Rich Mahogany' or item == 'Pearlwood' or item == 'Shadewood'
	or item == 'Spooky Wood' or item == 'Boreal Wood' or item == 'Palm Wood' or item == 'Acidwood' then
		return 'Any Wood'
	elseif item == 'Iron Bar' or item == 'Lead Bar' then
		return 'Any Iron Bar'
	elseif item == 'Sand Block' or item == 'Pearlsand Block' or item == 'Crimsand Block' or item == 'Ebonsand Block' or item == 'Hardened Sand Block' or item == 'Astral Sand' then
		return 'Any Sand'
	elseif item == 'Red Pressure Plate' or item == 'Green Pressure Plate' or item == 'Gray Pressure Plate' or item == 'Brown Pressure Plate'
	    or item == 'Blue Pressure Plate' or item == 'Yellow Pressure Plate' or item == 'Lihzahrd Pressure Plate' then
		return 'Any Pressure Plate'
	elseif item == 'Bird' or item == 'Blue Jay' or item == 'Cardinal' then
		return 'Any Bird'
	elseif item == 'Black Scorpion' or item == 'Scorpion' then
		return 'Any Scorpion'
	elseif item == 'Squirrel' or item == 'Red Squirrel' then
		return 'Any Squirrel'
	elseif item == 'Grubby' or item == 'Sluggy' or item == 'Buggy' then
		return 'Any Bug'
	elseif item == 'Mallard Duck' or item == 'Duck' then
		return 'Any Duck'
	elseif item == 'Sulphur Butterfly' or item == 'Julia Butterfly' or item == 'Monarch Butterfly' or item == 'Purple Emperor Butterfly'
	    or item == 'Red Admiral Butterfly' or item == 'Tree Nymph Butterfly' or item == 'Ulysses Butterfly' or item == 'Zebra Swallowtail Butterfly' then
		return 'Any Butterfly'
	elseif item == 'Firefly' or item == 'Lightning Bug' or item == 'Twinkler' then
		return 'Any Firefly'
	elseif item == 'Snail' or item == 'Glowing Snail' then
		return 'Any Snail'
	elseif item == 'Apple' or item == 'Apricot' or item == 'Banana' or item == 'Blackcurrant' or item == 'Blood Orange' or item == 'Cherry' or item == 'Coconut' 
	    or item == 'Dragon Fruit' or item == 'Elderberry' or item == 'Grapefruit' or item == 'Lemon' or item == 'Mango' or item == 'Peach' 
	    or item == 'Pineapple' or item == 'Plum' or item == 'Rambutan' or item == 'Star Fruit' then
		return 'Any Fruit'
	elseif item == 'Black Dragonfly' or item == 'Blue Dragonfly' or item == 'Green Dragonfly' or item == 'Orange Dragonfly'
	    or item == 'Red Dragonfly' or item == 'Yellow Dragonfly' then
		return 'Any Dragonfly'
	elseif item == 'Turtle' or item == 'Jungle Turtle' then
		return 'Any Turtle'
	elseif item == 'Copper Bar' or item == 'Tin Bar' then
		return 'Any Copper Bar'
	elseif item == 'Gold Ore' or item == 'Platinum Ore' then
		return 'Any Gold Ore'
	elseif item == 'Gold Bar' or item == 'Platinum Bar' then
		return 'Any Gold Bar'
	elseif item == 'Ebonstone Block' or item == 'Crimstone Block' then
		return 'Any Evil Block'
	elseif item == 'Demonite Bar' or item == 'Crimtane Bar' then
		return 'Any Evil Bar'
	elseif item == 'Cobalt Bar' or item == 'Palladium Bar' then
		return 'Any Cobalt Bar'
	elseif item == 'Adamantite Bar' or item == 'Titanium Bar' then
		return 'Any Adamantite Bar'
	elseif item == 'Nightmare Fuel' or item == 'Endothermic Energy' then
		return 'Any Seasonal Energy'
	elseif item == 'Fetid Essence' or item == 'Bloodletting Essence' then
		return 'Any Evil Essence'
	elseif item == 'Shadow Scale' or item == 'Tissue Sample' then
		return 'Any Evil Flesh'
	elseif item == 'Flask of Cursed Flames' or item == 'Flask of Ichor' then
		return 'Any Evil Flask'
	elseif item == 'Unholy Water' or item == 'Blood Water' then
		return 'Any Evil Water'
	elseif item == 'Cursed Flame' or item == 'Ichor' then
		return 'Any Hardmode Evil Component'
	elseif item == 'Snow Block' or item == 'Astral Snow' then
		return 'Any Snow Block'
	elseif item == 'Mythril Anvil' or item == 'Orichalcum Anvil' then
		return 'Any Hardmode Anvil'
	elseif item == 'Adamantite Forge' or item == 'Titanium Forge' then
		return 'Any Hardmode Forge'
	elseif item == 'Ice Block' or item == 'Purple Ice Block' or item == 'Red Ice Block' or item == 'Pink Ice Block' or item == 'Astral Ice' then
		return 'Any Ice Block'
	elseif item == 'Silt Block' or item == 'Slush Block' or item == 'Novae Slag' then
		return 'Any Silt Block'
	elseif item == 'Solar Flare Pickaxe' or item == 'Vortex Pickaxe' or item == 'Nebula Pickaxe' or item == 'Stardust Pickaxe' then
		return 'Lunar Pickaxe'
	elseif item == 'Solar Flare Hamaxe' or item == 'Vortex Hamaxe' or item == 'Nebula Hamaxe' or item == 'Stardust Hamaxe' then
		return 'Lunar Hamaxe'
	elseif item == 'Cooked Fish' or item == 'Cooked Marshmallow' or item == 'Pad Thai' or item == 'Pho' or item == 'Cooked Shrimp' or item == 'Sashimi' or item == 'Bacon' 
	    or item == 'Bowl of Soup' or item == 'Grub Soup' or item == 'Gingerbread Cookie' or item == 'Sugar Cookie' or item == 'Christmas Pudding' or item == 'Pumpkin Pie' 
	    or item == 'Baguette' or item == 'Delicious Meat' or item == 'Hadal Stew' then
		return 'Any Food'
	elseif item == 'Demon Wings' or item == 'Angel Wings' or item == 'Red\'s Wings' or item == 'Butterfly Wings' or item == 'Fairy Wings' or item == 'Harpy Wings'
	    or item == 'Bone Wings' or item == 'Flame Wings' or item == 'Frozen Wings' or item == 'Spectre Wings' or item == 'Steampunk Wings' or item == 'Leaf Wings'
	    or item == 'Bat Wings' or item == 'Bee Wings' or item == 'D-Town\'s Wings' or item == 'Will\'s Wings' or item == 'Crowno\'s Wings' or item == 'Cenx\'s Wings'
	    or item == 'Tattered Fairy Wings' or item == 'Spooky Wings' or item == 'Hoverboard' or item == 'Festive Wings' or item == 'Beetle Wings' or item == 'Fin Wings'
	    or item == 'Fishron Wings' or item == 'Mothron Wings' or item == 'Solar Wings' or item == 'Vortex Booster' or item == 'Nebula Mantle' or item == 'Stardust Wings'
	    or item == 'Yoraiz0r\'s Spell' or item == 'Jim\'s Wings' or item == 'Skiphs\'s Paws' or item == 'Loki\'s Wings' or item == 'Betsy\'s Wings'
	    or item == 'Arkhalis\'s Lightwings' or item == 'Skyline Wings' or item == 'Starlight Wings' or item == 'Aureate Booster' or item == 'Hadal Mantle'
	    or item == 'Exodus Wings' or item == 'Hadarian Wings' or item == 'Tarragon Wings' or item == 'Silva Wings' then
		return 'Any Wings'
	end
end

local normalizeStation = function(station)
	if station == 'Altar' then
		station = 'Demon Altar'
	end
	return station
end

local criStr = function(args)
	local constraints = {}
	-- station = ? and station != ?
	local _station = trim(args['station'] or '')
	local _stationnot = trim(args['stationnot'] or '')
	local str = ''
	if _station ~= '' then
		for _, v in ipairs(explode('/', _station)) do
			if str ~= '' then
				str = str .. ' OR '
			end
			str = str .. "station = " .. enclose(normalizeStation(v))
		end
	end
	if _stationnot ~= '' then
		if str ~= '' then
			str = '(' .. str .. ')'
		end
		for _, v in ipairs(explode('/', _stationnot)) do
			if str ~= '' then
				str = str .. ' AND '
			end
			str = str .. 'station <> ' .. enclose(normalizeStation(v))
		end
	end
	constraints['station'] = str
	-- result = ? and result != ?
	local _result = trim(args['result'] or '')
	local _resultnot = trim(args['resultnot'] or '')
	local str = ''
	if _result ~= '' then
		for _, v in ipairs(explode('/', _result)) do
			if str ~= '' then
				str = str .. ' OR '
			end
			if mw.ustring.sub(v, 1, 5) == 'LIKE ' then
				str = str .. "result LIKE " .. enclose(trim(mw.ustring.sub(v, 6)))
			else
				str = str .. 'result=' .. enclose(v)
			end
		end
	end
	if _resultnot ~= '' then
		if str ~= '' then
			str = '(' .. str .. ')'
		end
		for _, v in ipairs(explode('/', _resultnot)) do
			if str ~= '' then
				str = str .. ' AND '
			end
			if mw.ustring.sub(v, 1, 5) == 'LIKE ' then
				str = str .. "result NOT LIKE " .. enclose(trim(mw.ustring.sub(v, 6)))
			else
				str = str .. 'result <> ' .. enclose(v)
			end
		end
	end
	if str ~= '' then
		constraints['result'] = str
	end
	-- ingredient = ?
	local _ingredient = trim(args['ingredient'] or '')
	if _ingredient ~= '' then
		local str = ''
		for _, v in ipairs(explode('/', _ingredient)) do
			if str ~= '' then
				str = str .. ' OR '
			end
			if mw.ustring.sub(v, 1, 1) == '#' then
				str = str .. "ingredients HOLDS LIKE '%‡" .. escape(mw.ustring.sub(v, 2)) .. "‡%'"
			elseif mw.ustring.sub(v, 1, 5) == 'LIKE ' then
				str = str .. "ingredients HOLDS LIKE '%‡" .. escape(trim(mw.ustring.sub(v, 6))) .. "‡%'"
			else
				str = str .. "ingredients HOLDS LIKE '%‡" .. escape(v) .. "‡%'"
				-- any xxx
				local group = getItemGroupName(v)
				if group then
					str = str .. " OR ingredients HOLDS LIKE '%‡" .. escape(group) .. "‡%'"
				end
			end
		end
		constraints['ingredient'] = str
	end

	local where = ''
	if constraints['station'] then
		where = constraints['station']
	end
	if constraints['result'] then
		if where ~= '' then
			where = where .. ' AND '
		end
		where = where .. '(' .. constraints['result'] .. ')'
	end
	if constraints['ingredient'] then
		if where ~= '' then
			where = where .. ' AND '
		end
		where = where .. '(' .. constraints['ingredient'] .. ')'
	end
	-- query historical recipes?
	if trim(args['historical'] or '') == 'only' then
		where = where .. ' AND historical IS NOT NULL'
	elseif trim(args['historical'] or '') ~= 'include' then
		where = where .. ' AND historical IS NULL'
	end
	return where
end

local resultCell = function(row, needLink, template)
	local result, resultimage, resulttext, amount, historical = row['result'], row['resultimage'], row['resulttext'], row['amount'], row['historical']
	local str = ''
	local args = {anchor = resultanchor, nolink = not needLink, class='multi-line'}
	if resultimage then
		args['image'] = resultimage
	end
	if resulttext then
		args[2] = currentFrame:expandTemplate{ title = 'tr', args = {resulttext, lang=lang} }
	end
	str = str .. itemLink(result, args)
	if amount ~= '1' then
		str = str .. ' <span class="note-text">('..amount..')</span>'
	end
	if historical == "1" then
		str = str .. l10n('historical_recipe_note')
	end
	if template then
		local template_str = currentFrame:expandTemplate{ title = template, args = {
				link = needLink,
				resultimage=resultimage, resulttext=resulttext,
				result=result, amount=amount,
		} }
		str = template_str:gsub('@@@@', str)
	end
	return str
end

local ingredientsCell = function(args)
	local str = '<ul>'
	for _, v in ipairs(explode('^', args)) do
		str = str .. '<li>'
		local item, amount = v:match('^(.-)‡(.-)$')
		local s
		if item ~= nil then
			for _, itemname in ipairs(split(item)) do
				local actualitemname, itemtext
				if string.match(itemname, '¦') ~= nil then
					actualitemname, itemtext = itemname:match('^(.-)¦(.-)$')
				end
				if s then
					s = s .. l10n('ingredients_sep')
				end
				if itemtext then
					local itemlinkoptions = {}
					itemlinkoptions[2] = currentFrame:expandTemplate{ title = 'tr', args = {itemtext, lang=lang} }
					s = (s or '') .. itemLink(actualitemname, itemlinkoptions)
				else
					s = (s or '') .. itemLink(itemname)
				end
			end
		else
			s = l10n('no_ingredients')
		end
		str = str .. s
		if amount ~= nil then
			if amount ~= '1' then
				str = str .. ' <span class="note-text">('..amount..')</span>'
			end
		end
		str = str .. '</li>'
	end
	str = str .. '</ul>'
	return str
end

local stationCell = function(station, options)
	options = options or {wrap = 'y'}
	if station == 'By Hand' then
		return l10n('station_by_hand')
	elseif station == 'Furnace' or station == 'Work Bench' or station == 'Sawmill' or station == "Tinkerer's Workshop" or station == 'Dye Vat'
	  or station == 'Loom' or station == 'Keg' or station == 'Hellforge' or station == 'Bookcase' or station == 'Imbuing Station' or station == 'Lava'
	  or station == 'Honey' or station == 'Glass Kiln' or station == 'Flesh Cloning Vat' or station == 'Autohammer' or station == 'Crystal Ball'
	  or station == 'Ice Machine' or station == 'Meat Grinder' or station == 'Living Loom' or station == 'Heavy Work Bench' or station == 'Sky Mill'
	  or station == 'Solidifier' or station == 'Honey Dispenser' or station == 'Bone Welder' or station == 'Blend-O-Matic' or station == 'Steampunk Boiler'
	  or station == 'Ancient Manipulator' or station == 'Lihzahrd Furnace' or station == 'Ancient Altar' or station == 'Ashen Altar' or station == 'Botanic Planter'
	  or station == "Draedon's Forge" or station == 'Effulgent Manipulator' or station == 'Eutrophic Shelf' or station == 'Monolith Amalgam' or station == 'Plague Infuser'
	  or station == 'Profaned Crucible' or station == 'Static Refiner' or station == 'Void Condenser' or station == 'Particle Accelerator' then
		return itemLink(station, options)
	elseif station == 'Iron Anvil' then
		return itemLink('Iron Anvil', options) .. l10n('station_sep_or') .. itemLink('Lead Anvil', options)
	elseif station == 'Adamantite Forge' then
		return itemLink('Adamantite Forge', options) .. l10n('station_sep_or') .. itemLink('Titanium Forge', options)
	elseif station == 'Mythril Anvil' then
		return itemLink('Mythril Anvil', options) .. l10n('station_sep_or') .. itemLink('Orichalcum Anvil', options)
	elseif station == 'Demon Altar' then
		return itemLink('Demon Altar', options) .. l10n('station_sep_or') .. itemLink('Crimson Altar', options)
	elseif station == 'Cooking Pot' then
		return itemLink('Cooking Pot', options) .. l10n('station_sep_or') .. itemLink('Cauldron', options)
	elseif station == 'Placed Bottle' then
		return itemLink('Placed Bottle', options) .. l10n('station_sep_or') .. itemLink('Alchemy Table', options)
	elseif station == 'Water' then
		return itemLink('Water', options) .. l10n('station_sep_or') .. itemLink('Sink', options)
	elseif station == 'Table and Chair' then
		return itemLink('Table', options) .. l10n('station_sep_and') .. itemLink('Chair', options)
	elseif station == 'Work Bench and Chair' then
		return itemLink('Work Bench', options) .. l10n('station_sep_and') .. itemLink('Chair', options)
	elseif station == 'Crystal Ball and Lava' then
		return itemLink('Crystal Ball', options) .. l10n('station_sep_and') .. itemLink('Lava', options)
	elseif station == 'Crystal Ball and Honey' then
		return itemLink('Crystal Ball', options) .. l10n('station_sep_and') .. itemLink('Honey', options)
	elseif station == 'Crystal Ball and Water' then
		return itemLink('Crystal Ball', options) .. l10n('station_sep_and').. '<span class="water">' .. itemLink('Water', options) .. l10n('station_sep_or') .. itemLink('Sink', options) .. '</span>'
	elseif station == 'Sky Mill and Water' then
		return itemLink('Sky Mill', options) .. l10n('station_sep_and').. '<span  class="water">' .. itemLink('Water', options) .. l10n('station_sep_or') .. itemLink('Sink', options) .. '</span>'
	elseif station == 'Sky Mill and Snow Biome' then
		return itemLink('Sky Mill', options) .. l10n('station_sep_and').. l10n('snow_biome')
	elseif station == 'Placed Bottle only' then
		return itemLink('Placed Bottle', options)
	elseif station == 'Alchemy Table only' then
		return itemLink('Alchemy Table', options)
	elseif station == 'Profaned Crucible and Lava' then
		return itemLink('Profaned Crucible', options) .. l10n('station_sep_and') .. itemLink('Lava', options)
	else
		return station
	end
end
-- for extract.
local compactStation = function(station)
	if station == 'By Hand' then
		return '(' .. l10n('station_by_hand') .. ')'
	elseif station == 'Furnace' or station == 'Work Bench' or station == 'Sawmill' or station == "Tinkerer's Workshop" or station == 'Dye Vat'
	  or station == 'Loom' or station == 'Keg' or station == 'Hellforge' or station == 'Bookcase' or station == 'Imbuing Station' or station == 'Lava'
	  or station == 'Honey' or station == 'Glass Kiln' or station == 'Flesh Cloning Vat' or station == 'Autohammer' or station == 'Crystal Ball'
	  or station == 'Ice Machine' or station == 'Meat Grinder' or station == 'Living Loom' or station == 'Heavy Work Bench' or station == 'Sky Mill'
	  or station == 'Solidifier' or station == 'Honey Dispenser' or station == 'Bone Welder' or station == 'Blend-O-Matic' or station == 'Steampunk Boiler'
	  or station == 'Ancient Manipulator' or station == 'Lihzahrd Furnace' or station == 'Ancient Altar' or station == 'Ashen Altar' or station == 'Botanic Planter'
	  or station == "Draedon's Forge" or station == 'Effulgent Manipulator' or station == 'Eutrophic Shelf' or station == 'Monolith Amalgam' or station == 'Plague Infuser'
	  or station == 'Profaned Crucible' or station == 'Static Refiner' or station == 'Void Condenser' then
		return l10n('compact_before') .. itemLink(station, {mode = 'image'})
	elseif station == 'Iron Anvil' then
		return l10n('compact_before') .. itemLink('Iron Anvil', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Lead Anvil', {mode = 'image'})
	elseif station == 'Adamantite Forge' then
		return l10n('compact_before') .. itemLink('Adamantite Forge', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Titanium Forge', {mode = 'image'})
	elseif station == 'Mythril Anvil' then
		return l10n('compact_before') .. itemLink('Mythril Anvil', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Orichalcum Anvil', {mode = 'image'})
	elseif station == 'Demon Altar' then
		return l10n('compact_before') .. itemLink('Demon Altar', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Crimson Altar', {mode = 'image'})
	elseif station == 'Cooking Pot' then
		return l10n('compact_before') .. itemLink('Cooking Pot', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Cauldron', {mode = 'image'})
	elseif station == 'Placed Bottle' then
		return l10n('compact_before') .. itemLink('Placed Bottle', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Alchemy Table', {mode = 'image'})
	elseif station == 'Water' then
		return l10n('compact_before') .. itemLink('Water', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Sink', {mode = 'image'})
	elseif station == 'Table and Chair' then
		return l10n('compact_before') .. itemLink('Table', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Chair', {mode = 'image'})
	elseif station == 'Work Bench and Chair' then
		return l10n('compact_before') .. itemLink('Work Bench', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Chair', {mode = 'image'})
	elseif station == 'Crystal Ball and Lava' then
		return l10n('compact_before') .. itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Lava', {mode = 'image'})
	elseif station == 'Crystal Ball and Honey' then
		return l10n('compact_before') .. itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Honey', {mode = 'image'})
	elseif station == 'Crystal Ball and Water' then
		return l10n('compact_before') .. itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. itemLink('Water', {mode = 'image'}) .. " / " ..
		itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Sink', {mode = 'image'})
	elseif station == 'Sky Mill and Water' then
		return l10n('compact_before') .. itemLink('Sky Mill', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. itemLink('Water', {mode = 'image'}) .. " / " .. 
		itemLink('Sky Mill', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;"..  itemLink('Sink', {mode = 'image'})
	elseif station == 'Sky Mill and Snow Biome' then
		return l10n('compact_before') .. itemLink('Sky Mill', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. l10n('compact_snow_biome')
	elseif station == 'Placed Bottle only' then
		return l10n('compact_before') .. itemLink('Placed Bottle', {mode = 'image'})
	elseif station == 'Alchemy Table only' then
		return l10n('compact_before') .. itemLink('Alchemy Table', {mode = 'image'})
	elseif station == 'Profaned Crucible and Lava' then
		return l10n('compact_before') .. itemLink('Profaned Crucible', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. itemLink('Lava', {mode = 'image'})
	else
		return l10n('compact_before') .. station
	end
end

local getFlags = function(args)
	local needCate = 1
	local needLink = true
	local _cate = trim(args['cate'] or '')
	if _cate == 'force' or _cate == 'all' then
		needCate = 2
	elseif _cate == 'n' or _cate == 'no' then
		needCate = nil
	end
	local _link = trim(args['link'] or '')
	if _link == 'y' or _link == 'yes' or _link == 'force' then
		needLink = true
	elseif _link == 'n' or _link == 'no' then
		needLink = false
	end
	return needCate, needLink
end

local addCate, cateStr -- for table body. init in p.query

local tableStart = function(title, withStation)
	local header_
	local str = '<div class="crafts '.. (getArg('class') or '')
	local _id = (getArg('id') or '')
	if _id ~= '' then
		str = str .. '" id="'.. _id
	end
	local _css = (getArg('css') or getArg('style') or '')
	if _css ~= '' then
		str = str .. '" style="'.. _css
	end
	str = str .. '"><div class="wrap"><table '
	if (getArg('sortable') or 'y'):sub(1,1) ~= 'n' then
		str = str .. 'class="sortable" '
	end
	str = str .. 'cellpadding="0" cellspacing="0">'
	if title ~= '' then
		str = str .. '<caption>' .. title .. '</caption>'
	end

	local _i, _field
	str = str .. '<tr>'
	_i = 1
	_field = 'col-A-1'
	while getArg(_field) do
		if not extCols_A then
			extCols_A = {}
		end
		table.insert(extCols_A, _field)
		str = str .. '<th>'.. getArg(_field) ..'</th>'
		_i = _i + 1
		_field = 'col-A-' .. _i
	end
	str = str .. '<th class="result">' .. (getArg('header-result') or l10n('header_result')) .. '</th>'
	_i = 1
	_field = 'col-B-1'
	while getArg(_field) do
		if not extCols_B then
			extCols_B = {}
		end
		table.insert(extCols_B, _field)
		str = str .. '<th>'.. getArg(_field) ..'</th>'
		_i = _i + 1
		_field = 'col-B-' .. _i
	end
	str = str .. '<th class="ingredients">' .. (getArg('header-ingredients') or l10n('header_ingredients')) .. '</th>'
	_i = 1
	_field = 'col-C-1'
	while getArg(_field) do
		if not extCols_C then
			extCols_C = {}
		end
		table.insert(extCols_C, _field)
		str = str .. '<th>'.. getArg(_field) ..'</th>'
		_i = _i + 1
		_field = 'col-C-' .. _i
	end
	if withStation then
		_i = 1
		_field = 'station-col-before-1'
		while getArg(_field) do
			if not extCols_stationBefore then
				extCols_stationBefore = {}
			end
			table.insert(extCols_stationBefore, _field)
			str = str .. '<th class="station">'.. getArg(_field) ..'</th>'
			_i = _i + 1
			_field = 'station-col-before-' .. _i
		end
		str = str .. '<th class="station">' .. (getArg('header-station') or l10n('header_station')) .. '</th>'
		_i = 1
		_field = 'station-col-after-1'
		while getArg(_field) do
			if not extCols_stationAfter then
				extCols_stationAfter = {}
			end
			table.insert(extCols_stationAfter, _field)
			str = str .. '<th class="station">'.. getArg(_field) ..'</th>'
			_i = _i + 1
			_field = 'station-col-after-' .. _i
		end
	end
	_i = 1
	_field = 'col-D-1'
	while getArg(_field) do
		if not extCols_D then
			extCols_D = {}
		end
		table.insert(extCols_D, _field)
		str = str .. '<th>'.. getArg(_field) ..'</th>'
		_i = _i + 1
		_field = 'col-D-' .. _i
	end
	str = str .. '</tr>'
	return str
end

local tableEnd = function(rows_count, expectedrows)
	local str = '</table><div style="display: none">total: '..rows_count..' row(s)</div></div></div>'
	if expectedrows and rows_count ~= expectedrows then
		str = str .. '[[Category:'.. l10n('cate_unexpected_rows_count') .. ']]'
	end
	if not expectedrows and rows_count == 0 then
		str = str .. '[[Category:'.. l10n('cate_no_row') .. ']]'
	end
	return str
end

local tableRow = function(str, row, current_station, station_count, rows_count, withStation, needCate, needLink, needGroup, current_result, result_count, current_result_ext, result_ext_count, template, stationGroup)
	local str_w = '' -- before result col
	local str_x = '' -- between result and ingredients cols
	local str_y = '' -- between ingredients and station cols
	local str_z = '' -- after station
	local str_resultCell = ''

	local result_index = getArg('result-index-#'..rows_count) or getArg('result-index-'..row['result'])

	str = str .. '<tr data-rowid="'..tostring(rows_count)..'">'

	if needGroup then
		local result = row['result']..'|'..row['resultimage']..'|'..row['resulttext']..'|'..row['amount']
		-- grouping result col
		if current_result == result then -- is same group ??
			result_count = result_count + 1
		else
			--new group:
			-- rowspan value for prev group, if needed.
			if result_count then
				str = str:gsub("yyyrowspanyyy", tostring(result_count))
			end
			-- begin this group
			current_result = result
			result_count = 1
			str_resultCell = '<td class="result" rowspan="yyyrowspanyyy">'.. resultCell(row, needLink, template).. '</td>'
		end
		-- grouping ext cols
		if result_index and (current_result_ext == result_index) then -- is same group ??
			result_ext_count = result_ext_count + 1
		else
			--new group:
			-- rowspan value for prev group, if needed.
			if result_ext_count then
				str = str:gsub("zzzrowspanzzz", tostring(result_ext_count))
			end
			-- begin this group
			current_result_ext = result_index
			result_ext_count = 1
			if extCols_A then
				for _, v in ipairs(extCols_A) do
					if result_index then
						str_w = str_w .. '<td class="'..v..'" rowspan="zzzrowspanzzz">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
					else
						str_w = str_w .. '<td class="'..v..'" rowspan="zzzrowspanzzz"></td>'
					end
				end
			end
			if extCols_B then
				for _, v in ipairs(extCols_B) do
					if result_index then
						str_x = str_x .. '<td class="'..v..'" rowspan="zzzrowspanzzz">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
					else
						str_x = str_x .. '<td class="'..v..'" rowspan="zzzrowspanzzz"></td>'
					end
				end
			end
			if extCols_C then
				for _, v in ipairs(extCols_C) do
					if result_index then
						str_y = str_y .. '<td class="'..v..'" rowspan="zzzrowspanzzz">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
					else
						str_y = str_y .. '<td class="'..v..'" rowspan="zzzrowspanzzz"></td>'
					end
				end
			end
			if extCols_D then
				for _, v in ipairs(extCols_D) do
					if result_index then
						str_z = str_z .. '<td class="'..v..'" rowspan="zzzrowspanzzz">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
					else
						str_z = str_z .. '<td class="'..v..'" rowspan="zzzrowspanzzz"></td>'
					end
				end
			end
		end
	else
		if extCols_A then
			for _, v in ipairs(extCols_A) do
				if result_index then
					str_w = str_w .. '<td class="'..v..'">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
				else
					str_w = str_w .. '<td class="'..v..'"></td>'
				end
			end
		end
		if extCols_B then
			for _, v in ipairs(extCols_B) do
				if result_index then
					str_x = str_x .. '<td class="'..v..'">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
				else
					str_x = str_x .. '<td class="'..v..'"></td>'
				end
			end
		end
		if extCols_C then
			for _, v in ipairs(extCols_C) do
				if result_index then
					str_y = str_y .. '<td class="'..v..'">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
				else
					str_y = str_y .. '<td class="'..v..'"></td>'
				end
			end
		end
		if extCols_D then
			for _, v in ipairs(extCols_D) do
				if result_index then
					str_z = str_z .. '<td class="'..v..'">' .. (getArg(result_index .. '-row-' .. v) or '') .. '</td>'
				else
					str_z = str_z .. '<td class="'..v..'"></td>'
				end
			end
		end
		str_resultCell = '<td class="result">'.. resultCell(row, needLink, template).. '</td>'
	end

	str = str .. str_w .. str_resultCell .. str_x .. '<td class="ingredients">' .. ingredientsCell(row['args']).. '</td>' .. str_y

	if withStation then
		local station = row['station']
		if stationGroup then
			if current_station == station then -- is same group ??
				station_count = station_count + 1
			else
				--new group:
				-- rowspan value for prev group, if needed.
				if station_count then
					str = str:gsub("xxxrowspanxxx", tostring(station_count))
				end
				-- begin this group
				current_station = station
				station_count = 1
				local station_index = getArg('station-index-'..station)
				-- station before:
				if extCols_stationBefore then
					for _, v in ipairs(extCols_stationBefore) do
						if station_index then
							str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx">' .. (getArg(station_index .. '-row-' .. v) or '') .. '</td>'
						else
							str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx"></td>'
						end
					end
				end
				str = str .. '<td class="station" rowspan="xxxrowspanxxx">'.. stationCell(station) ..'</td>'
				-- station after:
				if extCols_stationAfter then
					for _, v in ipairs(extCols_stationAfter) do
						if station_index then
							str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx">' .. (getArg(station_index .. '-row-' .. v) or '') .. '</td>'
						else
							str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx"></td>'
						end
					end
				end
			end
		else
			if current_station == station then -- is same group ??
				station_count = station_count + 1
			else
				current_station = station
				station_count = 1
			end
			local station_index = getArg('station-index-'..station)
			-- station before:
			if extCols_stationBefore then
				for _, v in ipairs(extCols_stationBefore) do
					if station_index then
						str = str .. '<td class="station '..v..'">' .. (getArg(station_index .. '-row-' .. v) or '') .. '</td>'
					else
						str = str .. '<td class="station '..v..'"></td>'
					end
				end
			end
			str = str .. '<td class="station">'.. stationCell(station) ..'</td>'
			-- station after:
			if extCols_stationAfter then
				for _, v in ipairs(extCols_stationAfter) do
					if station_index then
						str = str .. '<td class="station '..v..'">' .. (getArg(station_index .. '-row-' .. v) or '') .. '</td>'
					else
						str = str .. '<td class="station '..v..'"></td>'
					end
				end
			end
		end
	end

	str = str .. str_z ..'</tr>'
	return str, current_station, station_count, current_result, result_count, current_result_ext, result_ext_count
end

local extRows = function(withStation, isTop)
	local prefix
	if isTop then
		prefix = 'topextrow-'
	else
		prefix = 'extrow-'
	end
	local returnstr = ''
	local valid = true
	local p
	local str
	local _i = 1
	local temp
	while valid do
		local i = tostring(_i) .. '-'
		p = prefix .. i
		valid = false
		str = '<tr data-'..prefix..'id="'..tostring(_i)..'">'
		if extCols_A then
			for _, v in ipairs(extCols_A) do
				temp = getArg(p..v)
				if temp then
					valid = true
					str = str .. '<td class="'..v..'">' .. temp .. '</td>'
				else
					str = str .. '<td class="'..v..'"></td>'
				end
			end
		end
		temp = getArg(p..'col-result')
		if temp then
			valid = true
			str = str .. '<td class="result">' .. temp .. '</td>'
		else
			str = str .. '<td class="result"></td>'
		end
		if extCols_B then
			for _, v in ipairs(extCols_B) do
				temp = getArg(p..v)
				if temp then
					valid = true
					str = str .. '<td class="'..v..'">' .. temp .. '</td>'
				else
					str = str .. '<td class="'..v..'"></td>'
				end
			end
		end
		temp = getArg(p..'col-ingredients')
		if temp then
			valid = true
			str = str .. '<td class="ingredients">' .. temp .. '</td>'
		else
			str = str .. '<td class="ingredients"></td>'
		end
		if extCols_C then
			for _, v in ipairs(extCols_C) do
				temp = getArg(p..v)
				if temp then
					valid = true
					str = str .. '<td class="'..v..'">' .. temp .. '</td>'
				else
					str = str .. '<td class="'..v..'"></td>'
				end
			end
		end
		if withStation then
			-- station before:
			if extCols_stationBefore then
				for _, v in ipairs(extCols_stationBefore) do
					temp = getArg(p..v)
					if temp then
						valid = true
						str = str .. '<td class="station '..v..'">' .. temp .. '</td>'
					else
						str = str .. '<td class="station '..v..'"></td>'
					end
				end
			end
			temp = getArg(p..'col-station')
			if temp then
				valid = true
				str = str .. '<td class="station">' .. temp .. '</td>'
			else
				str = str .. '<td class="station"></td>'
			end
			-- station after:
			if extCols_stationAfter then
				for _, v in ipairs(extCols_stationAfter) do
					temp = getArg(p..v)
					if temp then
						valid = true
						str = str .. '<td class="station '..v..'">' .. temp .. '</td>'
					else
						str = str .. '<td class="station '..v..'"></td>'
					end
				end
			end
		end
		if extCols_D then
			for _, v in ipairs(extCols_D) do
				temp = getArg(p..v)
				if temp then
					valid = true
					str = str .. '<td class="'..v..'">' .. temp .. '</td>'
				else
					str = str .. '<td class="'..v..'"></td>'
				end
			end
		end
		str = str .. '</tr>'

		if valid then
			_i = _i + 1
			returnstr = returnstr .. str
		end
	end
	return returnstr
end


local tableBody = function(result, withStation, needGroup, needCate, needLink, rootpagename, title, expectedrows, template, stationGroup)
		local str = tableStart(title, withStation)
		-- top ext rows:
		str = str .. extRows(withStation, true)
		-- main rows:
		local current_station
		local station_count
		local rows_count = 0
		local current_result
		local result_count
		local current_result_ext
		local result_ext_count
		for _, row in ipairs(result) do
			rows_count = rows_count + 1
			-- table row:
			str, current_station, station_count, current_result, result_count, current_result_ext, result_ext_count = tableRow(str, row, current_station, station_count, rows_count, withStation, needCate, needLink, needGroup, current_result, result_count, current_result_ext, result_ext_count, template, stationGroup)
			-- cate:
			if needCate then
				if needCate == 2 or rootpagename == currentFrame:expandTemplate{ title = 'tr', args = {row['result'], lang=lang} } then
					addCate(row['station'])
				end
			end
		end
		-- rowspan value for last station group and result group
		if withStation and station_count and stationGroup then
			str = str:gsub("xxxrowspanxxx", tostring(station_count))
		end
		if needGroup then
			str = str:gsub("yyyrowspanyyy", tostring(result_count))
			str = str:gsub("zzzrowspanzzz", tostring(result_ext_count))
		end
		-- ext rows:
		str = str .. extRows(withStation)
		-- table end
		str = str .. tableEnd(rows_count, expectedrows)

		-- cate
		if needCate then
			str = str .. cateStr()
		end

		return str
end


local infoboxesBody = function(result, needCate, needLink, rootpagename, title, expectedrows)
		-- main rows:
		local current_station
		local station_count
		local rows_count = 0
		local current_result
		local result_count
		local str = currentFrame:expandTemplate{ title = 'flexstart' }
		for _, row in ipairs(result) do
			rows_count = rows_count + 1
			-- table row:
			str = str .. '<table style="width:239px; background-color: #515151; border: 1px solid #aaaaaa; -moz-border-radius: .7em; -webkit-border-radius: .7em; padding: 0.2em; margin-bottom:5px; margin-right:5px;">'

			str = str .. '<th colspan="3" style="background-color: #373d40; color: #ffffff; font-size:110%; padding:0.2em;">Crafting Station</th>'

			str = str .. '<tr align="center"><td colspan="3">'.. stationCell(row['station']) .. '</td></tr>'

			str = str .. '<th colspan="2" style="background-color: #373d40; color: #ffffff; width:65%; padding:0.2em;">\'\'\'Ingredient(s)\'\'\'</th>'
			str = str .. '<th align="center" style="background-color: #373d40; color: #ffffff; width:35%; padding:0.2em;">\'\'\'Amount\'\'\'</th>'

			for _, v in ipairs(explode('^', row['args'])) do
				local item, amount = v:match('^(.-)‡(.-)$')
				local s = ''
				local needamount = true
				if item ~= nil then
					for _, itemname in ipairs(split(item)) do
						if s ~= '' then
							if amount ~= nil then
								s = s .. '<td rowspan="3" align="center">' .. amount .. '</td>'
								needamount = false
							end
							s = s .. '</tr><tr><td colspan="2" align="center">' .. l10n('ingredients_sep') .. '</td></tr><tr><td>' .. itemLink(itemname, { mode = 'htmltable', class = 'break' } ) .. '</td>'
						else
							s = '<tr><td>' .. itemLink(itemname, { mode = 'htmltable', class = 'break' } ) .. '</td>'
						end
					end
				else
					s = '<tr><td colspan="3" align="center">' .. l10n('no_ingredients') .. '</td></tr>'
				end
				str = str .. s
				if amount ~= nil and needamount == true then
					str = str .. '<td align="center">' .. amount .. '</td>'
				end
				str = str .. '</tr>'
			end

			str = str .. '<th colspan="3" style="background-color: #373d40; color: #ffffff; padding:0.2em;">\'\'\'Result\'\'\'</th>'

			str = str .. '<tr>'

			local args = {mode = 'htmltable', anchor = resultanchor, nolink = not needLink, class='multi-line break'}
			if row['resultimage'] then
				args['image'] = row['resultimage']
			end
			if row['resulttext'] then
				args[2] = currentFrame:expandTemplate{ title = 'tr', args = {row['resulttext'], lang=lang} }
			end
			str = str .. '<td>' .. itemLink(row['result'], args)
			
			if row['historical'] == "1" then
				str = str .. l10n('historical_recipe_note')
			end
			
			str = str  .. '</td><td align="center">' .. row['amount'] .. '</td>'

			str = str .. '</tr>'

			str = str .. '</table>'
			-- cate:
			if needCate then
				if needCate == 2 or rootpagename == currentFrame:expandTemplate{ title = 'tr', args = {row['result'], lang=lang} } then
					addCate(row['station'])
				end
			end
		end

		str = str .. currentFrame:expandTemplate{ title = 'flexend' }

		-- cate
		if expectedrows and rows_count ~= expectedrows then
			str = str .. '[[Category:'.. l10n('cate_unexpected_rows_count') .. ']]'
		end
		if not expectedrows and rows_count == 0 then
			return '<span style="color:red;font-weight:bold;">Recipes: No result</span>[[Category:'.. l10n('cate_no_row') .. ']]'
		end
		str = str .. cateStr()

		return str
end
-----------------------------------------------------------------

local p = {}

-- for {{recipes/register}}
p.register = function(frame)
	local args = frame:getParent().args
	local historical

	if (trim(args['historical'] or 'n')):sub(1,1) == 'y' then
		historical = true
	end

	-- {{{ingredients}}}
	local ingredients = {} -- list of {index, itemname, amount}
	for k, v in pairs(args) do
		if(type(k) == 'number') then
			if k % 2 == 1 then  -- 2n-1, nth item
				local index, item, amount = (k+1)/2, trim(v), trim(args[k+1])
				ingredients[index] = {item, amount}
			end
		end
	end

	local serialized = '' -- serialized ingredients list
	for _, v in ipairs(ingredients) do
		serialized = serialized .. '^' .. v[1] .. '‡' .. v[2]
	end
	serialized = mw.ustring.sub(serialized, 2)

	table.sort(ingredients, function(a , b) return a[1] < b[1] end) -- sort by ingredient item name
	local ingredients_string = ''
	local ingredients_string_full = ''
	for _, v in ipairs(ingredients) do
		local name, amount = unpack(v)
		local ingstr = normalize(name)
		ingredients_string = ingredients_string .. '^' .. ingstr
		ingredients_string_full = ingredients_string_full .. '^' .. ingstr .. amount
	end

	--store
	frame:callParserFunction('#cargo_store:_table=Recipes',{
		result = trim(args['result'] or ''),
		resultimage = trim(args['image'] or ''),
		resulttext = trim(args['text'] or ''),
		amount = trim(args['amount'] or ''),
		historical = historical,
		station = normalizeStation(trim(args['station'] or '')),
		ingredients = mw.ustring.sub(ingredients_string, 2),
		ings = mw.ustring.sub(ingredients_string_full, 2),
		args = serialized,
	})
end -- p.register

-- for {{recipes}}
p.query = function(frame)
	currentFrame = frame -- global frame cache
	local args = frame:getParent().args
	inputArgs = args

	lang = frame.args['lang'] or 'en'
	l10n_table = l10n_info[lang] or l10n_info['en']

	resultanchor = trim(args['resultanchor'] or '')

	addCate, cateStr = (function()
		local cate = l10n('station_cate')
		local cateCache = {}
		local addCate = function(station)
			cateCache[station] = true
		end
		local cateStr = function()
			local str = ''
			for station, _ in pairs(cateCache) do
				if station == 'By Hand' then
					str = str .. '[[Category:'.. l10n('station_by_hand_cate').. ']]'
				else
					str = str .. '[[Category:'.. l10n('station_cate_start_str')..(cate[station] or frame:expandTemplate{ title = 'tr', args = {station, lang=lang, link='y'}})..']]'
				end
			end
			if str ~= '' then
				str = str .. '[[Category:'.. l10n('cate_craftable').. ']]'
			end
			return str
		end
		return addCate, cateStr
	end)()

	local where = trim(args['where'] or '')
	if where == '' then
		where = criStr(args)
	end

	-- no constraint no result.
	if where == '' then
		return '<span style="color:red;font-weight:bold;">Recipes: No constraint</span>'
	end

	-- format:
	local needCate, needLink = getFlags(args)
	local needGroup = true
	if (getArg('grouping') or 'y'):sub(1,1) == 'n' then
		needGroup = false
	end
	local _title = trim(args['title'] or '')
	local _expectedrows = trim(args['expectedrows'] or '')
	if _expectedrows ~= '' then
		_expectedrows = tonumber(_expectedrows)
	else
		_expectedrows = nil
	end
	local rootpagename = mw.title.getCurrentTitle().rootText

	if getArg('view') == 'infobox' then
		local result = mw.ext.cargo.query('Recipes', 'result, resultimage, resulttext, amount, historical, station, args', {
			where = where,
			groupBy = "result, amount, ings",
			orderBy = "result, amount DESC", -- Don't order by station
			limit = 2000,
		})
		return infoboxesBody(result, needCate, needLink, rootpagename, _title, _expectedrows)
	else
		if trim(args['nostation'] or '') ~= '' then
			-- no station
			-- query, still need contain station field for cate.
			local result = mw.ext.cargo.query('Recipes', 'result, resultimage, resulttext, amount, historical, station, args', {
				where = where,
				groupBy = "result, amount, ings",
				orderBy = "result, amount DESC", -- Don't order by station
				limit = 2000,
			})
			return tableBody(result, false, needGroup, needCate, needLink, rootpagename, _title, _expectedrows, getArg('resulttemplate'), false)
		else
			-- with station
			local stationGroup = true
			if (getArg('stationgrouping') or 'y'):sub(1,1) == 'n' then
				stationGroup = false
			end
			-- query
			local result = mw.ext.cargo.query('Recipes', 'result, resultimage, resulttext, amount, historical, station, args', {
				where = where,
				groupBy = "result, amount, ings",
				orderBy = "station, result, amount DESC, ings", -- order by station first for station grouping.
				limit = 2000,
			})
			return tableBody(result, true, needGroup, needCate, needLink, rootpagename, _title, _expectedrows, getArg('resulttemplate'), stationGroup)
		end
	end
end -- p.query

-- for {{recipes/extract}}
p.extract = function(frame)
	currentFrame = frame -- global frame cache

	local args = frame:getParent().args
	inputArgs = args

	lang = frame.args['lang'] or 'en'
	l10n_table = l10n_info[lang] or l10n_info['en']

	local where = trim(args['where'] or '')
	if where == '' then
		where = criStr(args)
	end

	-- no constraint no result.
	if where == '' then
		return '<span style="color:red;font-weight:bold;">Recipes/extract: No constraint</span>'
	end

	-- query:
	local result = mw.ext.cargo.query('Recipes', 'result, resultimage, resulttext, amount, historical, station, args', {
		where = where,
		groupBy = "result, amount, ings",
		orderBy = "result, amount DESC", -- Don't order by station
		limit = 20, -- enough.
	})

	-- output
	local mode = getArg('mode')
	local sep = getArg('sep') or getArg('seperator')
	if not mode or mode =='compact' or mode == '' then
		--default mode = compact
		local sep = sep or l10n('default_sep_compact')
		local withResult = getArg('withresult')
		local withStation = not getArg('nostation')
		local str = nil
		for _, row in ipairs(result) do
			if str then
				str = str .. sep
			else
				str = ''
			end
			str = str .. '<span class="recipe compact">'
			local ingFlag = nil
			for _, v in ipairs(explode('^', row['args'])) do
				if ingFlag then
					str = str .. ' + '
				else
					ingFlag = true
				end
				local item, amount = v:match('^(.-)‡(.-)$')
				if amount ~= '1' then
					str = str .. amount .. ' '
				end
				local s
				for _, itemname in ipairs(split(item)) do
					if s then
						s = s .. "&thinsp;/&thinsp;" .. itemLink(itemname, {mode='image'})
					else
						s = itemLink(itemname, {mode='image'})
					end
				end
				str = str .. s
			end
			if withResult then
				str = str .. ' = '
				if row['amount'] ~= '1' then
					str = str .. row['amount'] .. ' '
				end
				local args = {mode='image'}
				if row['resultimage'] then
					args['image'] = row['resultimage']
				end
				str = str .. itemLink(row['result'], args)
			end
			if withStation then
				str = str .. compactStation(row['station'])
			end
			str = str..'</span>'
		end
		return str
	elseif mode == 'ingredients' then
		local sep = sep or l10n('default_sep_ingredients') 
		local str = nil
		for _, row in ipairs(result) do
			if str then
				str = str .. sep
			else
				str = ''
			end
			str = str .. ingredientsCell(row['args'])
		end
		return '<div class="crafting-ingredients">'..str..'</div>'
	elseif mode == 'station' then
		-- only return first row.
		for _, row in ipairs(result) do
			return stationCell(row['station'], {})
		end
	elseif mode == 'stationraw' then
		-- only return first row.
		for _, row in ipairs(result) do
			return row['station']
		end
	elseif mode == 'result' then
		-- only return first row.
		local needCate, needLink = getFlags(args)
		for _, row in ipairs(result) do
			return resultCell(row, needLink, getArg('resulttemplate'))
		end
	elseif mode == 'amount' then
		-- only return first row.
		for _, row in ipairs(result) do
			return row['amount']
		end
	elseif mode == 'ingredients-buy' then -- doesn't work now, left for possible future expansion
		-- only process first row.
		for _, row in ipairs(result) do
			local value = 0
			for _, v in ipairs(explode('^', row['args'])) do
				local item, amount = v:match('^(.-)‡(.-)$')
				value = value + require('Module:Iteminfo').getItemStat( tonumber(currentFrame:expandTemplate{ title = 'itemIdFromName', args = {item, lang='en'} }) or 0, 'value' ) * amount
			end
			return value
		end
	elseif mode == 'ingredients-sell' then -- doesn't work now, left for possible future expansion
		-- only process first row.
		for _, row in ipairs(result) do
			local value = 0
			for _, v in ipairs(explode('^', row['args'])) do
				local item, amount = v:match('^(.-)‡(.-)$')
				value = value + math.floor(require('Module:Iteminfo').getItemStat( tonumber(currentFrame:expandTemplate{ title = 'itemIdFromName', args = {item, lang='en'} }) or 0, 'value' )/5) * amount
			end
			return value
		end
	else
		return '<span style="color:red;font-weight:bold;">Recipes/extract: Invalid mode</span>'
	end
end -- p.extract

-- count
p.count = function(frame)
	local args = frame:getParent().args
	local where = trim(args['where'] or '')
	if where == '' then
		where = criStr(args)
	end
	-- no constraint no result.
	if where == '' then
		return 
	end
	-- query: since we must use group by to eliminate duplicates, so we can not use COUNT() to get row count directly.
	local result = mw.ext.cargo.query('Recipes', 'result, resultimage, resulttext, amount, station, args', {
		where = where,
		groupBy = "result, amount, ings",
		limit = 2000,
	})
	-- count
	local count = 0
	for _, row in ipairs(result) do
		count = count + 1
	end
	return count
end -- p.count

-- return "yes" or "" 
p.exist = function(frame)
	local args = frame:getParent().args
	local where = trim(args['where'] or '')
	if where == '' then
		where = criStr(args)
	end
	-- no constraint no result.
	if where == '' then
		return 
	end
	-- query:
	local result = mw.ext.cargo.query('Recipes', 'result', {
		where = where,
		limit = 1, -- enough.
	})
	-- output
	for _, row in ipairs(result) do
		return 'yes'
	end
end -- p.exist

return p
Advertisement