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 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[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			cache[key] = item_link(currentFrame, args)		end		return cache[key]	end end)

local function getVersionIconsStr(version) -- is a slow template, so cache its result: local vstr = cache.get(lang..':recipes:versionicon:'..version) -- cache for current lang if not vstr then vstr = currentFrame:expandTemplate{ title = 'version icons', args = {version} } cache.set(lang..':recipes:versionicon:'..version, vstr, 3600*24) -- cache 24hr. end return vstr 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' or item == 'Ash 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' or item == 'Hardened Ebonsand Block' or item == 'Hardened Crimsand Block' or item == 'Hardened Pearlsand 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 Jungle 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 == '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' or item == 'Spicy Pepper' or item == 'Pomegranate' 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 == 'Scarlet Macaw' or item == 'Blue Macaw' then return 'Any Macaw' elseif item == 'Yellow Cockatiel' or item == 'Gray Cockatiel' then return 'Any Cockatiel' elseif item == 'Cloud in a Balloon' or item == 'Blue Horseshoe Balloon' then return 'Any Cloud Balloon' elseif item == 'Blizzard in a Balloon' or item == 'White Horseshoe Balloon' then return 'Any Blizzard Balloon' elseif item == 'Sandstorm in a Balloon' or item == 'Yellow Horseshoe Balloon' then return 'Any Sandstorm Balloon' elseif item == 'Silly Green Balloon' or item == 'Silly Pink Balloon' or item == 'Silly Purple Balloon' then return 'Any Balloon' elseif item == 'Guide to Critter Companionship' or item == 'Guide to Critter Companionship (Inactive)' then return 'Any Guide to Critter Companionship' elseif item == 'Guide to Environmental Preservation' or item == 'Guide to Environmental Preservation (Inactive)' then return 'Any Guide to Environmental Preservation' 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('japan', 1, true) then version = version .. ' japan' elseif _version:find('newchinese', 1, true) then version = version .. ' newchinese' else if _version:find('desktop', 1, true) or _version:find('pc', 1, true) then version = version .. ' PC' 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('mobile', 1, true) then version = version .. ' mobile' end if _version:find('windowsphone', 1, true) then version = version .. ' windowsphone' end if _version:find('oldchinese', 1, true) then version = version .. ' oldchinese' end if _version:find('3ds', 1, true) then version = version .. ' 3ds' end if _version:find('tmodloader', 1, true) then version = version .. ' tmodloader' end if _version:find('tmodloaderlegacy', 1, true) then version = version .. ' tmodloaderlegacy' end if version == ' PC console old-gen mobile windowsphone oldchinese 3ds tmodloader tmodloaderlegacy' then version = '' end 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, template) local result, resultid, resultimage, resultnote, amount, version = row['result'], row['resultid'], row['resultimage'], (row['resulttext'] or ), row['amount'], (row['version'] or ) local str = '' --version text if version ~= '' then str = str .. ' ' .. l10n('version_note_before') .. getVersionIconsStr(version) .. l10n('version_note_after') .. ' '	end

local args = {anchor = resultanchor, nolink = not needLink, class='multi-line'} if showResultId then args['id'] = resultid end if resultimage then args['image'] = resultimage end str = str .. itemLink(result, args) if amount ~= '1' then str = str .. ' ('..amount..') ' end -- note text if resultnote ~= '' then str = str .. ' ' .. resultnote .. ' '	end if template then local template_str = currentFrame:expandTemplate{ title = template, args = { link = needLink, showid = showResultId, resultid=resultid, resultimage=resultimage, resultnote=resultnote, 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'} local il = function(s, o)		if o then for k,v in pairs(o) do options[k] = v end end return itemLink(s, options) end 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' or station == 'Teapot' or station == 'Campfire' then return il(station) elseif station == 'Iron Anvil' then return il('Iron Anvil') .. l10n('station_sep_or') .. il('Lead Anvil') elseif station == 'Iron Anvil and Ecto Mist' then return il('Iron Anvil') .. l10n('station_sep_or') .. il('Lead Anvil') .. l10n('station_sep_and').. il('Ecto Mist', {mode = 'text'}) elseif station == 'Adamantite Forge' then return il('Adamantite Forge') .. l10n('station_sep_or') .. il('Titanium Forge') elseif station == 'Mythril Anvil' then return il('Mythril Anvil') .. l10n('station_sep_or') .. il('Orichalcum Anvil') elseif station == 'Demon Altar' then return il('Demon Altar') .. l10n('station_sep_or') .. il('Crimson Altar') elseif station == 'Cooking Pot' then return il('Cooking Pot') .. l10n('station_sep_or') .. il('Cauldron') elseif station == 'Placed Bottle' then return il('Placed Bottle') .. l10n('station_sep_or') .. il('Alchemy Table') elseif station == 'Water' then return il('Water') .. l10n('station_sep_or') .. il('Sink') elseif station == 'Table and Chair' then return il('Table') .. l10n('station_sep_and') .. il('Chair') elseif station == 'Work Bench and Chair' then return il('Work Bench') .. l10n('station_sep_and') .. il('Chair') elseif station == 'Crystal Ball and Lava' then return il('Crystal Ball') .. l10n('station_sep_and') .. il('Lava') elseif station == 'Crystal Ball and Honey' then return il('Crystal Ball') .. l10n('station_sep_and') .. il('Honey') elseif station == 'Crystal Ball and Water' then return il('Crystal Ball') .. l10n('station_sep_and').. ' ' .. il('Water') .. l10n('station_sep_or') .. il('Sink') .. ' '	elseif station == 'Sky Mill and Water' then return il('Sky Mill') .. l10n('station_sep_and').. ' ' .. il('Water') .. l10n('station_sep_or') .. il('Sink') .. ' '	elseif station == 'Sky Mill and Snow Biome' then return il('Sky Mill') .. l10n('station_sep_and').. l10n('snow_biome') elseif station == 'Placed Bottle only' then return il('Placed Bottle') elseif station == 'Heavy Work Bench and Ecto Mist' then return il('Heavy Work Bench') .. l10n('station_sep_and').. il('Ecto Mist', {mode = 'text'}) elseif station == 'Work Bench and Ecto Mist' then return il('Work Bench') .. l10n('station_sep_and').. il('Ecto Mist', {mode = 'text'}) elseif station == "Tinkerer's Workshop and Ecto Mist" then return il("Tinkerer's Workshop") .. l10n('station_sep_and').. il('Ecto Mist', {mode = 'text'}) elseif station == 'Shimmer' then return il('Shimmer', {[2] = l10n('station_shimmer')}) else return station end end -- for extract. local compactStation = function(station) local options = {mode = 'image'} local il = function(s, o)		if o then for k,v in pairs(o) do options[k] = v end end return itemLink(s, options) end 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' or station == 'Teapot' or station == 'Campfire' then return l10n('compact_before') .. il(station) .. l10n('compact_after') elseif station == 'Iron Anvil' then return l10n('compact_before') .. il("Iron Anvil") .. "&thinsp;/&thinsp;" .. il("Lead Anvil") .. l10n('compact_after') elseif station == 'Iron Anvil and Ecto Mist' then return l10n('compact_before') .. il("Iron Anvil") .. "&thinsp;/&thinsp;" .. il("Lead Anvil") .. "&thinsp;&amp;&thinsp;".. il('Ecto Mist', {mode = 'text'}) .. l10n('compact_after') elseif station == 'Adamantite Forge' then return l10n('compact_before') .. il("Adamantite Forge") .. "&thinsp;/&thinsp;" .. il("Titanium Forge") .. l10n('compact_after') elseif station == 'Mythril Anvil' then return l10n('compact_before') .. il("Mythril Anvil") .. "&thinsp;/&thinsp;" .. il("Orichalcum Anvil") .. l10n('compact_after') elseif station == 'Demon Altar' then return l10n('compact_before') .. il("Demon Altar") .. "&thinsp;/&thinsp;" .. il("Crimson Altar") .. l10n('compact_after') elseif station == 'Cooking Pot' then return l10n('compact_before') .. il("Cooking Pot") .. "&thinsp;/&thinsp;" .. il("Cauldron") .. l10n('compact_after') elseif station == 'Placed Bottle' then return l10n('compact_before') .. il("Placed Bottle") .. "&thinsp;/&thinsp;" .. il("Alchemy Table") .. l10n('compact_after') elseif station == 'Water' then return l10n('compact_before') .. il("Water") .. "&thinsp;/&thinsp;" .. il("Sink") .. l10n('compact_after') elseif station == 'Table and Chair' then return l10n('compact_before') .. il("Table") .. "&thinsp;&amp;&thinsp;" .. il("Chair") .. l10n('compact_after') elseif station == 'Work Bench and Chair' then return l10n('compact_before') .. il("Work Bench") .. "&thinsp;&amp;&thinsp;" .. il("Chair") .. l10n('compact_after') elseif station == 'Crystal Ball and Lava' then return l10n('compact_before') .. il("Crystal Ball") .. "&thinsp;&amp;&thinsp;" .. il("Lava") .. l10n('compact_after') elseif station == 'Crystal Ball and Honey' then return l10n('compact_before') .. il("Crystal Ball") .. "&thinsp;&amp;&thinsp;" .. il("Honey") .. l10n('compact_after') elseif station == 'Crystal Ball and Water' then return l10n('compact_before') .. il("Crystal Ball") .. "&thinsp;&amp;&thinsp;".. il("Water") .. " / " ..		il("Crystal Ball") .. "&thinsp;&amp;&thinsp;" .. il("Sink") .. l10n('compact_after') elseif station == 'Sky Mill and Water' then return l10n('compact_before') .. il("Sky Mill") .. "&thinsp;&amp;&thinsp;".. il("Water") .. " / " .. 		il("Sky Mill") .. "&thinsp;&amp;&thinsp;".. il("Sink") .. l10n('compact_after') elseif station == 'Sky Mill and Snow Biome' then return l10n('compact_before') .. il("Sky Mill") .. "&thinsp;&amp;&thinsp;".. l10n('compact_snow_biome') .. l10n('compact_after') elseif station == 'Placed Bottle only' then return l10n('compact_before') .. il("Placed Bottle") .. l10n('compact_after') elseif station == 'Heavy Work Bench and Ecto Mist' then return l10n('compact_before') .. il("Heavy Work Bench") .. "&thinsp;&amp;&thinsp;".. il('Ecto Mist', {mode = 'text'}) .. l10n('compact_after') elseif station == 'Work Bench and Ecto Mist' then return l10n('compact_before') .. il("Work Bench") .. "&thinsp;&amp;&thinsp;".. il('Ecto Mist', {mode = 'text'}) .. l10n('compact_after') elseif station == "Tinkerer's Workshop and Ecto Mist" then return l10n('compact_before') .. il("Tinkerer's Workshop") .. "&thinsp;&amp;&thinsp;".. il('Ecto Mist', {mode = 'text'}) .. l10n('compact_after') elseif station == 'Shimmer' then return l10n('compact_before') .. il('Shimmer', {[2] = l10n('station_shimmer')}) .. 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_ -- table wrap to make both border-radius and wild table workaround work. 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 '')) or getArg('result-index-'..row['result'])

str = str .. ''

if needGroup then local result = row['result']..'|'..(row['resultid'] or )..'|'..(row['resultimage'] or )..'|'..(row['resulttext'] or )..'|'..row['amount']..'|'..(row['version'] or ) -- 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, 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, 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['note'] or ), -- reuse the legacy "resulttext" field as note.		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'] resultanchor = trim(args['resultanchor'] or '')

local result_order = trim(args['orderbyid'] or '') if result_order == 'y' or result_order == 'yes' then result_order = 'resultid' else result_order = 'result' end

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 frame:expandTemplate{ title='error', args={ "Recipes: No constraint", from = 'Recipes', inline = 'y'}} 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, station",			orderBy = result_order .. ", 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, station",			orderBy = "station, ".. result_order .. ", 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

local result_order = trim(args['orderbyid'] or '') if result_order == 'y' or result_order == 'yes' then result_order = 'resultid' else result_order = 'result' end

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 frame:expandTemplate{ title='error', args={ "Recipes/extract: Invalid mode", from = 'Recipes', inline = 'y'}} 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_order .. ", amount DESC, version", -- Don't order by station		limit = 20, -- enough.	})

-- output local mode = getArg('mode') local sep = getArg('sep') or getArg('seperator') if sep == 'null' or sep == 'nil' then sep = '' end local withVersion = not getArg('noversion') local withNote = not getArg('nonote') local withOne = getArg('full') 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 = ''

for _, row in ipairs(result) do			if str ~= '' then str = str .. sep end str = str .. ' '			if withVersion then if (row['version'] or ) ~=  then str = str .. getVersionIconsStr(row['version']) .. l10n(':') 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' or withOne 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' or withOne 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 if withNote and (row['resulttext'] or ) ~=  then str = str .. ' '..row['resulttext']..' ' end str = str..' ' end return str elseif mode == 'ingredients' then local sep = sep or l10n('default_sep_ingredients') local str = '' for _, row in ipairs(result) do			if str ~= '' then str = str .. sep end str = str .. ' '			if withVersion then if (row['version'] or ) ~=  then str = str ..getVersionIconsStr(row['version'])..l10n(':') end 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			local result, resultid, resultimage, resultnote, amount, version = row['result'], row['resultid'], row['resultimage'], (row['resulttext'] or ), row['amount'], (row['version'] or ) local showResultId = getArg('showresultid') local str = ' ' --version text if withVersion and (version ~= '') then str = str .. ' ' .. l10n('version_note_before_extract_result') .. getVersionIconsStr(version) .. l10n('version_note_after_extract_result') .. ' '			end --item: local args = {anchor = resultanchor, nolink = not needLink} if showResultId then args['id'] = resultid end if resultimage then args['image'] = resultimage end if getArg('notext') then args['mode'] = 'image' end str = str .. itemLink(result, args) --amount if not getArg('noamount') and (amount ~= '1' or withOne) then str = str ..' × '.. amount .. ' '			end if withNote and (resultnote ~= '') then str = str .. ' '..resultnote..' ' end if template then local template_str = currentFrame:expandTemplate{ title = getArg('resulttemplate'), args = { link = needLink, showid = showResultId, noversion = not noVersion, resultid=resultid, resultimage=resultimage, resultnote=resultnote, nonote = not noNote, result=result, amount=amount, versions=version, } }				str = template_str:gsub('@@@@', str) end return str..' ' end elseif mode == 'amount' or mode == 'resultamount' then -- only return first row. for _, row in ipairs(result) do			return row['amount'] 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 frame:expandTemplate{ title='error', args={ "Recipes/extract: Invalid mode", from = 'Recipes', inline = 'y'}} 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. and COUNT(DISTICT ..) will also not get correct result due to HOLD keyword. local result = mw.ext.cargo.query('Recipes', 'resultid', {		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