Permanently protected module

Difference between revisions of "Module:DM Fusion display"

From Yugipedia
Jump to: navigation, search
(The Fusion display for DOR cards works, but the lists were slowing down the articles, likely due to the superfluous queries for each material's properties. Creating a module which uses 1 query, hopefully to increase the speed, and as a learning experience)
 
(Fixed (yet another) sorting issue regarding pairs (yet again fixed by using ipairs). Fixed combinations for a same Fusion not being grouped if all of them had no description)
 
(18 intermediate revisions by the same user not shown)
Line 19: Line 19:
 
NAMESPACE,
 
NAMESPACE,
 
_frame,
 
_frame,
_args
+
_args,
 +
_fusionDisplay,
 +
_pageName,
 +
_combinationsQuery,
 +
_exceptionsQuery
 
;
 
;
  
Line 48: Line 52:
 
local function unlink( v, getLabel )
 
local function unlink( v, getLabel )
 
local function __unlink( v, getLabel )
 
local function __unlink( v, getLabel )
return _trim( v ) and (getLabel and v:match( '%[%[:?.-|(.-)]]' ) or v:match( '%[%[:?(.-)[|%]]' )) or _trim( v );
+
return UTIL.trim( v ) and (getLabel and v:match( '%[%[:?.-|(.-)]]' ) or v:match( '%[%[:?(.-)[|%]]' )) or UTIL.trim( v );
 
end
 
end
 
if type( v ) == 'string' then
 
if type( v ) == 'string' then
Line 63: Line 67:
 
end
 
end
  
-- @name format_cards_to_display
+
-- @name toTable
-- @description Receives a table with card page names and returns the expected strings to be displayed
+
-- @description Receives an object, and converts it to a table containing itself, unless it is already a table
local function format_cards_to_display(cards)
+
local function toTable(obj)
local display_table = {}
+
return type(obj) == "table" and obj or { obj }
for i, card in pairs(cards) do
+
end
attr = CARD_INFO[card]
+
 
display_table[i] = string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ), unlink(card), attr["name"])
+
-- @name formatCardDisplay
 +
-- @description Receives a card page name and returns the expected string to be displayed
 +
local function formatCardDisplay(card)
 +
attr = CARD_INFO[card]
 +
return string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ) or '', card, attr["name"])
 +
end
 +
 
 +
-- @name fillTableForFusionResult
 +
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Result section
 +
local function fillTableForFusionResult( currentTable, mats1, mats2, columns, mats_exceptions )
 +
local matsRow = currentTable:tag("tr")
 +
local mat1Cell = matsRow:tag("td"):tag("ul")
 +
local mat2Cell = matsRow:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
 +
 +
-- Prints the first cell
 +
for _, card in ipairs(mats1) do
 +
card = unlink(card)
 +
mat1Cell:tag("li"):wikitext(formatCardDisplay(card))
 +
end
 +
 +
-- The second cell also includes the "except with" addition
 +
for _, card in ipairs(mats2) do
 +
card = unlink(card)
 +
mat2Cell:tag("li"):wikitext(formatCardDisplay(card) .. (mats_exceptions[card] and string.format(" (except with \"[[%s|%s]]\")", mats_exceptions[card], CARD_INFO[mats_exceptions[card]]["name"]) or ''))
 +
end
 +
 +
return currentTable
 +
end
 +
 
 +
-- @name fillTableForFusionMaterial
 +
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Material section
 +
local function fillTableForFusionMaterial( currentTable, mats, columns, excepts )
 +
local matsRow = currentTable:tag("tr"):tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
 +
 
 +
-- Print other materials, but don't include those present in the exceptions table
 +
for _, card in ipairs(mats) do
 +
if not excepts[card] then
 +
card = unlink(card)
 +
matsRow:tag("li"):wikitext(formatCardDisplay(card))
 +
end
 +
end
 +
 +
return currentTable
 +
end
 +
 
 +
-- @name runCombinationExceptionsQuery
 +
-- @description Gets the combinations exceptions for a query of combinatigons
 +
local function runCombinationExceptionsQuery()
 +
local fusion_ids = {}
 +
for _, combination in ipairs(_combinationsQuery) do
 +
table.insert(fusion_ids, unlink(combination[1]))
 
end
 
end
return display_table
+
 +
local query = mw.smw.ask( string.format("[[For Fusion::%s]] |?Fusion Material exception 1 |?Fusion Material exception 2 |?For Fusion", table.concat(fusion_ids, "||")) ) or {}
 +
 +
return query
 
end
 
end
  
-- @name run_fusion_combination_query
+
-- @name processCombinationsQuery
 
-- @description Gets the required attributes for a Fusion query by running the provided conditions.  
 
-- @description Gets the required attributes for a Fusion query by running the provided conditions.  
 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
local function process_fusion_combination_query(conditions)
+
local function processCombinationsQuery(conditions)
 
-- The attributes that should be returned
 
-- The attributes that should be returned
 
local required_attributes = {  
 
local required_attributes = {  
"Fusion Material 1", "Fusion Material 2", "Description", "Columns", -- Attributes about the Fusion combination
+
"Fusion Material 1", "Fusion Material 2", "Fusion result", "Description", "Columns",                                                                                                 -- Attributes about the Fusion combination
"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", -- Attributes about each card listed as Material 1
+
"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", "Fusion Material 1.Card type = Card type 1", -- Attributes about each card listed as Material 1
"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2" -- Attributes about each card listed as Material 2
+
"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2", "Fusion Material 2.Card type = Card type 2", -- Attributes about each card listed as Material 2
 +
"Fusion result.English name = English name 3",    "Fusion result.Level = Level 3",    "Fusion result.Card number = Card number 3",    "Fusion result.Card type = Card type 3",    -- Attributes about each card listed as Result
 +
"Fusion result = Fusion Material 3"                                               -- Small hack to help iterate below
 
}
 
}
 
 
local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?"))
+
local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?")) or {}
 +
local nonMonsterOffset
 
 
 
-- Iterates over both Fusion Material lists, and adds each card to the table
 
-- Iterates over both Fusion Material lists, and adds each card to the table
for _, combination in pairs(query) do
+
for _, combination in ipairs(query) do
mw.logObject(combination)
+
for combi_index = 1,3 do
for _, combi_index in pairs({"1", "2"}) do
+
-- MW doesn't return multiple-valued properties as tables, so we convert them here
-- Pre-processing on the multi-value attributes
+
materials = toTable(combination["Fusion Material " .. combi_index])
materials = combination["Fusion Material " .. combi_index]
+
en_names = toTable(combination["English name " .. combi_index])
materials = type(materials) == "table" and materials or { materials }
+
levels    = toTable(combination["Level " .. combi_index])
+
numbers  = toTable(combination["Card number " .. combi_index])
en_names = combination["English name " .. combi_index]
+
types    = toTable(combination["Card type " .. combi_index])
en_names = type(en_names) == "table" and en_names or { en_names }
 
 
levels = combination["Level " .. combi_index]
 
levels = type(levels) == "table" and levels or { levels }
 
 
 
number = combination["Card number " .. combi_index]
+
-- Since non-Monsters don't have Levels, the properties get returned with some lines missing for Monster-specific properties (e.g. Level)
number = type(number) == "table" and number or { number }
+
-- This variable is used to offset that
 +
nonMonsterOffset = 0
 
 
    for i, fm in pairs(materials) do
+
    for i, fm in ipairs(materials) do
 +
    fm = unlink(fm)
 
    if not CARD_INFO[fm] then
 
    if not CARD_INFO[fm] then
    CARD_INFO[fm] = {}
+
    CARD_INFO[fm] = {
    CARD_INFO[fm]["name"] = en_names[i]
+
    ["name"]   = en_names[i],
    CARD_INFO[fm]["level"] = levels[i]
+
    ["level"]  = levels[i-nonMonsterOffset],
    CARD_INFO[fm]["number"] = number[i]
+
    ["number"] = numbers[i]
    end
+
    }
 +
end
 +
if unlink(types[i]) ~= 'Monster Card' then
 +
    nonMonsterOffset = nonMonsterOffset + 1
 +
    CARD_INFO[fm]["level"] = nil
 +
    end
 
     end
 
     end
    end
+
end
    end
+
end
+
 
 
return query
 
return query
 
end
 
end
  
-- @name fill_fusion_materials_table
+
-- @name setListsForFusionMaterialDisplay
-- @description Appends the row with the materials belonging to the current combination to a table
+
-- @description Creates the material and exception lists for a Fusion Material display
local function fill_fusion_materials_table( cur_table, mats1, mats2, columns )
+
local function setListsForFusionMaterialDisplay(mats1, mats2)
local mats_row = cur_table:tag("tr")
+
local materialIndex, mats
mats_row:tag("td"):wikitext("\n* " .. table.concat(format_cards_to_display(mats1), "\n* "))
+
local excepts = {}
mats_row:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):wikitext("\n* " .. table.concat(format_cards_to_display(mats2), "\n* "))
+
 +
-- Checks to which of the two lists should be displayed (if a card belongs to Materials 2, display Materials 1)
 +
for _, mat in ipairs(mats1) do
 +
if unlink(mat) == _pageName then
 +
materialIndex = 2
 +
break
 +
end
 +
end
 +
 +
-- Also checks the second list to see if the material is present there
 +
if materialIndex then
 +
for _, mat in ipairs(mats2) do
 +
if unlink(mat) == _pageName then
 +
materialIndex = materialIndex + 1
 +
break
 +
end
 +
end
 +
else
 +
materialIndex = 1
 +
end
 
 
return cur_table
+
-- Assigns correct material and exception list, based on the index found
 +
-- Index 3 means that it was found in both lists
 +
if materialIndex == 1 or materialIndex == 3 then
 +
mats = mats1
 +
for _, combi_ex in ipairs(_exceptionsQuery) do
 +
if unlink(combi_ex["Fusion Material exception 1"]) == _pageName then
 +
excepts[combi_ex["Fusion Material exception 2"]] = true
 +
end
 +
end
 +
end
 +
 
 +
if materialIndex == 2 or materialIndex == 3 then
 +
if not mats then mats = mats2
 +
else
 +
-- Joins both tables
 +
for _, m2 in ipairs(mats2) do
 +
if unlink(m2) ~= _pageName then -- Don't include the same article twice
 +
table.insert(mats, m2)
 +
end
 +
end
 +
end
 +
 +
for _, combi_ex in ipairs(_exceptionsQuery) do
 +
if unlink(combi_ex["Fusion Material exception 2"]) == _pageName then
 +
excepts[combi_ex["Fusion Material exception 1"]] = true
 +
end
 +
end
 +
end
 +
 
 +
return mats, excepts
 
end
 
end
  
-- @name fusion_result
+
-- @name createFusionResultTable
-- @description Generates the materials lists for fusion result. To be called through #invoke.
+
-- @description Creates the table to be displayed for Fusion Result monsters
local function fusion_result( frame )
+
local function createFusionResultTable()
_frame = frame;
+
local previousDescription, currentTable
_args  = UTIL.getArgs( frame, {
 
trim        = true,
 
removeBlanks = true,
 
parentOnly  = true
 
} );
 
 
 
if not mw.smw then
 
        return "mw.smw module not found"
 
    end
 
   
 
    local fusion_frame = HTML()
 
   
 
    local page_name = _args[1] or "Bracchio-raidus (DOR)"
 
   
 
    local query = process_fusion_combination_query( string.format("[[Fusion result::%s]]", page_name ))
 
   
 
    local prev_description, cur_table
 
 
      
 
      
     for i, combination in pairs(query) do
+
     -- Creates tables/rows for each combination found
     local mats1, mats2, description, columns = combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
+
    for _, combination in ipairs(_combinationsQuery) do
 +
     local combi_id, mats1, mats2, description, columns = combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
 +
    local combination_exceptions = {}
 
    
 
    
 
     -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 
     -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
     mats1 = type(mats1) ~= 'table' and { mats1 } or mats1
+
     mats1 = toTable(mats1)
     mats2 = type(mats2) ~= 'table' and { mats2 } or mats2
+
     mats2 = toTable(mats2)
 +
 +
for _, combi_ex in ipairs(_exceptionsQuery) do
 +
if combi_ex["For Fusion"] == combi_id then
 +
combination_exceptions[unlink(combi_ex["Fusion Material exception 1"])] = unlink(combi_ex["Fusion Material exception 2"])
 +
end
 +
end
 
    
 
    
 
     -- Creates a new table on the first itteration, and when a description changes
 
     -- Creates a new table on the first itteration, and when a description changes
if not cur_table or description ~= prev_description then
+
if not currentTable or description ~= previousDescription then
cur_table = fusion_frame:tag("table"):attr("class", "wikitable")
+
currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
+
currentTable:tag("ul"):tag("li"):wikitext(description or '')
cur_table:wikitext(description)
 
 
local header = cur_table:tag("tr")
 
 
 
 +
local header = currentTable:tag("tr")
 
header:tag("th"):attr("scope", "col"):wikitext("Material 1")
 
header:tag("th"):attr("scope", "col"):wikitext("Material 1")
 
header:tag("th"):attr("scope", "col"):wikitext("Material 2")
 
header:tag("th"):attr("scope", "col"):wikitext("Material 2")
Line 170: Line 270:
 
    
 
    
 
     -- Appends materials to the table
 
     -- Appends materials to the table
     cur_table = fill_fusion_materials_table(cur_table, mats1, mats2, columns)
+
     currentTable = fillTableForFusionResult(currentTable, mats1, mats2, columns, combination_exceptions)
 
    
 
    
     prev_description = description or prev_description
+
     previousDescription = description
 
     end
 
     end
 +
end
 +
 +
-- @name createFusionMaterialTable
 +
-- @description Creates the table to be displayed for Fusion Material monsters
 +
local function createFusionMaterialTable()
 +
local previousDescription, previousResult, currentTable
 +
   
 +
    -- Creates tables/rows for each combination found
 +
    for _, combination in ipairs(_combinationsQuery) do
 +
    local combi_id, mats1, mats2, result, description, columns =
 +
    combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Fusion result"], combination["Description"], combination["Columns"] or 1
 +
    local cur_combi_exceptions = {}
 +
 +
    -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 +
    mats1 = toTable(mats1)
 +
    mats2 = toTable(mats2)
 +
   
 +
    -- Build the materials and exceptions lists
 +
    local mats, excepts = setListsForFusionMaterialDisplay(mats1, mats2)
 +
   
 +
    -- Dynamically calculate the number of columns
 +
    if #mats > 17 then
 +
    columns = 3
 +
    elseif #mats > 8 then
 +
    columns = 2
 +
    end
 +
   
 +
    -- Creates a new table on the first itteration, and when a description changes
 +
if not currentTable or description ~= previousDescription or result ~= previousResult then
 +
mw.log(result .. ": " .. (description or 'nil') .. " " .. (previousDescription or 'nil'))
 +
currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
 +
currentTable:tag("ul"):tag("li"):wikitext(formatCardDisplay(unlink(result)) .. (description and (" (" .. description .. ')') or ''))
 +
currentTable:tag("tr"):tag("th"):attr("scope", "col"):wikitext("Materials")
 +
end
 +
   
 +
    -- Appends materials to the table
 +
    currentTable = fillTableForFusionMaterial(currentTable, mats, columns, excepts)
 +
   
 +
    previousDescription = description
 +
    previousResult = result or previousResult
 +
    end
 +
end
 +
 +
-- @name getCombinationsAndExceptionsForFusionResult
 +
-- @description Gets the materials combinations and exceptions that result in this card
 +
local function getCombinationsAndExceptionsForFusionResult()
 +
    _combinationsQuery = processCombinationsQuery( string.format("[[Fusion result::%s]]", _pageName ) ) or {}
 +
    _exceptionsQuery = runCombinationExceptionsQuery()
 +
end
 +
 +
-- @name getCombinationsAndExceptionsForFusionMaterial
 +
-- @description Gets the materials combinations and exceptions that this card is a material of
 +
local function getCombinationsAndExceptionsForFusionMaterial()
 +
    _combinationsQuery = processCombinationsQuery( string.format("<q>[[Fusion Material 1::%s]] OR [[Fusion Material 2::%s]]</q>|sort = Fusion result.Card number", _pageName, _pageName ) ) or {}
 +
    _exceptionsQuery = runCombinationExceptionsQuery()
 +
end
 +
 +
-- @name initParams
 +
-- @description Sets values for all parameters accessible from the current context
 +
local function initParams( frame )
 +
_frame = frame;
 +
_args  = UTIL.getArgs( frame, {
 +
trim        = true,
 +
removeBlanks = true,
 +
parentOnly  = true
 +
} );
 +
   
 +
    _fusionDisplay = HTML()
 +
    _pageName = _args[1] or "Mavelus (FMR)"
 +
end
 +
 +
-- @name fusionResult
 +
-- @description Generates the material lists for a given Fusion result. To be called through #invoke.
 +
local function fusionResult( frame )
 +
initParams(frame)
 +
 +
    getCombinationsAndExceptionsForFusionResult()
 +
   
 +
    createFusionResultTable()
 +
 +
return tostring(_fusionDisplay)
 +
end
 +
 +
-- @name fusionMaterial
 +
-- @description Generates the list of other materials and Fusion result for a given material. To be called through #invoke.
 +
local function fusionMaterial( frame )
 +
initParams(frame)
 +
   
 +
    getCombinationsAndExceptionsForFusionMaterial()
 +
   
 +
    createFusionMaterialTable()
  
return tostring(fusion_frame)
+
return tostring(_fusionDisplay)
 
end
 
end
  
Line 183: Line 374:
 
-- @exports  
 
-- @exports  
 
return {
 
return {
['fusion_result'] = fusion_result
+
['fusionResult'] = fusionResult,
 +
['fusionMaterial'] = fusionMaterial
 
};
 
};
 
-- </pre>
 
-- </pre>

Latest revision as of 00:05, 22 May 2024

-- <pre>
-- @name DM Fusion display
-- @description A module used to display Fusion combinations in their respective card articles
-- @author [[User:JustGian]]
-- @contact [[User talk:JustGian]]

----------------
-- Load modules:
----------------
local DATA = require( 'Module:Data' );
local UTIL = require( 'Module:Util' );

--------------------
-- Module variables:
--------------------
-- Parser state:
local
	PAGENAME,
	NAMESPACE,
	_frame,
	_args,
	_fusionDisplay,
	_pageName,
	_combinationsQuery,
	_exceptionsQuery
;

-- Stores properties for cards to avoid making a new query each time they are needed
local CARD_INFO = {}

---------------------
-- Utility functions:
---------------------
-- mw functions:
local HTML = mw.html.create;

--------------------
-- Module functions:
--------------------
--# Local functions

--[[Doc
@function U unlink
@description (From Module:Card type) If a string is passed, unlinks
and returns the page name (not the label, unless «getLabel»).
If a table is passed, unlinks every instance of it
and returns the table with the page names for each respective entry.
@parameter {*} v Any value to unlink.
@parameter {boolean} getLabel Makes the function return the label instead.
@return {*} The unlinked value.
]]
local function unlink( v, getLabel )
	local function __unlink( v, getLabel )
		return UTIL.trim( v ) and (getLabel and v:match( '%[%[:?.-|(.-)]]' ) or v:match( '%[%[:?(.-)[|%]]' )) or UTIL.trim( v );
	end
	if type( v ) == 'string' then
		return __unlink( v, getLabel );
	elseif type( v ) == 'table' then
		local t = {};
		for key, value in ipairs( v ) do
			table.insert( t, __unlink( value, getLabel ) );
		end
		return t;
	else
		return v; -- @@@ error()
	end
end

-- @name toTable
-- @description Receives an object, and converts it to a table containing itself, unless it is already a table
local function toTable(obj)
	return type(obj) == "table" and obj or { obj }
end

-- @name formatCardDisplay
-- @description Receives a card page name and returns the expected string to be displayed
local function formatCardDisplay(card)
	attr = CARD_INFO[card]
	return string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ) or '', card, attr["name"])
end

-- @name fillTableForFusionResult
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Result section
local function fillTableForFusionResult( currentTable, mats1, mats2, columns, mats_exceptions )
	local matsRow = currentTable:tag("tr")
	local mat1Cell = matsRow:tag("td"):tag("ul")
	local mat2Cell = matsRow:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
	
	-- Prints the first cell
	for _, card in ipairs(mats1) do
		card = unlink(card)
		mat1Cell:tag("li"):wikitext(formatCardDisplay(card))
	end
	
	-- The second cell also includes the "except with" addition
	for _, card in ipairs(mats2) do
		card = unlink(card)
		mat2Cell:tag("li"):wikitext(formatCardDisplay(card) .. (mats_exceptions[card] and string.format(" (except with \"[[%s|%s]]\")", mats_exceptions[card], CARD_INFO[mats_exceptions[card]]["name"]) or ''))
	end
	
	return currentTable
end

-- @name fillTableForFusionMaterial
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Material section
local function fillTableForFusionMaterial( currentTable, mats, columns, excepts )
	local matsRow = currentTable:tag("tr"):tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")

	-- Print other materials, but don't include those present in the exceptions table
	for _, card in ipairs(mats) do
		if not excepts[card] then
			card = unlink(card)
			matsRow:tag("li"):wikitext(formatCardDisplay(card))
		end
	end
	
	return currentTable
end

-- @name runCombinationExceptionsQuery
-- @description Gets the combinations exceptions for a query of combinatigons
local function runCombinationExceptionsQuery()
	local fusion_ids = {}
	for _, combination in ipairs(_combinationsQuery) do
		table.insert(fusion_ids, unlink(combination[1]))
	end
	
	local query = mw.smw.ask( string.format("[[For Fusion::%s]] |?Fusion Material exception 1 |?Fusion Material exception 2 |?For Fusion", table.concat(fusion_ids, "||")) ) or {}
	
	return query
end

-- @name processCombinationsQuery
-- @description Gets the required attributes for a Fusion query by running the provided conditions. 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
local function processCombinationsQuery(conditions)
	-- The attributes that should be returned
	local required_attributes = { 
		"Fusion Material 1", "Fusion Material 2", "Fusion result", "Description", "Columns",                                                                                                  -- Attributes about the Fusion combination
		"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", "Fusion Material 1.Card type = Card type 1", -- Attributes about each card listed as Material 1
		"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2", "Fusion Material 2.Card type = Card type 2", -- Attributes about each card listed as Material 2
		"Fusion result.English name = English name 3",     "Fusion result.Level = Level 3",     "Fusion result.Card number = Card number 3",     "Fusion result.Card type = Card type 3",     -- Attributes about each card listed as Result
		"Fusion result = Fusion Material 3"																										                                              -- Small hack to help iterate below
		}
	
	local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?")) or {}
	local nonMonsterOffset
	
	-- Iterates over both Fusion Material lists, and adds each card to the table
	for _, combination in ipairs(query) do
		for combi_index = 1,3 do
			-- MW doesn't return multiple-valued properties as tables, so we convert them here
			materials = toTable(combination["Fusion Material " .. combi_index])
			en_names  = toTable(combination["English name " .. combi_index])
			levels    = toTable(combination["Level " .. combi_index])
			numbers   = toTable(combination["Card number " .. combi_index])
			types     = toTable(combination["Card type " .. combi_index])
			
			-- Since non-Monsters don't have Levels, the properties get returned with some lines missing for Monster-specific properties (e.g. Level)
			-- This variable is used to offset that
			nonMonsterOffset = 0
			
	    	for i, fm in ipairs(materials) do
	    		fm = unlink(fm)
	    		if not CARD_INFO[fm] then
	    			CARD_INFO[fm] = {
	    				["name"]   = en_names[i],
	    				["level"]  = levels[i-nonMonsterOffset],
	    				["number"] = numbers[i]
	    			}
				end
				if unlink(types[i]) ~= 'Monster Card' then
    				nonMonsterOffset = nonMonsterOffset + 1
    				CARD_INFO[fm]["level"] = nil
    			end
    		end
		end
	end

	return query
end

-- @name setListsForFusionMaterialDisplay
-- @description Creates the material and exception lists for a Fusion Material display
local function setListsForFusionMaterialDisplay(mats1, mats2)
	local materialIndex, mats
	local excepts = {}
	
	-- Checks to which of the two lists should be displayed (if a card belongs to Materials 2, display Materials 1)
	for _, mat in ipairs(mats1) do
		if unlink(mat) == _pageName then
			materialIndex = 2
			break
		end
	end
	
	-- Also checks the second list to see if the material is present there
	if materialIndex then
		for _, mat in ipairs(mats2) do
			if unlink(mat) == _pageName then
				materialIndex = materialIndex + 1
				break
			end
		end
	else
		materialIndex = 1
	end
	
	-- Assigns correct material and exception list, based on the index found
	-- Index 3 means that it was found in both lists
	if materialIndex == 1 or materialIndex == 3 then
		mats = mats1
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if unlink(combi_ex["Fusion Material exception 1"]) == _pageName then
				excepts[combi_ex["Fusion Material exception 2"]] = true
			end
		end
	end

	if materialIndex == 2 or materialIndex == 3 then
		if not mats then mats = mats2
		else
			-- Joins both tables
			for _, m2 in ipairs(mats2) do
				if unlink(m2) ~= _pageName then -- Don't include the same article twice
					table.insert(mats, m2)
				end
			end
		end
		
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if unlink(combi_ex["Fusion Material exception 2"]) == _pageName then
				excepts[combi_ex["Fusion Material exception 1"]] = true
			end
		end
	end

	return mats, excepts
end

-- @name createFusionResultTable
-- @description Creates the table to be displayed for Fusion Result monsters
local function createFusionResultTable()
	local previousDescription, currentTable
    
    -- Creates tables/rows for each combination found
    for _, combination in ipairs(_combinationsQuery) do
    	local combi_id, mats1, mats2, description, columns = combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
    	local combination_exceptions = {}
    	
    	-- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
    	mats1 = toTable(mats1)
    	mats2 = toTable(mats2)
		
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if combi_ex["For Fusion"] == combi_id then
				combination_exceptions[unlink(combi_ex["Fusion Material exception 1"])] = unlink(combi_ex["Fusion Material exception 2"])
			end
		end
    	
    	-- Creates a new table on the first itteration, and when a description changes
		if not currentTable or description ~= previousDescription then
			currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
			currentTable:tag("ul"):tag("li"):wikitext(description or '')
			
			local header = currentTable:tag("tr")
			header:tag("th"):attr("scope", "col"):wikitext("Material 1")
			header:tag("th"):attr("scope", "col"):wikitext("Material 2")
		end
    	
    	-- Appends materials to the table
    	currentTable = fillTableForFusionResult(currentTable, mats1, mats2, columns, combination_exceptions)
    	
    	previousDescription = description
    end
end

-- @name createFusionMaterialTable
-- @description Creates the table to be displayed for Fusion Material monsters
local function createFusionMaterialTable()
	local previousDescription, previousResult, currentTable
    
    -- Creates tables/rows for each combination found
    for _, combination in ipairs(_combinationsQuery) do
    	local combi_id, mats1, mats2, result, description, columns = 
    		combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Fusion result"], combination["Description"], combination["Columns"] or 1
    	local cur_combi_exceptions = {}

    	-- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
    	mats1 = toTable(mats1)
    	mats2 = toTable(mats2)
    	
    	-- Build the materials and exceptions lists
    	local mats, excepts = setListsForFusionMaterialDisplay(mats1, mats2)
    	
    	-- Dynamically calculate the number of columns
    	if #mats > 17 then
    		columns = 3
    	elseif #mats > 8 then
    		columns = 2
    	end
    	
    	-- Creates a new table on the first itteration, and when a description changes
		if not currentTable or description ~= previousDescription or result ~= previousResult then
			mw.log(result .. ": " .. (description or 'nil') .. " " .. (previousDescription or 'nil'))
			currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
			currentTable:tag("ul"):tag("li"):wikitext(formatCardDisplay(unlink(result)) .. (description and (" (" .. description .. ')') or ''))
			currentTable:tag("tr"):tag("th"):attr("scope", "col"):wikitext("Materials")
		end
    	
    	-- Appends materials to the table
    	currentTable = fillTableForFusionMaterial(currentTable, mats, columns, excepts)
    	
    	previousDescription = description
    	previousResult = result or previousResult
    end	
end

-- @name getCombinationsAndExceptionsForFusionResult
-- @description Gets the materials combinations and exceptions that result in this card
local function getCombinationsAndExceptionsForFusionResult()
    _combinationsQuery = processCombinationsQuery( string.format("[[Fusion result::%s]]", _pageName ) ) or {}
    _exceptionsQuery = runCombinationExceptionsQuery()
end

-- @name getCombinationsAndExceptionsForFusionMaterial
-- @description Gets the materials combinations and exceptions that this card is a material of
local function getCombinationsAndExceptionsForFusionMaterial()
    _combinationsQuery = processCombinationsQuery( string.format("<q>[[Fusion Material 1::%s]] OR [[Fusion Material 2::%s]]</q>|sort = Fusion result.Card number", _pageName, _pageName ) ) or {}
    _exceptionsQuery = runCombinationExceptionsQuery()
end

-- @name initParams
-- @description Sets values for all parameters accessible from the current context
local function initParams( frame )
	_frame = frame;
	_args  = UTIL.getArgs( frame, {
		trim         = true,
		removeBlanks = true,
		parentOnly   = true
	} );
    
    _fusionDisplay = HTML()
    _pageName = _args[1] or "Mavelus (FMR)"
end

-- @name fusionResult
-- @description Generates the material lists for a given Fusion result. To be called through #invoke.
local function fusionResult( frame )
	initParams(frame)
	
    getCombinationsAndExceptionsForFusionResult()
    
    createFusionResultTable()

	return tostring(_fusionDisplay)
end

-- @name fusionMaterial
-- @description Generates the list of other materials and Fusion result for a given material. To be called through #invoke.
local function fusionMaterial( frame )
	initParams(frame)
    
    getCombinationsAndExceptionsForFusionMaterial()
    
    createFusionMaterialTable()

	return tostring(_fusionDisplay)
end

----------
-- Return:
----------
-- @exports 
return {
	['fusionResult'] = fusionResult,
	['fusionMaterial'] = fusionMaterial
};
-- </pre>