Module:Recipes

--- 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 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 args = args or {}		local key = name.."|"		for k, v in pairs(args) do			key = key..k..'='..tostring(v)..'|'		end		if cache[key] then			return cache[key]		else			args[1] = name			args[2] = currentFrame:expandTemplate{ title = 'tr', args = {name, lang=lang} }			args['small'] = 'y'			args['lang'] = lang or 'en'			args['nolink'] = args['nolink'] and 'y' or nil			cache[key] = item_link(currentFrame, args)			return cache[key]		end	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("&#39;", "\\'") 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' 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' 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' then return 'Any Firefly' elseif item == 'Snail' or item == 'Glowing Snail' then return 'Any Snail' elseif item == 'Apple' 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 == 'Grapes' 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' end end

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

local normalizeVersion = function(_version) local _version = trim(_version):lower local version = '' if _version:find('desktop', 1, true) then version = version .. ' desktop' end if _version:find('console', 1, true) then version = version .. ' console' end if _version:find('old-gen', 1, true) then version = version .. ' old-gen' end if _version:find('japan', 1, true) then version = version .. ' japan' end if _version:find('mobile', 1, true) then version = version .. ' mobile' end if _version:find('3ds', 1, true) then version = version .. ' 3ds' end if version == ' desktop console old-gen mobile 3ds' then version = '' end return trim(version) 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

--versions local _version = normalizeVersion(args['version'] or args['versions'] or '') if _version ~= '' then constraints['version'] = 'version = '..enclose(_version) 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 if constraints['version'] then if where ~= '' then where = where .. ' AND ' end where = where .. '(' .. constraints['version'] .. ')' end return where end

local resultCell = function(row, showResultId, needLink, noVersion, template) local result, resultid, resultimage, resulttext, amount, version = row['result'], row['resultid'], row['resultimage'], row['resulttext'], row['amount'], row['version'] local str = '' local args = {anchor = 'y', nolink = not needLink, class='multi-line'} if showResultId then args['id'] = resultid end if resultimage then args['image'] = resultimage end if resulttext then args[2] = resulttext end if version ~= '' then args['icons'] = 'n'	end str = str .. itemLink(result, args) if amount ~= '1' then str = str .. ' ('..amount..') ' end if not noVersion and version ~= '' then -- is a slow template, so cache its result: local vstr = cache.get(lang..':recipes:versionicons:'..version) -- cache for current lang if not vstr then vstr = ' (' ..currentFrame:expandTemplate{ title = 'version icons', args = {version} }..')' cache.set(lang..':recipes:versionicons:'..version, vstr, 3600*24) -- cache 24hr. end str = str .. vstr end if template then local template_str = currentFrame:expandTemplate{ title = template, args = { link = needLink, showid = showResultId, noversion = noVersion, resultid=resultid, resultimage=resultimage, resulttext=resulttext, result=result, amount=amount, versions=version, } }		str = template_str:gsub('@@@@', str) end return str end

local ingredientsCell = function(args) local str = '' for _, v in ipairs(explode('^', args)) do str = str .. '' local item, amount = v:match('^(.-)¦(.-)$') local s		for _, itemname in ipairs(split(item)) do			if s then s = s .. l10n('ingredients_sep') .. itemLink(itemname) else s = itemLink(itemname) end end str = str .. s		if amount ~= '1' then str = str .. ' ('..amount..') ' end str = str .. '' end str = str .. '' 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 == 'Living Wood' or station == 'Decay Chamber' 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').. ' ' .. itemLink('Water', options) .. l10n('station_sep_or') .. itemLink('Sink', options) .. ' '	elseif station == 'Sky Mill and Water' then return itemLink('Sky Mill', options) .. l10n('station_sep_and').. ' ' .. itemLink('Water', options) .. l10n('station_sep_or') .. itemLink('Sink', options) .. ' '	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 == 'Heavy Work Bench and Ecto Mist' then return itemLink('Heavy Work Bench', options) .. l10n('station_sep_and').. l10n('ecto_mist') elseif station == 'Work Bench and Ecto Mist' then return itemLink('Work Bench', options) .. l10n('station_sep_and').. l10n('ecto_mist') else return station end end -- for extract. local compactStation = function(station) if station == 'By Hand' then return '' 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 == 'Living Wood' or station == 'Decay Chamber' then return l10n('compact_before') .. itemLink(station, {mode = 'image'}) .. l10n('compact_after') elseif station == 'Iron Anvil' then return l10n('compact_before') .. itemLink('Iron Anvil', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Lead Anvil', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Iron Anvil and Ecto Mist' then return l10n('compact_before') .. itemLink('Iron Anvil', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Lead Anvil', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. l10n('compact_ecto_mist') .. l10n('compact_after') elseif station == 'Adamantite Forge' then return l10n('compact_before') .. itemLink('Adamantite Forge', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Titanium Forge', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Mythril Anvil' then return l10n('compact_before') .. itemLink('Mythril Anvil', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Orichalcum Anvil', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Demon Altar' then return l10n('compact_before') .. itemLink('Demon Altar', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Crimson Altar', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Cooking Pot' then return l10n('compact_before') .. itemLink('Cooking Pot', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Cauldron', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Placed Bottle' then return l10n('compact_before') .. itemLink('Placed Bottle', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Alchemy Table', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Water' then return l10n('compact_before') .. itemLink('Water', {mode = 'image'}) .. "&thinsp;/&thinsp;" .. itemLink('Sink', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Table and Chair' then return l10n('compact_before') .. itemLink('Table', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Chair', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Work Bench and Chair' then return l10n('compact_before') .. itemLink('Work Bench', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Chair', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Crystal Ball and Lava' then return l10n('compact_before') .. itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Lava', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Crystal Ball and Honey' then return l10n('compact_before') .. itemLink('Crystal Ball', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;" .. itemLink('Honey', {mode = 'image'}) .. l10n('compact_after') 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'}) .. l10n('compact_after') 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'}) .. l10n('compact_after') elseif station == 'Sky Mill and Snow Biome' then return l10n('compact_before') .. itemLink('Sky Mill', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. l10n('compact_snow_biome') .. l10n('compact_after') elseif station == 'Placed Bottle only' then return l10n('compact_before') .. itemLink('Placed Bottle', {mode = 'image'}) .. l10n('compact_after') elseif station == 'Heavy Work Bench and Ecto Mist' then return l10n('compact_before') .. itemLink('Heavy Work Bench', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. l10n('compact_ecto_mist') .. l10n('compact_after') elseif station == 'Work Bench and Ecto Mist' then return l10n('compact_before') .. itemLink('Work Bench', {mode = 'image'}) .. "&thinsp;&amp;&thinsp;".. l10n('compact_ecto_mist') .. l10n('compact_after') else return l10n('compact_before') .. station .. l10n('compact_after') 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 = ' total: '..rows_count..' row(s)   ' if expectedrows and rows_count ~= expectedrows then str = str .. ''	end if not expectedrows and rows_count == 0 then str = str .. ''	end return str end

local tableRow = function(str, row, current_station, station_count, rows_count, showResultId, 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']..'-'..row['version']) or getArg('result-index-'..row['result'])

str = str .. ''

if needGroup then local result = row['result']..'|'..row['resultid']..'|'..row['resultimage']..'|'..row['resulttext']..'|'..row['amount']..'|'..row['version'] -- 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 = ''.. resultCell(row, showResultId, needLink, false, template).. ' '		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 .. '' .. (getArg(result_index .. '-row-' .. v) or '') .. ' '					else str_w = str_w .. ' ' end end end if extCols_B then for _, v in ipairs(extCols_B) do					if result_index then str_x = str_x .. '' .. (getArg(result_index .. '-row-' .. v) or '') .. ' '					else str_x = str_x .. ' ' end end end if extCols_C then for _, v in ipairs(extCols_C) do					if result_index then str_y = str_y .. '' .. (getArg(result_index .. '-row-' .. v) or '') .. ' '					else str_y = str_y .. ' ' end end end if extCols_D then for _, v in ipairs(extCols_D) do					if result_index then str_z = str_z .. '' .. (getArg(result_index .. '-row-' .. v) or '') .. ' '					else str_z = str_z .. ' ' end end end end else if extCols_A then for _, v in ipairs(extCols_A) do				if result_index then str_w = str_w .. '' .. (getArg(result_index .. '-row-' .. v) or '') .. ' '				else str_w = str_w .. '<td class="'..v..'"> ' 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 '') .. ' '				else str_x = str_x .. '<td class="'..v..'"> ' 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 '') .. ' '				else str_y = str_y .. '<td class="'..v..'"> ' 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 '') .. ' '				else str_z = str_z .. '<td class="'..v..'"> ' end end end str_resultCell = ' '.. resultCell(row, showResultId, needLink, false, template).. ' '	end

str = str .. str_w .. str_resultCell .. str_x .. ' ' .. ingredientsCell(row['args']).. ' ' .. 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 '') .. ' '						else str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx"> ' end end end str = str .. '<td class="station" rowspan="xxxrowspanxxx">'.. stationCell(station) ..' ' -- 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 '') .. ' '						else str = str .. '<td class="station '..v..'" rowspan="xxxrowspanxxx"> ' 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 '') .. ' '					else str = str .. '<td class="station '..v..'"> ' end end end str = str .. ' '.. stationCell(station) ..' ' -- 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 '') .. ' '					else str = str .. '<td class="station '..v..'"> ' end end end end end

str = str .. str_z ..' ' 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 .. ' '				else str = str .. '<td class="'..v..'"> ' end end end temp = getArg(p..'col-result') if temp then valid = true str = str .. ' ' .. temp .. ' '		else str = str .. ' '		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 .. ' '				else str = str .. '<td class="'..v..'"> ' end end end temp = getArg(p..'col-ingredients') if temp then valid = true str = str .. ' ' .. temp .. ' '		else str = str .. ' '		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 .. ' '				else str = str .. '<td class="'..v..'"> ' 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 .. ' '					else str = str .. '<td class="station '..v..'"> ' end end end temp = getArg(p..'col-station') if temp then valid = true str = str .. ' ' .. temp .. ' '			else str = str .. ' '			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 .. ' '					else str = str .. '<td class="station '..v..'"> ' 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 .. ' '				else str = str .. '<td class="'..v..'"> ' end end end str = str .. ' '

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

local tableBody = function(result, showResultId, 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, showResultId, 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 p = {}

-- for p.register = function(frame) local args = frame:getParent.args

-- 	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

--, normalize version = normalizeVersion(args['version'] or '')

--store frame:callParserFunction('#cargo_store:_table=Recipes',{		result = trim(args['result'] or ),		resultid = trim(args['resultid'] or ),		resultimage = trim(args['image'] or ),		resulttext = trim(args['text'] or ),		amount = trim(args['amount'] or ),		version = version,		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 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']

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				str = str .. 			end			if str ~=  then				str =  .. str			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 ' Recipes: No constraint ' end

-- format: local needCate, needLink = getFlags(args) local needGroup = true if (getArg('grouping') or 'y'):sub(1,1) == 'n' then needGroup = false end local showResultId = false if trim(args['showresultid'] or ) ~=  then showResultId = true 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 trim(args['nostation'] or ) ~=  then -- no station -- query, still need contain station field for cate. local result = mw.ext.cargo.query('Recipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {			where = where,			groupBy = "resultid, result, amount, version, ings",			orderBy = "result, amount DESC, version", -- Don't order by station			limit = 2000,		}) return tableBody(result, showResultId, 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, resultid, resultimage, resulttext, amount, version, station, args', {			where = where,			groupBy = "resultid, result, amount, ings, version",			orderBy = "station, result, amount DESC, version, ings", -- order by station first for station grouping.			limit = 2000,		}) return tableBody(result, showResultId, true, needGroup, needCate, needLink, rootpagename, _title, _expectedrows, getArg('resulttemplate'), stationGroup) end end -- p.query

-- for 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 ' Recipes/extract: No constraint ' end

-- query: local result = mw.ext.cargo.query('Recipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {		where = where,		groupBy = "resultid, result, amount, version, ings",		orderBy = "result, amount DESC, version", -- 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 withVersion = not getArg('noversion') local str = nil for _, row in ipairs(result) do			if str then str = str .. sep else str = '' end str = str .. ' '			if withVersion then if row['version'] ~= '' then str = str ..currentFrame:expandTemplate{ title = 'version icons', args = {row['version']} }..': ' end end 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..' ' 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 ' '..str..' ' elseif mode == 'station' then -- only return first row. for _, row in ipairs(result) do			return stationCell(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, getArg('showresultid'), needLink, true, getArg('resulttemplate')) end elseif mode == 'ingredients-buy' then -- 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 -- 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 ' Recipes/extract: Invalid mode ' 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, resultid, resultimage, resulttext, amount, version, station, args', {		where = where,		groupBy = "resultid, result, amount, ings, version",		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