Module:Crafting Recipes: Difference between revisions

From Valheim Wiki
No edit summary
No edit summary
 
Line 6: Line 6:


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


local resultanchor
local resultanchor


local l10n = function(key)
local l10n = function(key)
return key
    return key
end
end


Line 24: Line 24:


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


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


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


-- return an 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.
-- return an 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 split = (function()
local metals = {
    local metals = {
['Copper/Tin'] = 1,
        ['Copper/Tin'] = 1,
['Tin/Copper'] = 2,
        ['Tin/Copper'] = 2,
}
    }
return function(name)
 
local count = select(2, name:gsub("/", "/", 2))
    return function(name)
if count == 0 then
        local count = select(2, name:gsub("/", "/", 2))
-- only 1 item
        if count == 0 then
return { trim(name) }
            -- only 1 item
elseif count == 1 then
            return { trim(name) }
-- 2 items
        elseif count == 1 then
local item1a, item1b, item2a, item2b = name:match("^%s*(%S+)%s*(.-)/%s*(%S+)%s*(.-)$")
            -- 2 items
local x = metals[item1a..'/'..item2a]
            local item1a, item1b, item2a, item2b = name:match("^%s*(%S+)%s*(.-)/%s*(%S+)%s*(.-)$")
if tostring(item1b) == '' and x then
            local x = metals[item1a .. '/' .. item2a]
item1b = item2b
            if tostring(item1b) == '' and x then
end
                item1b = item2b
if x == 2 then
            end
return {trim(item2a..' '..item2b), trim(item1a..' '..item1b)}
            if x == 2 then
else
                return { trim(item2a .. ' ' .. item2b), trim(item1a .. ' ' .. item1b) }
return {trim(item1a..' '..item1b), trim(item2a..' '..item2b)}
            else
end
                return { trim(item1a .. ' ' .. item1b), trim(item2a .. ' ' .. item2b) }
else
            end
-- 3 or more items
        else
return explode('/', name)
            -- 3 or more items
end
            return explode('/', name)
end
        end
    end
end)()
end)()


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


-- normalize ingredient name input, Lead Bar=>¦Lead Bar¦, Iron/Lead Bar => ¦Iron Bar¦Lead Bar¦, Lead/Iron Bar => ¦Iron Bar¦Lead Bar¦ ....
-- 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 normalize = function(name)
local result = '¦'
    local result = '¦'
for k, v in ipairs(split(name)) do
    for k, v in ipairs(split(name)) do
result = result .. itemname(v) .. '¦'
        result = result .. itemname(v) .. '¦'
end
    end
return result
 
    return result
end
end


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


local getItemGroupName = function(item)
local getItemGroupName = function(item)
if item == 'Wood' or item == 'Wood2' or item == 'Wood3' then
    if item == 'Wood' or item == 'Wood2' or item == 'Wood3' then
return 'Any Wood'
        return 'Any Wood'
elseif item == 'Copper Bar' or item == 'Tin Bar' then
    elseif item == 'Copper Bar' or item == 'Tin Bar' then
return 'Any Bar'
        return 'Any Bar'
end
    end
end
end


local normalizeStation = function(station)
local normalizeStation = function(station)
return station
    return station
end
end


local normalizeVersion = function(_version)
local normalizeVersion = function(_version)
return _version
    return _version
end
end


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


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


local where = ''
    return 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
end


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


local ingredientsCell = function(args)
local ingredientsCell = function(args)
local str = '<ul>'
    local str = '<ul>'
for _, v in ipairs(explode('^', args)) do
    for _, v in ipairs(explode('^', args)) do
str = str .. '<li>'
        str = str .. '<li>'
local item, amount = v:match('^(.-)¦(.-)$')
        local item, amount = v:match('^(.-)¦(.-)$')
local s
        local s
for _, itemname in ipairs(split(item)) do
        for _, itemname in ipairs(split(item)) do
if s then
            if s then
s = s .. l10n('ingredients_sep') .. itemLink(itemname)
                s = s .. l10n('ingredients_sep') .. itemLink(itemname)
else
            else
s = itemLink(itemname)
                s = itemLink(itemname)
end
            end
end
        end
str = str .. s
        str = str .. s
if amount ~= '1' then
        if amount ~= '1' then
str = str .. ' <span class="note-text">('..amount..')</span>'
            str = str .. ' <span class="note-text">(' .. amount .. ')</span>'
end
        end
str = str .. '</li>'
        str = str .. '</li>'
end
    end
str = str .. '</ul>'
    str = str .. '</ul>'
return str
 
    return str
end
end


local stationLevelLink = function(station, level)
local stationLevelLink = function(station, level)
return '<div class="station-level-container" title="' .. l10n('Required station level') .. '">'
    return '<div class="station-level-container" title="' .. l10n('Required station level') .. '">'
    .. '[[File:Crafting Station Level Star.png|link=' .. station .. ']]'
        .. '[[File:Crafting Station Level Star.png|link=' .. station .. ']]'
    .. '<span>' .. level .. '</span>'
        .. '<span>' .. level .. '</span>'
    .. '</div>'
        .. '</div>'
end
end


local stationCell = function(station, level, options)
local stationCell = function(station, level, options)
options = options or {wrap = 'y', suffixLinkWithItemTag = false}
    options = options or { wrap = 'y', suffixLinkWithItemTag = false }
if station == 'By Hand' then
    if station == 'By Hand' then
return l10n('By Hand')
        return l10n('By Hand')
elseif true == is_crafting_station(station) then
    elseif true == is_crafting_station(station) then
-- station == 'Workbench' or station == 'Forge' or station == 'Cauldron' or station == 'Fermenter' then
        -- station == 'Workbench' or station == 'Forge' or station == 'Cauldron' or station == 'Fermenter' then
local linkItem = itemLink(station, options)
        local linkItem = itemLink(station, options)
if level ~= '' then
        if level ~= '' then
linkItem = linkItem .. '<br>' .. stationLevelLink(station, level)
            linkItem = linkItem .. '<br>' .. stationLevelLink(station, level)
end
        end
 
return linkItem
        return linkItem
-- return itemLink(station, options)
        -- return itemLink(station, options)
elseif station == "Station One and Station Two" then
    elseif station == "Station One and Station Two" then
return itemLink("Station One", options) .. l10n('And').. itemLink('Station Two', {mode = 'text'})
        return itemLink("Station One", options) .. l10n('And') .. itemLink('Station Two', { mode = 'text' })
else
    else
return station
        return station
end
    end
end
end
-- for extract.
-- for extract.
local compactStation = function(station)
local compactStation = function(station)
if station == 'By Hand' then
    if station == 'By Hand' then
return ''
        return ''
else
    else
return l10n('compact_before') .. station .. l10n('compact_after')
        return l10n('compact_before') .. station .. l10n('compact_after')
end
    end
end
end


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


Line 382: Line 392:


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


local _i, _field
    return str
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('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('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('Crafting 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
end


local tableEnd = function(rows_count, expectedrows)
local tableEnd = function(rows_count, expectedrows)
local str = '</table><div style="display: none">total: '..rows_count..' row(s)</div></div></div>'
    local str = '</table><div style="display: none">total: ' .. rows_count .. ' row(s)</div></div></div>'
if expectedrows and rows_count ~= expectedrows then
    if expectedrows and rows_count ~= expectedrows then
str = str .. '[[Category:'.. l10n('cate_unexpected_rows_count') .. ']]'
        str = str .. '[[Category:' .. l10n('cate_unexpected_rows_count') .. ']]'
end
    end
if not expectedrows and rows_count == 0 then
    if not expectedrows and rows_count == 0 then
str = str .. '[[Category:'.. l10n('cate_no_row') .. ']]'
        str = str .. '[[Category:' .. l10n('cate_no_row') .. ']]'
end
    end
return str
 
    return str
end
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 tableRow = function(str, row, current_station, station_count, rows_count, showResultId, withStation, needCate,
local str_w = '' -- before result col
                          needLink, needGroup, current_result, result_count, current_result_ext, result_ext_count,
local str_x = '' -- between result and ingredients cols
                          template, stationGroup)
local str_y = '' -- between ingredients and station cols
    local str_w = '' -- before result col
local str_z = '' -- after station
    local str_x = '' -- between result and ingredients cols
local str_resultCell = ''
    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'])
    local result_index = getArg('result-index-#' .. rows_count) or getArg('result-index-' .. row['result'])


str = str .. '<tr data-rowid="'..tostring(rows_count)..'">'
    str = str .. '<tr data-rowid="' .. tostring(rows_count) .. '">'
 
    if needGroup then
        local result = row['result'] .. '|' .. (row['resultid'] or '') .. '|' .. 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, showResultId, needLink, false, 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
            -- extCols_C = { col-C-1 }
            -- for _, v in ipairs(extCols_C) = col-C-1
            -- result_index = 'result-index-'..row['result'] = getArg('result-index-Grilled neck tail') = a
            -- value = result_index .. '-row-'' .. col-C-1) = getArg('a-row-col-C-1') = 20s
            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, showResultId, needLink, false, template) .. '</td>'
    end


if needGroup then
    str = str ..
local result = row['result']..'|'..(row['resultid'] or '')..'|'..row['amount']
        str_w ..
-- grouping result col
        str_resultCell .. str_x .. '<td class="ingredients">' .. ingredientsCell(row['args']) .. '</td>' .. str_y
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, showResultId, needLink, false, 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
-- extCols_C = { col-C-1 }
-- for _, v in ipairs(extCols_C) = col-C-1
-- result_index = 'result-index-'..row['result'] = getArg('result-index-Grilled neck tail') = a
-- value = result_index .. '-row-'' .. col-C-1) = getArg('a-row-col-C-1') = 20s
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, showResultId, needLink, false, template).. '</td>'
end


str = str .. str_w .. str_resultCell .. str_x .. '<td class="ingredients">' .. ingredientsCell(row['args']).. '</td>' .. str_y
    if withStation then
        local stationName = row['station'] or ''
        local stationLevel = row['stationlevel'] or ''
        local station = stationName .. stationLevel -- @TODO: Mave


if withStation then
        if stationGroup then
local stationName = row['station'] or ''
            if current_station == station then -- is same group ??
local stationLevel = row['stationlevel'] or ''
                station_count = station_count + 1
local station = stationName .. stationLevel -- @TODO: Mave
            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" data-station="' ..
                    stationName ..
                    '" data-stationlevel="' ..
                    stationLevel .. '" rowspan="xxxrowspanxxx">' .. stationCell(stationName, stationLevel) .. '</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(stationName, stationLevel) .. '</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


if stationGroup then
    str = str .. str_z .. '</tr>'
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" data-station="' .. stationName .. '" data-stationlevel="' .. stationLevel .. '" rowspan="xxxrowspanxxx">'.. stationCell(stationName, stationLevel) ..'</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(stationName, stationLevel) ..'</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
return str, current_station, station_count, current_result, result_count, current_result_ext, result_ext_count
end
end


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


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




local tableBody = function(noResultsText, result, showResultId, withStation, needGroup, needCate, needLink, rootpagename, title, expectedrows, template, stationGroup)
local tableBody = function(noResultsText, result, showResultId, withStation, needGroup, needCate, needLink, rootpagename,
if next(result) == nil then
                          title, expectedrows, template, stationGroup)
  return "''" .. (noResultsText or 'No results') .. "''"
    if next(result) == nil then
end
        return "''" .. (noResultsText or 'No results') .. "''"
    end


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


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


return str
    return str
end
end
-----------------------------------------------------------------
-----------------------------------------------------------------
Line 860: Line 908:
-- for {{craftingrecipes/register}}
-- for {{craftingrecipes/register}}
p.register = function(frame)
p.register = function(frame)
local args = frame:getParent().args
    local args = frame:getParent().args


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


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


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


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


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


-- for {{craftingrecipes}}
-- for {{craftingrecipes}}
p.query = function(frame)
p.query = function(frame)
currentFrame = frame -- global frame cache
    currentFrame = frame -- global frame cache
local args = frame:getParent().args
    local args = frame:getParent().args
inputArgs = args
    inputArgs = args
 
    lang = frame.args['lang'] or '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
                str = str ..
                    '[[Category:' ..
                    (cate[station] or frame:expandTemplate { title = 'tr', args = { station, lang = lang, link = 'y' } }) ..
                    ']]'
            end
            if str ~= '' then
                str = '[[Category:' .. l10n('cate_craftable') .. ']]' .. str
            end


lang = frame.args['lang'] or 'en'
            return str
        end


resultanchor = trim(args['resultanchor'] or '')
        return addCate, cateStr
    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 .. '[[Category:'..(cate[station] or frame:expandTemplate{ title = 'tr', args = {station, lang=lang, link='y'}})..']]'
end
if str ~= '' then
str = '[[Category:'.. l10n('cate_craftable').. ']]' .. str
end
return str
end
return addCate, cateStr
end)()


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


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


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


     local noResultsText = 'No results'
     local noResultsText = 'No results'
     if args['result'] or '' ~= '' then
     if args['result'] or '' ~= '' then
    noResultsText = 'This item cannot be crafted'
        noResultsText = 'This item cannot be crafted'
     end
     end
     if args['ingredient'] or '' ~= '' then
     if args['ingredient'] or '' ~= '' then
    noResultsText = 'This item is not used in any crafting recipes'
        noResultsText = 'This item is not used in any crafting recipes'
end
    end
 
    if trim(args['nostation'] or '') ~= '' then
        -- no station
        -- query, still need contain station field for cate.
        local result = mw.ext.cargo.query('CraftingRecipes',
            'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, args', {
                where = where,
                groupBy = "resultid, result, amount, version, ings",
                orderBy = "result, amount DESC, version", -- Don't order by station
                limit = 2000,
            })
 
        return tableBody(noResultsText, 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('CraftingRecipes',
            'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, args', {
                where = where,
                groupBy = "resultid, result, amount, ings, version",
                orderBy = "station, stationlevel, result, amount DESC, version, ings", -- order by station first for station grouping.
                limit = 2000,
            })


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


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


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


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


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


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


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


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


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


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


return p
return p

Latest revision as of 22:00, 8 February 2026

Documentation for this module may be created at Module:Crafting Recipes/doc

local item_link = require('Module:Item').go
local is_crafting_station = require('Module:Item').is_crafting_station
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 resultanchor

local l10n = function(key)
    return 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
            local mode = args['mode'] or nil
            if mode == nil and name:lower() == 'by hand' then
                mode = 'noimage'
            end
            if mode ~= nil then
                args['mode'] = mode
            end
            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

-- return an 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,
        ['Tin/Copper'] = 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 == 'Wood2' or item == 'Wood3' then
        return 'Any Wood'
    elseif item == 'Copper Bar' or item == 'Tin Bar' then
        return 'Any Bar'
    end
end

local normalizeStation = function(station)
    return station
end

local normalizeVersion = function(_version)
    return _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
    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 = resultanchor, 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 .. ' <span class="note-text">(' .. amount .. ')</span>'
    end
    if not noVersion and version ~= nil and version ~= '' then
        -- {{version icons}} 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 = '<ul>'
    for _, v in ipairs(explode('^', args)) do
        str = str .. '<li>'
        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 .. ' <span class="note-text">(' .. amount .. ')</span>'
        end
        str = str .. '</li>'
    end
    str = str .. '</ul>'

    return str
end

local stationLevelLink = function(station, level)
    return '<div class="station-level-container" title="' .. l10n('Required station level') .. '">'
        .. '[[File:Crafting Station Level Star.png|link=' .. station .. ']]'
        .. '<span>' .. level .. '</span>'
        .. '</div>'
end

local stationCell = function(station, level, options)
    options = options or { wrap = 'y', suffixLinkWithItemTag = false }
    if station == 'By Hand' then
        return l10n('By Hand')
    elseif true == is_crafting_station(station) then
        -- station == 'Workbench' or station == 'Forge' or station == 'Cauldron' or station == 'Fermenter' then
        local linkItem = itemLink(station, options)
        if level ~= '' then
            linkItem = linkItem .. '<br>' .. stationLevelLink(station, level)
        end

        return linkItem
        -- return itemLink(station, options)
    elseif station == "Station One and Station Two" then
        return itemLink("Station One", options) .. l10n('And') .. itemLink('Station Two', { mode = 'text' })
    else
        return station
    end
end
-- for extract.
local compactStation = function(station)
    if station == 'By Hand' then
        return ''
    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 = '<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('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('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('Crafting 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, 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'])

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

    if needGroup then
        local result = row['result'] .. '|' .. (row['resultid'] or '') .. '|' .. 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, showResultId, needLink, false, 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
            -- extCols_C = { col-C-1 }
            -- for _, v in ipairs(extCols_C) = col-C-1
            -- result_index = 'result-index-'..row['result'] = getArg('result-index-Grilled neck tail') = a
            -- value = result_index .. '-row-'' .. col-C-1) = getArg('a-row-col-C-1') = 20s
            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, showResultId, needLink, false, template) .. '</td>'
    end

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

    if withStation then
        local stationName = row['station'] or ''
        local stationLevel = row['stationlevel'] or ''
        local station = stationName .. stationLevel -- @TODO: Mave

        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" data-station="' ..
                    stationName ..
                    '" data-stationlevel="' ..
                    stationLevel .. '" rowspan="xxxrowspanxxx">' .. stationCell(stationName, stationLevel) .. '</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(stationName, stationLevel) .. '</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(noResultsText, result, showResultId, withStation, needGroup, needCate, needLink, rootpagename,
                           title, expectedrows, template, stationGroup)
    if next(result) == nil then
        return "''" .. (noResultsText or 'No results') .. "''"
    end

    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 {{craftingrecipes/register}}
p.register = function(frame)
    local args = frame:getParent().args

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

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

    --store
    frame:callParserFunction('#cargo_store:_table=CraftingRecipes', {
        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 '')),
        stationlevel = trim(args['stationlevel'] or ''),
        ingredients = mw.ustring.sub(ingredients_string, 2),
        ings = mw.ustring.sub(ingredients_string_full, 2),
        args = serialized,
    })
end -- p.register

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

    lang = frame.args['lang'] or '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
                str = str ..
                    '[[Category:' ..
                    (cate[station] or frame:expandTemplate { title = 'tr', args = { station, lang = lang, link = 'y' } }) ..
                    ']]'
            end
            if str ~= '' then
                str = '[[Category:' .. l10n('cate_craftable') .. ']]' .. 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 '<span style="color:red;font-weight:bold;">CraftingRecipes: 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 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

    local noResultsText = 'No results'
    if args['result'] or '' ~= '' then
        noResultsText = 'This item cannot be crafted'
    end
    if args['ingredient'] or '' ~= '' then
        noResultsText = 'This item is not used in any crafting recipes'
    end

    if trim(args['nostation'] or '') ~= '' then
        -- no station
        -- query, still need contain station field for cate.
        local result = mw.ext.cargo.query('CraftingRecipes',
            'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, args', {
                where = where,
                groupBy = "resultid, result, amount, version, ings",
                orderBy = "result, amount DESC, version", -- Don't order by station
                limit = 2000,
            })

        return tableBody(noResultsText, 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('CraftingRecipes',
            'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, args', {
                where = where,
                groupBy = "resultid, result, amount, ings, version",
                orderBy = "station, stationlevel, result, amount DESC, version, ings", -- order by station first for station grouping.
                limit = 2000,
            })

        return tableBody(noResultsText, result, showResultId, true, needGroup, needCate, needLink, rootpagename, _title,
            _expectedrows, getArg('resulttemplate'), stationGroup)
    end
end -- p.query

-- for {{craftingrecipes/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;">CraftingRecipes/extract: No constraint</span>'
    end

    -- query:
    local result = mw.ext.cargo.query('CraftingRecipes',
        'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, 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 .. '<span class="recipe compact">'
            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 .. '</span>'
        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 .. 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 == '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 '<span style="color:red;font-weight:bold;">CraftingRecipes/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('CraftingRecipes',
        'result, resultid, resultimage, resulttext, amount, version, station, stationlevel, 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('CraftingRecipes', 'result', {
        where = where,
        limit = 1, -- enough.
    })
    -- output
    for _, row in ipairs(result) do
        return 'yes'
    end
end -- p.exist

return p