Path of Exile Wiki

Please consider helping keep the wiki up to date. Check the to-do list of updates needed for version 3.14.0.

Game data exports will becoming later as the technical changes in addition to regular changes take some more time.

READ MORE

Path of Exile Wiki
(Fixed headers showing even though there are no results. If there's only 1 mod group then use mod types instead when creating new drop down lists. Cargo support offset now.)
(Added the ability to add custom fields to mod_table via q_fields (added at end, in order))
Line 422: Line 422:
 
query[string.sub(key, 3)] = value
 
query[string.sub(key, 3)] = value
 
end
 
end
 
end
  +
  +
  +
fields = table.concat(fields, ',')
  +
if tpl_args.q_fields then
  +
fields = fields .. ' ,' .. tpl_args.q_fields
 
end
 
end
 
 
 
local results = cargo.query(
 
local results = cargo.query(
 
tables,
 
tables,
table.concat(fields, ','),
+
fields,
 
query
 
query
 
)
 
)
Line 483: Line 489:
 
stats[page_id] = stat_id_map
 
stats[page_id] = stat_id_map
 
end
 
end
  +
end
  +
  +
--
  +
-- Display
  +
--
  +
  +
-- Preformance optimization
  +
if tpl_args.q_fields then
  +
tpl_args._extra_fields = m_util.string.split(tpl_args.q_fields, ',')
  +
for index, field in ipairs(tpl_args._extra_fields) do
  +
field = m_util.string.split(field, '=')
  +
-- field[2] will be nil if there is no alias
  +
tpl_args._extra_fields[index] = field[2] or field[1]
  +
end
  +
else
  +
tpl_args._extra_fields = {}
 
end
 
end
 
 
 
local tbl = mw.html.create('table')
 
local tbl = mw.html.create('table')
 
tbl:attr('class', 'wikitable sortable modifier-table')
 
tbl:attr('class', 'wikitable sortable modifier-table')
 
 
-- Header
 
-- Header
 
 
Line 515: Line 536:
 
:wikitext(i18n.mod_table[key])
 
:wikitext(i18n.mod_table[key])
 
end
 
end
  +
end
  +
  +
for _, field in ipairs(tpl_args._extra_fields) do
  +
tr
  +
:tag('th')
  +
:wikitext(field)
 
end
 
end
 
 
Line 579: Line 606:
 
tr:wikitext(m_util.html.td.na())
 
tr:wikitext(m_util.html.td.na())
 
end
 
end
  +
end
  +
end
  +
  +
for _, field in ipairs(tpl_args._extra_fields) do
  +
if row[field] then
  +
tr
  +
:tag('td')
  +
:wikitext(row[field])
  +
else
  +
tr:wikitext(m_util.html.td.na())
 
end
 
end
 
end
 
end

Revision as of 14:12, 13 April 2018

Template info icon Module documentation[view] [edit] [history] [purge]

Module implementing {{Modifier table}} with Cargo support.

--[[
Module responsible for displaying modifiers in various ways.

]]

local m_util = require('Module:Util')
local getArgs = require('Module:Arguments').getArgs
local m_game = require('Module:Game')
local f_item_link = require('Module:Item link').item_link

local cargo = mw.ext.cargo

local p = {}

-- ----------------------------------------------------------------------------
-- Strings
-- ----------------------------------------------------------------------------
-- This section contains strings used by this module.
-- Add new strings here instead of in-code directly, this will help other
-- people to correct spelling mistakes easier and help with translation to
-- other PoE wikis.

local i18n = {
    mod_table = {
        name = m_util.html.abbr('Name', 'Name of the modifier if available or its internal identifier instead'),
        mod_group = m_util.html.abbr('Group', 'Only one modifier from the specified group can appear at a time under normal circumstances'),
        mod_type = 'Type',
        domain = '[[Modifiers#Mod_Domain|Domain]]',
        generation_type = '[[Modifiers#Mod_Generation_Type|Generation Type]]',
        required_level = '[[Image:Level_up_icon_small.png|link=|For generated item/monster modifiers the minimum item/monster level respectively. Some generation types may not require this condition to be met, however item level restrictions may be raised to 80% of this value.]]',
        stat_text = m_util.html.abbr('Stats', 'Stats of the modifier and the range they can roll in (if applicable)'),
        buff = m_util.html.abbr('Buff', 'ID of the buff granted and the values associated'),
        granted_skill = m_util.html.abbr('Skill', 'ID of the skill granted'),
        tags = '[[Tags]]',
        
        iiq = m_util.html.abbr('IIQ', 'increased Quantity of Items found in this Area'),
        iir = m_util.html.abbr('IIR', 'increased Rarity of Items found in this Area'),
        pack_size = m_util.html.abbr('Pack<br>Size', 'Monster pack size'),
        
        spawn_weights = m_util.html.abbr('Spawn Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
        generation_weights = m_util.html.abbr('Generation Weighting', 'List of applicable tags and their values for weighting (i.e. calculating the chances) of the spawning of this modifier'),
    },
    
    drop_down_table = {
        collapse_all = 'Collapse all',
        expand_all = 'Expand all',
        table_intro = 'The table below displays the available [[modifiers]] for [[item]]s such as',
        prefix = 'Prefix',
        suffix = 'Suffix',
        corrupted = 'Corrupted',     
        enchant = 'Enchantment',
        mod_group = 'Mod group:',
    },
    
    errors = {
        --
        -- Mod template
        --
        sell_price_duplicate_name = 'Do not specify a sell price item name multiple times. Adjust the amount instead.',
        sell_price_missing_argument = 'Both %s and %s must be specified',
        
        --
        -- Modifier link template
        --
        undefined_statid = 'Please define any of these stat ids: %s',
        incorrect_modid = 'Please change the name from "%s" to any of these modifier ids:<br>%s',
        multiple_results = 'Please choose only one of these modifier ids:<br>%s',
        no_results = 'No results found.',
    },
}


--
-- Helper/Utility functions
--

local h = {}

function h.header(str)
    --[[
    This function replaces specific numbers with a generic #. 
    ]]
    
    local s = table.concat(m_util.string.split(str, '%(%d+%.*%d*%-%d+%.*%d*%)'), '#')
    s = table.concat(m_util.string.split(s, '%d+%.*%d*'), '#')
    s = table.concat(m_util.string.split(s, '<br>'), ', ')
    s = table.concat(m_util.string.split(s, '<br />'), ' ')
   
   return s
end

function h.query_weights(table_name, page_ids)
    results = cargo.query(
        string.format('mods,%s', table_name),
        string.format('mods._pageID,%s.tag,%s.weight', table_name, table_name),
        {
            where=page_ids,
            join=string.format('mods._pageID=%s._pageID', table_name),
            orderBy=string.format('mods.id ASC,%s.ordinal ASC', table_name),
            limit=5000,
        }
    )
    if #results == 5000 then
        error('Hit maximum cargo results')
    end
    
    return m_util.cargo.map_results_to_id{results=results,field='mods._pageID'}
end

function h.disambiguate_mod_name(results)
    --[[
        Disambiguates results from a mods query.
    ]]
    local str = {}
    for i,v in pairs(results) do
        str[#str+1] = string.format(
            '%s - %s ([[%s|page]])', 
            v['mods.id'] or v['mods._pageName'],
            string.gsub(
                v['mods.stat_text_raw'] or 'N/A', 
                '<br>', 
                ', '
            ) or '',
            v['mods._pageName']
        )
    end 
    return table.concat(str, '<br>')
end

function h.cargo_query(tpl_args)
    --[[
    Returns a Cargo query of all the results.
    
    tpl_args to be added to the cargo table needs to prefixed with q_.
    * tpl_args.q_*
    
    ]]
    
    -- Parse query arguments
    local query = {
        limit = 5000,
        offset = 0,
    }
    for key, value in pairs(tpl_args) do 
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end
    
    -- Query cargo table. If there are too many results then repeat, 
    -- offset, re-query and add the remaining results:
    local results = {}      
    repeat
        local result = cargo.query(
            query.tables,
            query.fields,
            query
        )
        query.offset = query.offset + #result

        for _,v in ipairs(result) do
            results[#results + 1] = v
        end
    until #result < query.limit 
    
    return results
end

-- ----------------------------------------------------------------------------
-- Template: Mod table
-- ----------------------------------------------------------------------------

local mod_table = {}
mod_table.data = {
    {
        arg = nil,
        header = i18n.mod_table.name,
        fields = {'mods._pageName', 'mods.id', 'mods.name'},
        options = {
            [3] = {
                optional=true,
           },
        },
        display = function(tpl_args, frame, tr, data)
            local name
            if data['mods.name'] then
                name = data['mods.name']
            else
                name = data['mods.id']
            end
            tr
                :tag('td')
                    :wikitext(string.format('[[%s|%s]]', data['mods._pageName'], name))
        end,
        order = 1000,
        sort_type = 'text',
    },
    {
        arg = 'domain',
        header = i18n.mod_table.domain,
        fields = {'mods.domain'},
        display = function(tpl_args, frame, tr, data)
            local value = data['mods.domain']
            tr
                :tag('td')
                    :attr('data-sort-value', value)
                    :wikitext(m_game.constants.mod.domains[tonumber(value)]['short_upper'])
        end,
        order = 2000,
        sort_type = 'text',
    },
    {
        arg = 'generation_type',
        header = i18n.mod_table.generation_type,
        fields = {'mods.generation_type'},
        display = function(tpl_args, frame, tr, data)
            local value = data['mods.generation_type']
            tr
                :tag('td')
                    :attr('data-sort-value', value)
                    :wikitext(m_game.constants.mod.generation_types[tonumber(value)]['short_upper'])
        end,
        order = 2001,
        sort_type = 'text',
    },
    {
        arg = {'group', 'mod_group'},
        header = i18n.mod_table.mod_group,
        fields = {'mods.mod_group'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.mod_group'])
        end,
        order = 2002,
        sort_type = 'text',
    },
    {
        arg = {'mod_type'},
        header = i18n.mod_table.mod_type,
        fields = {'mods.mod_type'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.mod_type'])
        end,
        order = 2003,
        sort_type = 'text',
    },
    {
        arg = {'level', 'required_level'},
        header = i18n.mod_table.required_level,
        fields = {'mods.required_level'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.required_level'])
        end,
        order = 2004,
    },
    {
        arg = {'stat_text'},
        header = i18n.mod_table.stat_text,
        fields = {'mods.stat_text'},
        display = function(tpl_args, frame, tr, data)
            local text
            -- map display type shows this in another column, remove this text to avoid clogging up the list
            if tpl_args.type == 'map' then
                local texts = m_util.string.split(data['mods.stat_text'], '<br>')
                local out = {}
                
                local valid
                for _, v in ipairs(texts) do
                    valid = true
                    for _, data in pairs(mod_table.stat_ids) do
                        if string.find(v, data.pattern) ~= nil then
                            valid = false
                            break
                        end
                    end
                    
                    if valid then
                        table.insert(out, v)
                    end
                end
                
                text = table.concat(out, '<br>')
            else
                text = data['mods.stat_text']
            end
            
            tr
                :tag('td')
                    :wikitext(text)
        end,
        order = 3000,
        sort_type = 'text',
    },

    {
        arg = 'buff',
        header = i18n.mod_table.buff,
        fields = {'mods.granted_buff_id', 'mods.granted_buff_value'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(string.format('%s %s', data['mods.granted_buff_id'], data['mods.granted_buff_value']))
        end,
        order = 4000,
        sort_type = 'text',
    },
    {
        arg = {'skill', 'granted_skill'},
        header = i18n.mod_table.granted_skill,
        fields = {'mods.granted_skill'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(data['mods.granted_skill'])
        end,
        order = 4001,
        sort_type = 'text',
    },
    {
        arg = {'tags'},
        header = i18n.mod_table.tags,
        fields = {'mods.tags'},
        display = function(tpl_args, frame, tr, data)
            tr
                :tag('td')
                    :wikitext(table.concat(m_util.string.split(data['mods.tags'], ','), ', '))
        end,
        order = 5000,
        sort_type = 'text',
    },
}
mod_table.stat_ids = {
    ['map_item_drop_quantity_+%'] = {
        header = i18n.mod_table.iiq,
        pattern = '%d+%% increased Quantity of Items found in this Area',
    },
    ['map_item_drop_rarity_+%'] = {
        header = i18n.mod_table.iir,
        pattern = '%d+%% increased Rarity of Items found in this Area',
    },
    ['map_pack_size_+%'] = {
        header = i18n.mod_table.pack_size,
        pattern = '%+%d+%% Monster pack size',
    }
}
mod_table.weights = {'spawn_weights', 'generation_weights'}

function p.mod_table(frame)
    tpl_args = getArgs(frame, {
        parentFirst = true
    })
    frame = m_util.misc.get_frame(frame)
    
    for _, key in ipairs(mod_table.weights) do
        tpl_args[key] = m_util.cast.boolean(tpl_args[key])
    end
    
    if string.find(tpl_args.q_where, '%[%[') ~= nil then
        error('SMW leftover in where clause')
    end
    
    local row_infos = {}
    for _, row_info in ipairs(mod_table.data) do
        local enabled = false
        if row_info.arg == nil then
            enabled = true
        elseif type(row_info.arg) == 'string' and m_util.cast.boolean(tpl_args[row_info.arg]) then
            enabled = true
        elseif type(row_info.arg) == 'table' then 
            for _, argument in ipairs(row_info.arg) do
                if m_util.cast.boolean(tpl_args[argument]) then
                    enabled = true
                    break
                end
            end
        end
        
        if enabled then
            row_info.options = row_info.options or {}
            row_infos[#row_infos+1] = row_info
        end
    end
    
    -- sort the rows
    table.sort(row_infos, function (a, b)
        return (a.order or 0) < (b.order or 0)
    end)
    
    -- Set tables
    local tables = 'mods' 
    if tpl_args.q_tables then
        tables = tables .. ',' .. tpl_args.q_tables
    end
    
    
    -- Set required fields
    local fields = {
        'mods._pageID',
    }
    for _, rowinfo in ipairs(row_infos) do
        if type(rowinfo.fields) == 'function' then
            rowinfo.fields = rowinfo.fields()
        end
        for index, field in ipairs(rowinfo.fields) do
            rowinfo.options[index] = rowinfo.options[index] or {}
            fields[#fields+1] = field
        end
    end
    
    -- Parse query arguments
    local query = {
        -- Workaround: fix duplicates
        groupBy='mods._pageID',
    }
    for key, value in pairs(tpl_args) do 
        if string.sub(key, 0, 2) == 'q_' then
            query[string.sub(key, 3)] = value
        end
    end
    
    
    fields = table.concat(fields, ',')
    if tpl_args.q_fields then
        fields = fields .. ' ,' .. tpl_args.q_fields
    end
    
    local results = cargo.query(
        tables,
        fields,
        query
    )
    
    if #results == 0 then
        if tpl_args.default ~= nil then
            return tpl_args.default
        else
            return 'No results found'
        end
    end
    
    -- this might be needed in other queries, currently not checking if it's actually needed
    -- because performance impact should be neglible
    local page_ids = {}
    for _, row in ipairs(results) do
        page_ids[#page_ids+1] = string.format('mods._pageID="%s"', row['mods._pageID'])
    end
    page_ids = table.concat(page_ids, ' OR ')
    
    local weights = {}
    for _, key in ipairs(mod_table.weights) do
        if tpl_args[key] then
            weights[key] = h.query_weights(key, page_ids)
        end
    end
    
    local stats
    if tpl_args.type == 'map' then
        local query_stat_ids = {}
        for k, _ in pairs(mod_table.stat_ids) do
            query_stat_ids[#query_stat_ids+1] = string.format('mod_stats.id="%s"', k)
        end
        
        local stat_results = cargo.query(
            'mods,mod_stats',
            'mods._pageID,mod_stats.id,mod_stats.min,mod_stats.max',
            {
                where=string.format('(%s) AND (%s)', page_ids, table.concat(query_stat_ids, ' OR ')),
                join='mods._pageID=mod_stats._pageID',
                orderBy='mods.id ASC',
                limit=5000,
            }
        )
        if #stat_results == 5000 then
            error('Hit maximum cargo results')
        end
        
        stats = m_util.cargo.map_results_to_id{results=stat_results, field='mods._pageID'}
        -- In addition map stats to stat <-> min/max pairs
        for page_id, rows in pairs(stats) do
            local stat_id_map = {}
            for _, row in ipairs(rows) do
                stat_id_map[row['mod_stats.id']] = {min=tonumber(row['mod_stats.min']), max=tonumber(row['mod_stats.max'])}
            end
            stats[page_id] = stat_id_map
        end
    end
    
    --
    -- Display
    --
    
    -- Preformance optimization
    if tpl_args.q_fields then
        tpl_args._extra_fields = m_util.string.split(tpl_args.q_fields, ',')
        for index, field in ipairs(tpl_args._extra_fields) do
            field = m_util.string.split(field, '=')
            -- field[2] will be nil if there is no alias
            tpl_args._extra_fields[index] = field[2] or field[1]
        end
    else
        tpl_args._extra_fields = {}
    end
    
    local tbl = mw.html.create('table')
    tbl:attr('class', 'wikitable sortable modifier-table')
    -- Header
    
    local tr = tbl:tag('tr')
    for _, row_info in ipairs(row_infos) do
        tr
            :tag('th')
                :attr('data-sort-type', row_info.sort_type or 'number')
                :wikitext(row_info.header)
                :done()
    end
    
    if tpl_args.type == 'map' then
        for stat_id, data in pairs(mod_table.stat_ids) do
            tr
                :tag('th')
                    :attr('data-sort-type', 'number')
                    :wikitext(data.header)
                    :done()
        end
    end
    
    for _, key in ipairs(mod_table.weights) do
        if tpl_args[key] then
            tr
                :tag('th')
                    :wikitext(i18n.mod_table[key])
        end
    end
    
    for _, field in ipairs(tpl_args._extra_fields) do
        tr
            :tag('th')
                :wikitext(field)
    end
    
    -- Body
    
    for _, row in ipairs(results) do
        tr = tbl:tag('tr')
                
        for _, rowinfo in ipairs(row_infos) do
            -- this has been cast from a function in an earlier step
            local display = true
            for index, field in ipairs(rowinfo.fields) do
                -- this will bet set to an empty value not nil confusingly
                if row[field] == '' then
                    if rowinfo.options[index].optional ~= true then
                        display = false
                        break
                    else
                        row[field] = nil
                    end
                end
            end
            if display then
                rowinfo.display(tpl_args, frame, tr, row, rowinfo.fields)
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
        
        if tpl_args.type == 'map' then
            for stat_id, data in pairs(mod_table.stat_ids) do
                local stat_data = stats[row['mods._pageID']]
                if stat_data and stat_data[stat_id] then
                    local v = stat_data[stat_id]
                    local text
                    if v.min == v.max then
                        text = v.min
                    else
                        text = string.format('(%s to %s)', v.min, v.max)
                    end
                    tr
                        :tag('td')
                            :attr('data-sort-value', (v.min+v.max)/2)
                            :wikitext(string.format('%s%%', text))
                            :done()
                else
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end
        
        for _, key in ipairs(mod_table.weights) do
            if tpl_args[key] then
                local weight_out = {}
                for _, wrow in ipairs(weights[key][row['mods._pageID']]) do
                    weight_out[#weight_out+1] = string.format('%s %s', wrow[key .. '.tag'], wrow[key .. '.weight'])
                end
                if #weight_out > 0 then
                    tr
                            :tag('td')
                                :wikitext(table.concat(weight_out, '<br>'))
                                :done()
                else    
                    tr:wikitext(m_util.html.td.na())
                end
            end
        end
        
        for _, field in ipairs(tpl_args._extra_fields) do
            if row[field] then
                tr
                    :tag('td')
                        :wikitext(row[field])
            else
                tr:wikitext(m_util.html.td.na())
            end
        end
    end
    
    return tostring(tbl)
end




-- ---------------------------------------------------------------------
-- Modifier link
-- ---------------------------------------------------------------------

function p.modifier_link(frame)
    --[[
    Finds and links to a modifier in formatted form.
    
    Examples: 
    = p.modifier_link{"Tyrannical"}
    = p.modifier_link{"Flaring"}
    = p.modifier_link{"Dictator's"}
    = p.modifier_link{"StrDexMaster%"}
    = p.modifier_link{"LocalIncreasedPhysicalDamagePercentAndAccuracyRating8", display='max', statid='local_physical_damage_+%'}
    
    ]]

    -- Get template args:
    local tpl_args = getArgs(frame, {parentFirst = true})
    local frame = m_util.misc.get_frame(frame)
        
    -- Aliases:
    tpl_args.modid = tpl_args.modid or tpl_args.id or tpl_args[1] or ''
    
    -- Define query arguments:
    local tables = {'mods', 'mod_stats', 'spawn_weights'}
    local fields = {'mods.name', 'mods.stat_text', 'mods.stat_text_raw', 'mods.generation_type', 'mods._pageName', 'mod_stats.max', 'mod_stats.min', 'spawn_weights.tag', 'spawn_weights.weight', 'mods.id', 'mod_stats.id'}
    local query = {
        join = 'mods._pageName=mod_stats._pageName, mods._pageName=spawn_weights._pageName',
        where = string.format(
            '(mods.name LIKE "%s" or mods.id LIKE "%s" or mods.stat_text LIKE "%s" or mods.stat_text_raw LIKE "%s") AND mod_stats.id LIKE "%%%s%%"', 
            tpl_args.modid, 
            tpl_args.modid,
            tpl_args.modid,
            tpl_args.modid,
            tpl_args.statid or '%' 
        ),
        -- groupBy = 'mods._pageID, mod_stats.id, spawn_weights.tag',
    }
    
    -- Query cargo rows:
    local results = m_util.cargo.query(tables, fields, query, args)
    
    -- Create own list for each cargo table and group by page name:
    tpl_args.tbl = {}
    for _,v in ipairs(tables) do 
        tpl_args.tbl[v] = {}
    end
    
    tpl_args.results_unique = {}
    local hash = {}
    for _,v in ipairs(results) do
        for ii, vv in pairs(tpl_args.tbl) do 
            if tpl_args.tbl[ii][v['mods._pageName']] == nil then 
                tpl_args.tbl[ii][v['mods._pageName']] = {}
            end
            local n = #tpl_args.tbl[ii][v['mods._pageName']] or 0
            tpl_args.tbl[ii][v['mods._pageName']][n+1] = v
            
            -- Get a sorted list that only has unique page names:
            if hash[v['mods._pageName']] ~= true then
                local m = #tpl_args.results_unique
                tpl_args.results_unique[m+1] = v
                hash[v['mods._pageName']] = true
            end
        end
    end 
    
    -- Helpful error handling:
    local err_tbl = {
        {
            bool = #results == 0,
            disp = {
                i18n.errors.no_results,
            }
        },
        {
            bool = #tpl_args.results_unique > 1,
            disp = {
                i18n.errors.multiple_results,
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
        {
            bool = tpl_args.modid ~= tpl_args.results_unique[1]['mods.id'],
            disp = {
                string.gsub(
                    i18n.errors.incorrect_modid, 
                    '%%s',
                    tpl_args.modid,
                    1
                ),
                h.disambiguate_mod_name(tpl_args.results_unique),
            },
        },
    }
    for _,v in ipairs(err_tbl) do 
        if v.bool then
            local cats = {'Pages with modifier link errors'}
            return m_util.html.error(
                {msg = string.format(v.disp[1], v.disp[2]) .. m_util.misc.add_category(cats)}
            )
        end
    end
    
    -- Display formats:
    local display = {
        abbr = {
            display = function(tpl_args, frame)
                return string.format(
                    '%s%s',
                    m_util.html.poe_color(
                        'mod', 
                        string.format(
                            '[[%s|%s]]', 
                            tpl_args.results_unique[1]['mods._pageName'], 
                            tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                        )
                    ),
                    m_util.html.abbr(
                        '[?]', 
                        string.gsub(
                            tpl_args.results_unique[1]['mods.stat_text_raw'], 
                            '<br>', 
                            ', '
                        ), 
                        class
                    )
                )
            end,
        },
        verbose = {
            display = function(tpl_args, frame)
                return string.format(
                    '%s - %s (%s)',
                    m_util.html.poe_color(
                        'mod', 
                        string.format(
                            '[[%s|%s]]', 
                            tpl_args.results_unique[1]['mods._pageName'], 
                            tpl_args.results_unique[1]['mods.name'] or tpl_args.results_unique[1]['mods.id']
                        )
                    ),
                    m_util.html.poe_color(
                        'mod', 
                        string.gsub(
                            tpl_args.results_unique[1]['mods.stat_text'], 
                            '<br>', 
                            ', '
                        )
                    ),
                    m_game.constants.mod.generation_types[
                        tonumber(
                            tpl_args.results_unique[1]['mods.generation_type']
                        )
                    ].full
                )
            end,
        },
        stat_text = {
            display = function(tpl_args, frame)
                return m_util.html.poe_color(
                    'mod', 
                    tpl_args.results_unique[1]['mods.stat_text']
                )
            end,
        },
        max = {
            display = function(tpl_args, frame)
                local statid = {}
                for _,v in ipairs(tpl_args.tbl.mod_stats[tpl_args.results_unique[1]['mods._pageName']]) do 
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.max']
                    end
                end
                
                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid, 
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
        min = {
            display = function(tpl_args, frame)
                local statid = {}
                for _,v in ipairs(tpl_args.tbl.mod_stats[tpl_args.results_unique[1]['mods._pageName']]) do 
                    statid[#statid+1] = v['mod_stats.id']
                    if tpl_args.statid == v['mod_stats.id'] then
                        return v['mod_stats.min']
                    end
                end
                
                if tpl_args.statid == nil then
                    return m_util.html.error(
                        {
                            msg = string.format(
                                i18n.errors.undefined_statid, 
                                table.concat(statid, ', ')
                            )
                        }
                    )
                end
            end
        },
    }
    
    return display[tpl_args.display or 'abbr'].display(tpl_args, frame)
end




-- ---------------------------------------------------------------------
-- Drop down list
-- ---------------------------------------------------------------------
    
function h.get_mod_domain(cargo_query)
    --[[ 
	Gets the mod domain based on the item class.
    ]]
	
    local out = cargo_query
    local mod_domains = m_game.constants.mod.domains
    
    -- Set the item class as key and the corresponding mod domain as 
    -- value:
    local class_to_domain = {
        ['Life Flasks']=2,
        ['Mana Flasks']=2,
        ['Hybrid Flasks']=2,
        ['Utility Flasks']=2,
        ['Critical Utility Flasks']=2,
        ['Maps']=5,
        ['Jewel']=11,
        ['Leaguestones']=13,
        ['Abyss Jewel']=14,
    }
    
    for i,_ in ipairs(out) do
        -- Get the domain, if it's not defined in the table assume it's 
        -- in the item domain.
        out[i]['items.domain'] = class_to_domain[out[i]['items.class']] or 1
        
        -- Convert the mod domain number to understandable text:
        out[i]['items.domain_text'] = mod_domains[out[i]['items.domain']]['short_lower']
    end

    return out
end

function h.get_item_tags(frame)
    --[[ 
    This function queries for the tags of a specific item.
    ]]
    
    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)
    
    -- Format the cargo query:
    tpl_args.q_tables = 'items'
    tpl_args.q_fields = 'items.name, items.tags, items.class, items.inventory_icon, items.html, items._pageName'
    local tbl = {
        {tpl_args.page, 'items._pageName = "%s"'},
        {tpl_args.item, 'items.name = "%s"'},
    }
    for _,v in ipairs(tbl) do 
        if v[1] ~= nil then
            condition = string.format(v[2], v[1])
            break
        end
    end
    tpl_args.q_where = condition
    tpl_args.q_groupBy = 'items._pageName'
    tpl_args.q_orderBy = 'items.name'

    -- Query mods with cargo:
    results = h.cargo_query(tpl_args)
    
    -- Find out what mod domain an item class is in:
    results = h.get_mod_domain(results)

    return results
end

function h.get_spawn_chance(frame)
    --[[
    Calculates the spawn chance of a set of mods that all have a 
    spawn weight.
    
    ]]
	
    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)
    
    -- Get the table:
    local tbl = tpl_args['tbl']
    
    -- Probabilities affecting the result besides the spawn weight:
    local chance_multiplier = tonumber(tpl_args['chance_multiplier']) or 1 
    
    -- Total number of outcomes.
    local N = 0
    for i,_ in ipairs(tbl) do
        N = N + tbl[i]['spawn_weights.weight']
    end
    
    for i,_ in ipairs(tbl) do
        -- Number of ways it can happen:
        local n = tbl[i]['spawn_weights.weight']

        -- Truncated value:
        tbl[i]['spawn_weights.chance'] = string.format(
            "%0.2f%%", 
            n/N * chance_multiplier*100
        )
    end 
    
    return tbl
end
    
function p.drop_down_table(frame)
    --[[
    This function queries mods that can spawn on an item. It compares 
    the item tags and the spawn weight tags. If there's a match and 
    the spawn weight is larger than zero, then that mod is added to a 
    drop down list.
    
    To Do:
    * Add support to:
        * Forsaken masters
        * Elder mods
        * Bestiary
    * Add a proper expand/collapse toggle for the entire header row so 
        it reacts together with mw-collapsible. 
    * Show Mod group in a better way perhaps: 
        Mod group [Collapsible, default=Expanded]
            # to Damage [Collapsible, default=Collapsed]
                3 to Damage
                5 to Damage
    * Add a where condition that somehow filters out mods that obviously 
      wont match with the item. spawn_weights.weight>0 isn't enough due 
      to possible edge cases.
    * Consider moving the where variable to section table.
		
    
    Examples: 
    Weapons
    p.drop_down_table{item = 'Rusted Hatchet', header = 'One Handed Axes'}
    p.drop_down_table{item = 'Stone Axe', header = 'Two Handed Axes'}

    Accessories
    p.drop_down_table{item = 'Amber Amulet', header = 'Amulets'}
    
    Jewels
    p.drop_down_table{item = 'Cobalt Jewel', header = 'Jewels'}
    
    Armour
    p.drop_down_table{item = 'Plate Vest', header = 'Body armours'}
    
    Boots
    p.drop_down_table{item = 'Iron Greaves', header = 'Boots'}
    
    p.drop_down_table{
        item = 'Fishing Rod', 
        header = 'FISH PLEASE', 
        item_tags = 'fishing_rod', 
        extra_fields = 'Has spawn weight, Has spawn chance'
    }
    
    = p.drop_down_table{
        item = 'Fishing Rod',
        item_tags = 'axe, one_hand_weapon, onehand, weapon, default',
        extra_item_tags = 'fishing_rod'
    }
	
    = p.drop_down_table{
        item = 'Vaal Blade',
    }
        
    ]]

    -- Get template arguments:
    local tpl_args = getArgs(frame, {parentFirst=true})
    local frame = m_util.misc.get_frame(frame)
    
    -- Get the items tags:
    local get_item_tags = h.get_item_tags(tpl_args)[1]
    
    -- For some reason cargo queried item tags, are not comma-space 
    -- separated any more.
    local item_tags = {}
    if tpl_args.item_tags ~= nil then
        item_tags = m_util.string.split(tpl_args.item_tags, ', ')
    else
        item_tags = m_util.string.split(get_item_tags['items.tags'], ',')
    end
    if tpl_args.extra_item_tags then
        local extra_item_tags = m_util.string.split(tpl_args.extra_item_tags, ', ')
        for _,v in ipairs(extra_item_tags) do 
            item_tags[#item_tags+1] = v
        end
    end
    -- local item_tags = m_util.string.split(
        -- table.concat(
            -- {
                -- tpl_args.item_tags or get_item_tags['items.tags'],
                -- tpl_args.extra_item_tags,
            -- }, 
            -- ', '
        -- ), 
         -- ', '
    -- )
    
    
    -- Create drop down lists in these sections and query in these
    -- generation types.
    local section = {}
    section = {
        {
            header = i18n.drop_down_table.prefix,
            generation_type = 1,
        },
        {
            header = i18n.drop_down_table.suffix,
            generation_type = 2,
        },
        {
            header = i18n.drop_down_table.corrupted,
            generation_type = 5,
            chance_multiplier = 1/4, -- See Vaal orb, for the 4 possible events. 
        },
        {
            header = i18n.drop_down_table.enchant,
            generation_type = 10,
        },
        -- {
            -- header = 'Forsaken masters',
            -- generation_type = 'master',    
        -- },
    }
      
       
    -- Introductory text:
    local out = {}
    out[#out+1] = string.format(
        '==%s== \n', 
        tpl_args['header'] or table.concat(item_tags, ', ')
    )
    out[#out+1] = string.format(
        '<div style="float: right; text-align:center"><div class="mw-collapsible-collapse-all" style="cursor:pointer;">[%s]</div><hr><div class="mw-collapsible-expand-all" style="cursor:pointer;">[%s]</div></div>',
        i18n.drop_down_table.collapse_all,
        i18n.drop_down_table.expand_all
    )
    out[#out+1] = string.format('%s %s.<br><br><br>', 
        i18n.drop_down_table.table_intro, 
        f_item_link{
            page=get_item_tags['items._pageName'], 
            name=get_item_tags['items.name'],
            inventory_icon=get_item_tags['items.inventory_icon'] or '', 
            html=get_item_tags['items.html'] or '', 
            skip_query=true
        }
    )
    
    local item_mods = {}
    local mod_group_counter = {}
    mod_group_counter['all'] = {}
    local tableIndex = -1   
    for _, sctn in ipairs(section) do
        local container = mw.html.create('div')
            :attr('style', 'vertical-align:top; display:inline-block;')
    
        -- Format the where condition:
        local generation_type = sctn['generation_type']
        local where = {}
        for _, item_tag in ipairs(item_tags) do 
            where[#where+1] = string.format(
            '(spawn_weights.tag="%s" AND mods.generation_type=%s AND mods.domain=%s)',
            item_tag,
            sctn['generation_type'],
            get_item_tags['items.domain']
        )
        end
        
        tpl_args.q_tables = 'mods, spawn_weights, mod_stats'
        tpl_args.q_fields = 'mods.name, mods.id, mods.required_level, mods.generation_type, mods.domain, mods.mod_group, mods.mod_type, mods.stat_text, mods._pageName, mod_stats.id, spawn_weights.tag, spawn_weights.weight, spawn_weights.ordinal, spawn_weights._pageName'
        tpl_args.q_join = 'mods._pageName=spawn_weights._pageName, mods._pageName=mod_stats._pageName'
        tpl_args.q_where = table.concat(where, ' OR ')
        tpl_args.q_groupBy = 'mods._pageName, spawn_weights.tag, spawn_weights.weight'
        tpl_args.q_orderBy = 'mods.generation_type, mods.mod_group, mods.mod_type, mods._pageName, mods.required_level, spawn_weights.ordinal'
        
        local extra_fields = {}
        if tpl_args.extra_fields ~= nil then
            extra_fields = m_util.string.split(tpl_args.extra_fields, ', ')
            tpl_args.fields = string.format(
                '%s, %s', 
                tpl_args.fields, 
                table.concat(extra_fields, ', ')
            )
        end
        
        -- Query mods:
        results = h.cargo_query(tpl_args)
		
        -- Create own list for spawn weights and group by page name:
        local spawn_weights = {}
		local results_unique = {}
		local hash = {}
        for _,v in ipairs(results) do
            if spawn_weights[v['mods._pageName']] == nil then 
                spawn_weights[v['mods._pageName']] = {}
            end
            local n = #spawn_weights[v['mods._pageName']] or 0
            spawn_weights[v['mods._pageName']][n+1] = v
			
            -- Get a sorted list that only has unique page names:
            if hash[v['mods._pageName']] ~= true then
                results_unique[#results_unique+1] = v
                hash[v['mods._pageName']] = true
            end
        end
        
        if #results_unique > 0 then 
            item_mods[generation_type] = {}
            mod_group_counter[generation_type] = {}
                      
            -- Loop through all found modifiers:
            local last
            for _, v in ipairs(results_unique) do   
                local pagename = v['spawn_weights._pageName']
                
                -- Loop through all the modifier tags until they match 
                -- the item tags:
                local j = 0
                local tag_match_stop
                repeat 
                    j = j+1
                    local mod_tag = spawn_weights[pagename][j]['spawn_weights.tag']
                    local mod_tag_weight = tonumber(
                        spawn_weights[pagename][j]['spawn_weights.weight']
                    )

                    -- Loop through the item tags until it matches the 
                    -- spawn weight tag and the mod tag has a value larger than 
                    -- zero:
                    local y = 0
                    local tag_match_add = false
                    repeat     
                        y = y+1
                        tag_match_stop = ((mod_tag == item_tags[y]) and ((mod_tag_weight or -1) >= 0)) or (spawn_weights[pagename][j] == nil)
                        tag_match_add =   (mod_tag == item_tags[y]) and ((mod_tag_weight or -1) > 0)
                    until tag_match_stop or y == #item_tags
                    
                    -- If there's a match then save that mod and other 
                    -- interesting information:
                    if tag_match_add then 
                        
                        -- Assume that the mod is global then go through 
                        -- all the stat ids and check if any of the 
                        -- stats are local:
                        local mod_scope = 'Global' 
                        for _, vv in ipairs(spawn_weights[pagename]) do 
                            if vv['mod_stats.id']:find('.*local.*') ~= nil then 
                                mod_scope = 'Local'
                            end
                        end 
                        
                        -- Save the matching modifier tag:
                        local a = #item_mods[generation_type]
                        item_mods[generation_type][a+1] = spawn_weights[pagename][j]
                        
                        -- Save other interesting fields:
                        item_mods[generation_type][a+1]['mods.scope'] = mod_scope     
                        item_mods[generation_type][a+1]['spawn_weight.idx_match'] = j                        
						item_mods[generation_type][a+1]['mods.add'] = tag_match_add     
						item_mods[generation_type][a+1]['mods.stop'] = tag_match_stop
                        -- Mod group counter:
                        local group = item_mods[generation_type][a+1]['mods.mod_group'] or 'nil_group'
                        mod_group_counter[generation_type][group] = 1 + (mod_group_counter[generation_type][group] or 0)
                        mod_group_counter['all'][group] = 1 + (mod_group_counter['all'][group] or 0)
                    end
                until tag_match_stop 
            end
            
            -- If the user wants to see the spawn chance then do the 
            -- calculations and save that result as well:
            if tpl_args.spawn_chance ~= nil then 
                extra_fields[#extra_fields+1] = 'spawn_weights.chance'
                item_mods[generation_type] = h.get_spawn_chance{
                    tbl = item_mods[generation_type], 
                    chance_multiplier = sctn['chance_multiplier']
                }
            end
            
            -- Create the drop down table with <table></table>:
            local headers = container 
            if #item_mods[generation_type] > 0 then 
                headers
                    :tag('h3')
                        :wikitext(string.format(
                            '%s', 
                            sctn['header']
                            )
                        )
                        :done()
                    :done()
            end
            
            local total_mod_groups = 0
            for k,v in pairs(mod_group_counter[generation_type]) do
                total_mod_groups = 1+total_mod_groups
            end
                
            -- Loop through and add all matching mods to the <table>. 
            local tbl, last_group, last_type
            for _, rows in ipairs(item_mods[generation_type]) do   
            
                -- If the last mod group is different to the current 
                -- mod group then assume the mod isn't related and start 
                -- a new drop down list, if there's only one mod group 
                -- then use mod type instead:
                if rows['mods.mod_group'] ~= last_group or (total_mod_groups == 1 and rows['mods.mod_type'] ~= last_type) then
                    -- Check through all the mods and see if there are 
                    -- multiple mod types within the same mod group:
                    local count = {}
                    for _, n in ipairs(item_mods[generation_type]) do 
                        
                        -- If the mod has the same mod group, then add 
                        -- the mod type to the counter. Only unique mod 
                        -- types matter so the number is just a dummy 
                        -- value:
                        if n['mods.mod_group'] == rows['mods.mod_group'] then
                            count[n['mods.mod_type']] = 1 
                        end
                    end
                    
                    -- Calculate how many unique mod types with the 
                    -- same mod group there are:
                    local number_of_mod_types = 0
                    for _ in pairs(count) do 
                        number_of_mod_types = number_of_mod_types + 1 
                    end
                    
                    -- If there are multiple unique mod types with the 
                    -- same mod group then change the style of the drop 
                    -- down list to indicate it, if there's only one 
                    -- mod group in the generation type then ignore it:
                    if number_of_mod_types > 1 and total_mod_groups > 1 then 
                        tbl_caption = string.format(
                            '%s', 
                            m_util.html.poe_color(
                                'mod', 
                                string.format(
                                    '%s %s', 
                                    i18n.drop_down_table.mod_group, 
                                    rows['mods.mod_group']
                                )
                            )
                        )

                    else
                        tbl_caption = string.format(
                            '%s (%s)', 
                            m_util.html.poe_color(
                                'mod', 
                                h.header(rows['mods.stat_text'])
                            ), 
                            rows['mods.scope']
                        )
                    end
                    
                    -- Add class and style to the <table>:
                    tableIndex = tableIndex+1
                    tbl = container:tag('table')
                    tbl
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr('style', 
                            'text-align:left; line-height:1.60em; width:810px;'
                        )
                        :tag('th')
                            :attr('class', 
                                string.format(
                                    'mw-customtoggle-%s', 
                                    tableIndex
                                )
                            )
                            :attr('style', 
                                'text-align:left; line-height:1.40em; border-bottom:1pt solid dimgrey;'
                            )
                            :attr('colspan', '3' .. #extra_fields)
                            :wikitext(tbl_caption)
                            :done()
                        :done()    
                end
                
                -- If the mod has no name then use the mod type:
                local mod_name = rows['mods.name']
                if  mod_name == '' or mod_name == nil then 
                    mod_name = rows['mods.mod_type']
                end
                
                -- Check if there are any extra properties to show in 
                -- the drop down list and then add a cell for that, 
                -- add this node at the end of the table row:
                local td = mw.html.create('td')
                if extra_fields ~= nil then 
                    for _, extra_field in ipairs(extra_fields) do 
                        td
                            :attr('width', '*')
                            :wikitext(string.format(
                                '%s:&nbsp;%s ', 
                                extra_field, 
                                rows[extra_field]
                                )
                            )
                            :done()
                    end
                end
                
                -- Add a table row with the interesting properties that 
                -- modifier has:
                tbl
                    :tag('tr')
                        :attr('class', 'mw-collapsible mw-collapsed')
                        :attr(
                            'id', 
                            string.format(
                                'mw-customcollapsible-%s', 
                                tableIndex
                            )
                        )
                        :tag('td')
                            :attr('width', '160')
                            :wikitext(
                                string.format(
                                    '&nbsp;&nbsp;&nbsp;[[%s|%s]]', 
                                    rows['mods._pageName'], 
                                    mod_name:gsub('%s', '&nbsp;')
                                )
                            )
                            :done()
                        :tag('td')
                            :attr('width', '1')
                            :wikitext(
                                string.format(
                                    '%s&nbsp;%s',
                                    m_game.level_requirement['short_upper']
                                        :gsub('%s', '&nbsp;'), 
                                    rows['mods.required_level']
                                )
                            )
                            :done()        
                        :tag('td')
                            :attr('width', '*')
                            :wikitext(
                                string.format(
                                    '%s', 
                                    m_util.html.poe_color(
                                        'mod', 
                                        rows['mods.stat_text']
                                            :gsub('<br>', ', ')
                                            :gsub('<br />', ' ')
                                    )  
                                )
                            )
                            :done()
                        :node(td)
                        :done()
                    :done()
                
                -- Save the last mod group for later comparison:
                last_group = rows['mods.mod_group']
                last_type = rows['mods.mod_type']
            end
        end
        
        out[#out+1] = tostring(container)
    end
    
    return table.concat(out,'')
end


-- ----------------------------------------------------------------------------
-- Debug functions
-- ----------------------------------------------------------------------------

p.debug = {}
function p.debug.tbl_data(tbl)
    keys = {}
    for _, data in ipairs(mod_table.data) do
        if type(data.arg) == 'string' then 
            keys[data.arg] = 1
        elseif type(data.arg) == 'table' then
            for _, arg in ipairs(data.arg) do
                keys[arg] = 1
            end
        end
    end
    
    for _, key in ipairs(mod_table.weights) do
        keys[key] = 1
    end
    
    local out = {}
    for key, _ in pairs(keys) do
        out[#out+1] = string.format("['%s'] = '1'", key)
    end
    
    return table.concat(out, ', ')
end

-- ----------------------------------------------------------------------------
--
-- ----------------------------------------------------------------------------

return p