DataTableParserV2: Difference between revisions
From Dune Awakening DB
Created page with "-- Module:DataTableParser -- Handles display and formatting of building data for Templates -- Updated to use External Data instead of parsing a wiki table page local p = {} -- Function to generate icon file reference for a resource local function getResourceIcon(resourceName) if not resourceName or resourceName == "" then return "" end local fileName = resourceName:gsub("%s+", "_") .. "_-_Icon.png" local fileTitle = mw.title.new("File:" .. fileN..." |
mNo edit summary |
||
| Line 1: | Line 1: | ||
-- Module: | -- Module:DataTableParserV2 | ||
-- Handles display and formatting of building data for Templates | -- Handles display and formatting of building data for Templates using External Data. | ||
-- | -- Data is fetched from your SQL tables (data_buildings and data_refining_recipes) | ||
-- via the ExternalData extension rather than from a wiki table page. | |||
local p = {} | local p = {} | ||
-- | -------------------------------------------------- | ||
-- Helper: Get icon file reference for a resource | |||
-------------------------------------------------- | |||
local function getResourceIcon(resourceName) | local function getResourceIcon(resourceName) | ||
if not resourceName or resourceName == "" then | if not resourceName or resourceName == "" then | ||
| Line 19: | Line 22: | ||
end | end | ||
-- Function | --------------------------------------------- | ||
-- Function: iconize | |||
-- Adds icons to resource links in text | |||
--------------------------------------------- | |||
function p.iconize(frame) | function p.iconize(frame) | ||
local text = frame.args[1] or "" | local text = frame.args[1] or "" | ||
| Line 33: | Line 39: | ||
end | end | ||
---------------------------------------------------------------------- | ------------------------------------------------------ | ||
-- | -- Function: formatComponent | ||
---------------------------------------------------------------------- | -- Formats a recipe component by adding icons to resource links. | ||
local function | ------------------------------------------------------ | ||
function p.formatComponent(text) | |||
if not text or text == "" then | |||
return "" | |||
end | |||
local components = mw.text.split(text, ";") | |||
local formatted = {} | |||
for i, component in ipairs(components) do | |||
component = mw.text.trim(component) | |||
local itemName, quantity = component:match("%[%[([^%]]+)%]%] x (%d+)") | |||
if itemName and quantity then | |||
local icon = getResourceIcon(itemName) | |||
table.insert(formatted, icon .. " [[" .. itemName .. "]] x " .. quantity) | |||
else | |||
table.insert(formatted, component) | |||
end | |||
end | |||
return table.concat(formatted, "<br>") | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: loadBuildingData | |||
-- Uses externaldata to fetch building data from data_buildings as a wiki table. | |||
-------------------------------------------------------------------------------- | |||
local function loadBuildingData(frame) | |||
local edQuery = [[ | local edQuery = [[ | ||
{{#get_external_data: source=externaldb | {{#get_external_data: source=externaldb | ||
|from=data_buildings | |from=data_buildings | ||
|data=Name=name,Description=description,JourneyRequirement=journey_requirement,Health=health,PowerCost=power_cost,GeneratesPower=generates_power,StorageSlots=storage_slots,StorageVolume=storage_capacity,RecipeToBuild=recipe_to_build,PlacedWith=placed_with,ImageFile=image_file,AdditionalNotes=additional_notes | |data=Name=name,Tier=building_type,Description=description,JourneyRequirement=journey_requirement,Health=health,PowerCost=power_cost,GeneratesPower=generates_power,StorageSlots=storage_slots,StorageVolume=storage_capacity,RecipeToBuild=recipe_to_build,PlacedWith=placed_with,ImageFile=image_file,AdditionalNotes=additional_notes | ||
|cache=yes | |cache=yes | ||
|where=name='{{PAGENAME}}' | |||
|limit=1 | |||
}} | }} | ||
{| class="wikitable" | {| class="wikitable" | ||
! Name | ! Name | ||
! Tier | |||
! Description | ! Description | ||
! JourneyRequirement | ! JourneyRequirement | ||
| Line 60: | Line 92: | ||
{{!}}- | {{!}}- | ||
{{!}} {{{Name}}} | {{!}} {{{Name}}} | ||
{{!}} {{{Tier}}} | |||
{{!}} {{{Description}}} | {{!}} {{{Description}}} | ||
{{!}} {{{JourneyRequirement}}} | {{!}} {{{JourneyRequirement}}} | ||
| Line 75: | Line 108: | ||
]] | ]] | ||
local content = frame:preprocess(edQuery) | local content = frame:preprocess(edQuery) | ||
if not content or content == "" then | if not content or content == "" then | ||
return nil, "Error: No building data returned from external source!" | return nil, "Error: No building data returned from external source!" | ||
end | end | ||
local lines = mw.text.split(content, "\n") | local lines = mw.text.split(content, "\n") | ||
local filtered = {} | local filtered = {} | ||
| Line 93: | Line 125: | ||
local inTable = false | local inTable = false | ||
local headers = {} -- table headers | local headers = {} -- store table headers | ||
local colMap = {} -- header name | local colMap = {} -- map header name to column index | ||
local data = {} -- array of row arrays | local data = {} -- array of row arrays | ||
local currentRow = {} | local currentRow = {} | ||
| Line 113: | Line 145: | ||
table.insert(headers, header) | table.insert(headers, header) | ||
elseif line:match("^|%+") then | elseif line:match("^|%+") then | ||
-- | -- skip table caption | ||
elseif line:match("^|}") then | elseif line:match("^|}") then | ||
if not readingHeader and #currentRow > 0 then | if not readingHeader and #currentRow > 0 then | ||
| Line 135: | Line 167: | ||
end | end | ||
---------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Function: loadRefiningData | ||
---------------------------------------------------------------------- | -- Uses externaldata to fetch refining recipes from data_refining_recipes. | ||
local function | -------------------------------------------------------------------------------- | ||
local function loadRefiningData(frame) | |||
local edQuery = [[ | local edQuery = [[ | ||
{{#get_external_data: source=externaldb | {{#get_external_data: source=externaldb | ||
| Line 144: | Line 177: | ||
|data=Refiner=Refiner,Output=Output,Ingredients=Ingredients,Time=Time,Recipe=Recipe | |data=Refiner=Refiner,Output=Output,Ingredients=Ingredients,Time=Time,Recipe=Recipe | ||
|cache=yes | |cache=yes | ||
|where=Refiner='{{PAGENAME}}' | |||
}} | }} | ||
{| class="wikitable" | {| class="wikitable" | ||
| Line 199: | Line 233: | ||
table.insert(headers, header) | table.insert(headers, header) | ||
elseif line:match("^|%+") then | elseif line:match("^|%+") then | ||
-- | -- skip caption | ||
elseif line:match("^|}") then | elseif line:match("^|}") then | ||
if not readingHeader and #currentRow > 0 then | if not readingHeader and #currentRow > 0 then | ||
| Line 225: | Line 259: | ||
end | end | ||
-- [ | -------------------------------------------------------------------------------- | ||
-- | -- Function: getBuildingData | ||
-- | -- Returns the row (and colMap) for a given building name from external data. | ||
-------------------------------------------------------------------------------- | |||
function p.getBuildingData(buildingName, frame) | |||
local buildingData, err = loadBuildingData(frame) | |||
if not buildingData then | |||
return nil, err | |||
end | |||
local colMap = buildingData.colMap | |||
local rows = buildingData.rows | |||
local nameCol = colMap["Name"] | |||
if not nameCol then | |||
return nil, "Error: 'Name' column not found in building data" | |||
end | |||
for _, fields in ipairs(rows) do | |||
local name = fields[nameCol] | |||
if name == buildingName then | |||
return fields, colMap | |||
end | |||
end | |||
return nil, "Building '" .. buildingName .. "' not found" | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: getRefiningRecipes | |||
-- Returns a table of refining recipes matching the building name. | |||
-------------------------------------------------------------------------------- | |||
function p.getRefiningRecipes(frame) | |||
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text | |||
local refiningData, err = loadRefiningData(frame) | |||
if not refiningData then | |||
return "Error loading refining data: " .. (err or "Unknown error") | |||
end | |||
local rows = refiningData.rows | |||
local resourceClassCol = 1 -- Resource Class (if applicable) | |||
local nameCol = 2 -- Output Name | |||
local timeCol = 4 -- Time | |||
local ingredientsCol = 3 -- Ingredients | |||
local recipeCol = 5 -- Recipe (if needed) | |||
local function normalizeText(text) | |||
if not text then return "" end | |||
text = text:gsub('["\']', '') | |||
text = text:gsub("%[%[([^%]]+)%]%]", "%1") | |||
return mw.text.trim(text) | |||
end | |||
local matchingRecipes = {} | |||
for _, row in ipairs(rows) do | |||
local refinerNeeded = row[1] or "" | |||
if normalizeText(refinerNeeded) == normalizeText(buildingName) then | |||
table.insert(matchingRecipes, row) | |||
end | |||
end | |||
if #matchingRecipes == 0 then | |||
return '<tr><td colspan="3" style="text-align:center;">No refining recipes found for this building.</td></tr>' | |||
end | |||
local output = "<tr>\n" .. | |||
"<th style=\"text-align:left;\">Output</th>\n" .. | |||
"<th style=\"text-align:left;\">Ingredients</th>\n" .. | |||
"<th style=\"text-align:left;\">Craft Time</th>\n" .. | |||
"</tr>\n" | |||
for _, recipe in ipairs(matchingRecipes) do | |||
local outputItem = recipe[nameCol] or "" | |||
local ingredients = recipe[ingredientsCol] or "" | |||
local time = recipe[timeCol] or "" | |||
local recipeQty = recipe[recipeCol] or "" | |||
local qty = "1" | |||
if recipeQty ~= "" then | |||
local extractQty = recipeQty:match("x%s*(%d+)") | |||
if extractQty then | |||
qty = extractQty | |||
end | |||
end | |||
local formattedOutput = p.iconize({args = {[1] = outputItem}}) | |||
if qty ~= "1" then | |||
formattedOutput = formattedOutput .. " × " .. qty | |||
end | |||
local formattedIngredients = "" | |||
if ingredients ~= "" then | |||
local ingredientsList = mw.text.split(ingredients, ";") | |||
for i, ingredient in ipairs(ingredientsList) do | |||
local trimmedIngredient = mw.text.trim(ingredient) | |||
if trimmedIngredient ~= "" then | |||
local formattedIngredient = p.iconize({args = {[1] = trimmedIngredient}}) | |||
if i > 1 then | |||
formattedIngredients = formattedIngredients .. "<br>" | |||
end | |||
formattedIngredients = formattedIngredients .. formattedIngredient | |||
end | |||
end | |||
end | |||
output = output .. "<tr>\n" .. | |||
"<td style=\"text-align:left;\">" .. formattedOutput .. "</td>\n" .. | |||
"<td style=\"text-align:left;\">" .. formattedIngredients .. "</td>\n" .. | |||
"<td style=\"text-align:left;\">" .. time .. "</td>\n" .. | |||
"</tr>\n" | |||
end | |||
return output | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: formatBuilding | |||
-- Formats building data as a template call to BuildingRefinerDisplayV2. | |||
-------------------------------------------------------------------------------- | |||
function p.formatBuilding(frame) | |||
local buildingName = frame.args[1] or frame.args.name or mw.title.getCurrentTitle().text | |||
if not buildingName then | |||
return "Error: No building name provided" | |||
end | |||
local building, colMap = p.getBuildingData(buildingName, frame) | |||
if not building then | |||
return "Error: Building '" .. buildingName .. "' not found" | |||
end | |||
local params = { | |||
Name = building[colMap["Name"]] or "", | |||
Tier = building[colMap["Tier"]] or "", | |||
Description = p.iconize({args = {[1] = building[colMap["Description"]] or ""}}), | |||
JourneyRequirement = p.iconize({args = {[1] = building[colMap["JourneyRequirement"]] or ""}}), | |||
Health = building[colMap["Health"]] or "", | |||
EnergyConsumption = building[colMap["PowerCost"]] or "", | |||
GeneratesPower = building[colMap["GeneratesPower"]] or "", | |||
StorageSlots = building[colMap["StorageSlots"]] or "", | |||
StorageVolume = building[colMap["StorageVolume"]] or "", | |||
Components = p.formatComponent(building[colMap["RecipeToBuild"]] or ""), | |||
PlacedWith = p.iconize({args = {[1] = building[colMap["PlacedWith"]] or ""}}), | |||
ImageFile = building[colMap["ImageFile"]] or "", | |||
AdditionalNotes = p.iconize({args = {[1] = building[colMap["AdditionalNotes"]] or ""}}), | |||
PrimarySource = "Crafting" | |||
} | |||
local templateCall = "{{BuildingRefinerDisplayV2" | |||
for key, value in pairs(params) do | |||
if value and value ~= "" then | |||
templateCall = templateCall .. "\n|" .. key .. "=" .. value | |||
end | |||
end | |||
templateCall = templateCall .. "\n}}" | |||
return frame:preprocess(templateCall) | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: displayBuilding | |||
-- A wrapper function that returns the fully formatted building display. | |||
-------------------------------------------------------------------------------- | |||
function p.displayBuilding(frame) | |||
return p.formatBuilding(frame) | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: getYoutubeEmbed | |||
-- Returns a processed YouTube embed tag. | |||
-------------------------------------------------------------------------------- | |||
function p.getYoutubeEmbed(frame) | |||
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text | |||
local width = frame.args.width or 400 | |||
local height = frame.args.height or 300 | |||
local dimensions = width .. "x" .. height | |||
local buildingData, err = loadBuildingData(frame) | |||
if not buildingData then | |||
return "Error loading data" | |||
end | |||
local rows = buildingData.rows | |||
local building = nil | |||
for _, row in ipairs(rows) do | |||
if row[1] == buildingName then | |||
building = row | |||
break | |||
end | |||
end | |||
if not building or not building[12] or building[12] == "-" or building[12] == "" then | |||
return "Coming Soon" | |||
end | |||
local youtubeUrl = building[12] | |||
local videoId = youtubeUrl:match("v=([%w-_]+)") | |||
if not videoId then | |||
return "Coming Soon" | |||
end | |||
local ytMarkup = string.format('<youtube dimensions="%s" alignment="center">%s</youtube>', dimensions, videoId) | |||
return frame:preprocess(ytMarkup) | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: breadcrumb | |||
-- Builds a breadcrumb trail for the current building. | |||
-------------------------------------------------------------------------------- | |||
function p.breadcrumb(frame) | |||
local buildingData, err = loadBuildingData(frame) | |||
if not buildingData then | |||
return err | |||
end | |||
local rows = buildingData.rows | |||
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text | |||
for _, row in ipairs(rows) do | |||
if row[1] == buildingName then | |||
local cat1 = row[11] or "" -- adjust index if needed | |||
local cat2 = row[12] or "" | |||
local cat3 = row[13] or "" | |||
local function cleanCategory(cat) | |||
if not cat or cat == "-" then return "" end | |||
if cat:find("%[%[Category:") then | |||
cat = cat:match("%[%[Category:%s*(.-)%s*%]%]") | |||
elseif cat:find("%[%[") then | |||
cat = cat:match("%[%[(.-)%]%]") | |||
end | |||
return mw.text.trim(cat) | |||
end | |||
cat1 = cleanCategory(cat1) | |||
cat2 = cleanCategory(cat2) | |||
cat3 = cleanCategory(cat3) | |||
local breadcrumbParts = {} | |||
if cat1 ~= "" then table.insert(breadcrumbParts, "[[" .. cat1 .. "]]") end | |||
if cat2 ~= "" then table.insert(breadcrumbParts, "[[" .. cat2 .. "]]") end | |||
if cat3 ~= "" then table.insert(breadcrumbParts, "[[" .. cat3 .. "]]") end | |||
table.insert(breadcrumbParts, "'''" .. buildingName .. "'''") | |||
return table.concat(breadcrumbParts, " > ") | |||
end | |||
end | |||
return "Breadcrumb not available" | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: getCategory3 | |||
-- Returns the Category3 field for the current building. | |||
-------------------------------------------------------------------------------- | |||
function p.getCategory3(frame) | |||
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text | |||
local buildingData, err = loadBuildingData(frame) | |||
if not buildingData then | |||
return "Buildings" | |||
end | |||
local rows = buildingData.rows | |||
for _, row in ipairs(rows) do | |||
if row[1] == buildingName then | |||
local cat3 = row[13] or "" | |||
if cat3 == "-" or cat3 == "" then | |||
return "Buildings" | |||
end | |||
if cat3:find("%[%[") then | |||
cat3 = cat3:match("%[%[(.-)%]%]") | |||
end | |||
return mw.text.trim(cat3) | |||
end | |||
end | |||
return "Buildings" | |||
end | |||
-------------------------------------------------------------------------------- | |||
-- Function: relatedBuildings | |||
-- Finds and formats related buildings based on Category3. | |||
-------------------------------------------------------------------------------- | |||
function p.relatedBuildings(frame) | |||
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text | |||
local buildingData, err = loadBuildingData(frame) | |||
if not buildingData then | |||
return "Error loading building data" | |||
end | |||
local rows = buildingData.rows | |||
local targetCategory = nil | |||
for _, row in ipairs(rows) do | |||
if row[1] == buildingName then | |||
targetCategory = row[13] or "" | |||
if targetCategory == "-" or targetCategory == "" then | |||
return "No related buildings found" | |||
end | |||
if targetCategory:find("%[%[") then | |||
targetCategory = targetCategory:match("%[%[(.-)%]%]") | |||
end | |||
targetCategory = mw.text.trim(targetCategory) | |||
break | |||
end | |||
end | |||
if not targetCategory then | |||
return "Building not found" | |||
end | |||
local relatedBuildings = {} | |||
for _, row in ipairs(rows) do | |||
if row[1] ~= buildingName then | |||
local category = row[13] or "" | |||
if category ~= "-" and category ~= "" then | |||
if category:find("%[%[") then | |||
category = category:match("%[%[(.-)%]%]") | |||
end | |||
category = mw.text.trim(category) | |||
if category == targetCategory then | |||
table.insert(relatedBuildings, row) | |||
end | |||
end | |||
end | |||
end | |||
if #relatedBuildings == 0 then | |||
return "No other " .. targetCategory .. " found" | |||
end | |||
local output = '<table class="infobox-dune" style="width:100%">\n' | |||
output = output .. "<tr>\n" | |||
output = output .. "<th style=\"text-align:left;\">Name</th>\n" | |||
output = output .. "<th style=\"text-align:left;\">Tier</th>\n" | |||
output = output .. "<th style=\"text-align:left;\">Description</th>\n" | |||
output = output .. "</tr>\n" | |||
for _, building in ipairs(relatedBuildings) do | |||
local name = building[1] or "" | |||
local tier = building[2] or "" | |||
local description = building[3] or "" | |||
if tier == "-" then tier = "" end | |||
if description == "-" then description = "" end | |||
local nameWithIcon = "[[" .. name .. "]]" | |||
local iconFile = building[11] or "" | |||
if iconFile ~= "-" and iconFile ~= "" then | |||
nameWithIcon = "[[File:" .. iconFile .. "|20px]] [[" .. name .. "]]" | |||
end | |||
output = output .. "<tr>\n" | |||
output = output .. "<td>" .. nameWithIcon .. "</td>\n" | |||
output = output .. "<td>" .. tier .. "</td>\n" | |||
output = output .. "<td>" .. description .. "</td>\n" | |||
output = output .. "</tr>\n" | |||
end | |||
output = output .. "</table>" | |||
return output | |||
end | |||
return p | return p | ||
Revision as of 13:37, 26 March 2025
Documentation for this module may be created at Module:DataTableParserV2/doc
-- Module:DataTableParserV2
-- Handles display and formatting of building data for Templates using External Data.
-- Data is fetched from your SQL tables (data_buildings and data_refining_recipes)
-- via the ExternalData extension rather than from a wiki table page.
local p = {}
--------------------------------------------------
-- Helper: Get icon file reference for a resource
--------------------------------------------------
local function getResourceIcon(resourceName)
if not resourceName or resourceName == "" then
return ""
end
local fileName = resourceName:gsub("%s+", "_") .. "_-_Icon.png"
local fileTitle = mw.title.new("File:" .. fileName)
if fileTitle and fileTitle.exists then
return "[[File:" .. fileName .. "|20px]]"
else
return ""
end
end
---------------------------------------------
-- Function: iconize
-- Adds icons to resource links in text
---------------------------------------------
function p.iconize(frame)
local text = frame.args[1] or ""
text = text:gsub("%[%[([^%]]+)%]%]", function(resourceName)
local icon = getResourceIcon(resourceName)
if icon ~= "" then
return icon .. " [[" .. resourceName .. "]]"
else
return "[[" .. resourceName .. "]]"
end
end)
return text
end
------------------------------------------------------
-- Function: formatComponent
-- Formats a recipe component by adding icons to resource links.
------------------------------------------------------
function p.formatComponent(text)
if not text or text == "" then
return ""
end
local components = mw.text.split(text, ";")
local formatted = {}
for i, component in ipairs(components) do
component = mw.text.trim(component)
local itemName, quantity = component:match("%[%[([^%]]+)%]%] x (%d+)")
if itemName and quantity then
local icon = getResourceIcon(itemName)
table.insert(formatted, icon .. " [[" .. itemName .. "]] x " .. quantity)
else
table.insert(formatted, component)
end
end
return table.concat(formatted, "<br>")
end
--------------------------------------------------------------------------------
-- Function: loadBuildingData
-- Uses externaldata to fetch building data from data_buildings as a wiki table.
--------------------------------------------------------------------------------
local function loadBuildingData(frame)
local edQuery = [[
{{#get_external_data: source=externaldb
|from=data_buildings
|data=Name=name,Tier=building_type,Description=description,JourneyRequirement=journey_requirement,Health=health,PowerCost=power_cost,GeneratesPower=generates_power,StorageSlots=storage_slots,StorageVolume=storage_capacity,RecipeToBuild=recipe_to_build,PlacedWith=placed_with,ImageFile=image_file,AdditionalNotes=additional_notes
|cache=yes
|where=name='{{PAGENAME}}'
|limit=1
}}
{| class="wikitable"
! Name
! Tier
! Description
! JourneyRequirement
! Health
! PowerCost
! GeneratesPower
! StorageSlots
! StorageVolume
! RecipeToBuild
! PlacedWith
! ImageFile
! AdditionalNotes
{{#for_external_table:|
{{!}}-
{{!}} {{{Name}}}
{{!}} {{{Tier}}}
{{!}} {{{Description}}}
{{!}} {{{JourneyRequirement}}}
{{!}} {{{Health}}}
{{!}} {{{PowerCost}}}
{{!}} {{{GeneratesPower}}}
{{!}} {{{StorageSlots}}}
{{!}} {{{StorageVolume}}}
{{!}} {{{RecipeToBuild}}}
{{!}} {{{PlacedWith}}}
{{!}} {{{ImageFile}}}
{{!}} {{{AdditionalNotes}}}
}}
|}
]]
local content = frame:preprocess(edQuery)
if not content or content == "" then
return nil, "Error: No building data returned from external source!"
end
local lines = mw.text.split(content, "\n")
local filtered = {}
for _, line in ipairs(lines) do
line = mw.text.trim(line)
if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
table.insert(filtered, line)
end
end
if #filtered < 2 then
return nil, "Error: No valid building data found!"
end
local inTable = false
local headers = {} -- store table headers
local colMap = {} -- map header name to column index
local data = {} -- array of row arrays
local currentRow = {}
local readingHeader = true
for _, line in ipairs(filtered) do
if line:match("^{|") then
inTable = true
elseif line:match("^|%-") then
if not readingHeader and #currentRow > 0 then
table.insert(data, currentRow)
end
currentRow = {}
readingHeader = false
elseif line:match("^!") then
local header = line:gsub("^!+%s*", "")
header = mw.text.trim(header)
table.insert(headers, header)
elseif line:match("^|%+") then
-- skip table caption
elseif line:match("^|}") then
if not readingHeader and #currentRow > 0 then
table.insert(data, currentRow)
end
inTable = false
elseif inTable and line:match("^|[^%-+}]") then
local cell = line:gsub("^|+%s*", "")
cell = mw.text.trim(cell)
if not readingHeader then
table.insert(currentRow, cell)
end
end
end
for i, h in ipairs(headers) do
colMap[mw.text.trim(h)] = i
end
return { rows = data, colMap = colMap }, nil
end
--------------------------------------------------------------------------------
-- Function: loadRefiningData
-- Uses externaldata to fetch refining recipes from data_refining_recipes.
--------------------------------------------------------------------------------
local function loadRefiningData(frame)
local edQuery = [[
{{#get_external_data: source=externaldb
|from=data_refining_recipes
|data=Refiner=Refiner,Output=Output,Ingredients=Ingredients,Time=Time,Recipe=Recipe
|cache=yes
|where=Refiner='{{PAGENAME}}'
}}
{| class="wikitable"
! Refiner
! Output
! Ingredients
! Time
! Recipe
{{#for_external_table:|
{{!}}-
{{!}} {{{Refiner}}}
{{!}} {{{Output}}}
{{!}} {{{Ingredients}}}
{{!}} {{{Time}}}
{{!}} {{{Recipe}}}
}}
|}
]]
local content = frame:preprocess(edQuery)
if not content or content == "" then
return nil, "Error: No refining data returned from external source!"
end
local lines = mw.text.split(content, "\n")
local filtered = {}
for _, line in ipairs(lines) do
line = mw.text.trim(line)
if line ~= "" and not line:match("^__") and not line:match("<noindex>") and not line:match("</noindex>") then
table.insert(filtered, line)
end
end
if #filtered < 2 then
return nil, "Error: No valid refining data found!"
end
local inTable = false
local headers = {}
local colMap = {}
local data = {}
local currentRow = {}
local readingHeader = true
for _, line in ipairs(filtered) do
if line:match("^{|") then
inTable = true
elseif line:match("^|%-") then
if not readingHeader and #currentRow > 0 then
table.insert(data, currentRow)
end
currentRow = {}
readingHeader = false
elseif line:match("^!") then
local header = line:gsub("^!+%s*", "")
header = mw.text.trim(header)
table.insert(headers, header)
elseif line:match("^|%+") then
-- skip caption
elseif line:match("^|}") then
if not readingHeader and #currentRow > 0 then
table.insert(data, currentRow)
end
inTable = false
elseif inTable and line:match("^|[^%-+}]") then
local cell = line:gsub("^|+%s*", "")
cell = mw.text.trim(cell)
if not readingHeader then
table.insert(currentRow, cell)
end
end
end
for i, h in ipairs(headers) do
colMap[mw.text.trim(h)] = i
end
if #data == 0 then
return nil, "Error: No data rows found in refining data!"
end
return { rows = data, colMap = colMap }, nil
end
--------------------------------------------------------------------------------
-- Function: getBuildingData
-- Returns the row (and colMap) for a given building name from external data.
--------------------------------------------------------------------------------
function p.getBuildingData(buildingName, frame)
local buildingData, err = loadBuildingData(frame)
if not buildingData then
return nil, err
end
local colMap = buildingData.colMap
local rows = buildingData.rows
local nameCol = colMap["Name"]
if not nameCol then
return nil, "Error: 'Name' column not found in building data"
end
for _, fields in ipairs(rows) do
local name = fields[nameCol]
if name == buildingName then
return fields, colMap
end
end
return nil, "Building '" .. buildingName .. "' not found"
end
--------------------------------------------------------------------------------
-- Function: getRefiningRecipes
-- Returns a table of refining recipes matching the building name.
--------------------------------------------------------------------------------
function p.getRefiningRecipes(frame)
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
local refiningData, err = loadRefiningData(frame)
if not refiningData then
return "Error loading refining data: " .. (err or "Unknown error")
end
local rows = refiningData.rows
local resourceClassCol = 1 -- Resource Class (if applicable)
local nameCol = 2 -- Output Name
local timeCol = 4 -- Time
local ingredientsCol = 3 -- Ingredients
local recipeCol = 5 -- Recipe (if needed)
local function normalizeText(text)
if not text then return "" end
text = text:gsub('["\']', '')
text = text:gsub("%[%[([^%]]+)%]%]", "%1")
return mw.text.trim(text)
end
local matchingRecipes = {}
for _, row in ipairs(rows) do
local refinerNeeded = row[1] or ""
if normalizeText(refinerNeeded) == normalizeText(buildingName) then
table.insert(matchingRecipes, row)
end
end
if #matchingRecipes == 0 then
return '<tr><td colspan="3" style="text-align:center;">No refining recipes found for this building.</td></tr>'
end
local output = "<tr>\n" ..
"<th style=\"text-align:left;\">Output</th>\n" ..
"<th style=\"text-align:left;\">Ingredients</th>\n" ..
"<th style=\"text-align:left;\">Craft Time</th>\n" ..
"</tr>\n"
for _, recipe in ipairs(matchingRecipes) do
local outputItem = recipe[nameCol] or ""
local ingredients = recipe[ingredientsCol] or ""
local time = recipe[timeCol] or ""
local recipeQty = recipe[recipeCol] or ""
local qty = "1"
if recipeQty ~= "" then
local extractQty = recipeQty:match("x%s*(%d+)")
if extractQty then
qty = extractQty
end
end
local formattedOutput = p.iconize({args = {[1] = outputItem}})
if qty ~= "1" then
formattedOutput = formattedOutput .. " × " .. qty
end
local formattedIngredients = ""
if ingredients ~= "" then
local ingredientsList = mw.text.split(ingredients, ";")
for i, ingredient in ipairs(ingredientsList) do
local trimmedIngredient = mw.text.trim(ingredient)
if trimmedIngredient ~= "" then
local formattedIngredient = p.iconize({args = {[1] = trimmedIngredient}})
if i > 1 then
formattedIngredients = formattedIngredients .. "<br>"
end
formattedIngredients = formattedIngredients .. formattedIngredient
end
end
end
output = output .. "<tr>\n" ..
"<td style=\"text-align:left;\">" .. formattedOutput .. "</td>\n" ..
"<td style=\"text-align:left;\">" .. formattedIngredients .. "</td>\n" ..
"<td style=\"text-align:left;\">" .. time .. "</td>\n" ..
"</tr>\n"
end
return output
end
--------------------------------------------------------------------------------
-- Function: formatBuilding
-- Formats building data as a template call to BuildingRefinerDisplayV2.
--------------------------------------------------------------------------------
function p.formatBuilding(frame)
local buildingName = frame.args[1] or frame.args.name or mw.title.getCurrentTitle().text
if not buildingName then
return "Error: No building name provided"
end
local building, colMap = p.getBuildingData(buildingName, frame)
if not building then
return "Error: Building '" .. buildingName .. "' not found"
end
local params = {
Name = building[colMap["Name"]] or "",
Tier = building[colMap["Tier"]] or "",
Description = p.iconize({args = {[1] = building[colMap["Description"]] or ""}}),
JourneyRequirement = p.iconize({args = {[1] = building[colMap["JourneyRequirement"]] or ""}}),
Health = building[colMap["Health"]] or "",
EnergyConsumption = building[colMap["PowerCost"]] or "",
GeneratesPower = building[colMap["GeneratesPower"]] or "",
StorageSlots = building[colMap["StorageSlots"]] or "",
StorageVolume = building[colMap["StorageVolume"]] or "",
Components = p.formatComponent(building[colMap["RecipeToBuild"]] or ""),
PlacedWith = p.iconize({args = {[1] = building[colMap["PlacedWith"]] or ""}}),
ImageFile = building[colMap["ImageFile"]] or "",
AdditionalNotes = p.iconize({args = {[1] = building[colMap["AdditionalNotes"]] or ""}}),
PrimarySource = "Crafting"
}
local templateCall = "{{BuildingRefinerDisplayV2"
for key, value in pairs(params) do
if value and value ~= "" then
templateCall = templateCall .. "\n|" .. key .. "=" .. value
end
end
templateCall = templateCall .. "\n}}"
return frame:preprocess(templateCall)
end
--------------------------------------------------------------------------------
-- Function: displayBuilding
-- A wrapper function that returns the fully formatted building display.
--------------------------------------------------------------------------------
function p.displayBuilding(frame)
return p.formatBuilding(frame)
end
--------------------------------------------------------------------------------
-- Function: getYoutubeEmbed
-- Returns a processed YouTube embed tag.
--------------------------------------------------------------------------------
function p.getYoutubeEmbed(frame)
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
local width = frame.args.width or 400
local height = frame.args.height or 300
local dimensions = width .. "x" .. height
local buildingData, err = loadBuildingData(frame)
if not buildingData then
return "Error loading data"
end
local rows = buildingData.rows
local building = nil
for _, row in ipairs(rows) do
if row[1] == buildingName then
building = row
break
end
end
if not building or not building[12] or building[12] == "-" or building[12] == "" then
return "Coming Soon"
end
local youtubeUrl = building[12]
local videoId = youtubeUrl:match("v=([%w-_]+)")
if not videoId then
return "Coming Soon"
end
local ytMarkup = string.format('<youtube dimensions="%s" alignment="center">%s</youtube>', dimensions, videoId)
return frame:preprocess(ytMarkup)
end
--------------------------------------------------------------------------------
-- Function: breadcrumb
-- Builds a breadcrumb trail for the current building.
--------------------------------------------------------------------------------
function p.breadcrumb(frame)
local buildingData, err = loadBuildingData(frame)
if not buildingData then
return err
end
local rows = buildingData.rows
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
for _, row in ipairs(rows) do
if row[1] == buildingName then
local cat1 = row[11] or "" -- adjust index if needed
local cat2 = row[12] or ""
local cat3 = row[13] or ""
local function cleanCategory(cat)
if not cat or cat == "-" then return "" end
if cat:find("%[%[Category:") then
cat = cat:match("%[%[Category:%s*(.-)%s*%]%]")
elseif cat:find("%[%[") then
cat = cat:match("%[%[(.-)%]%]")
end
return mw.text.trim(cat)
end
cat1 = cleanCategory(cat1)
cat2 = cleanCategory(cat2)
cat3 = cleanCategory(cat3)
local breadcrumbParts = {}
if cat1 ~= "" then table.insert(breadcrumbParts, "[[" .. cat1 .. "]]") end
if cat2 ~= "" then table.insert(breadcrumbParts, "[[" .. cat2 .. "]]") end
if cat3 ~= "" then table.insert(breadcrumbParts, "[[" .. cat3 .. "]]") end
table.insert(breadcrumbParts, "'''" .. buildingName .. "'''")
return table.concat(breadcrumbParts, " > ")
end
end
return "Breadcrumb not available"
end
--------------------------------------------------------------------------------
-- Function: getCategory3
-- Returns the Category3 field for the current building.
--------------------------------------------------------------------------------
function p.getCategory3(frame)
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
local buildingData, err = loadBuildingData(frame)
if not buildingData then
return "Buildings"
end
local rows = buildingData.rows
for _, row in ipairs(rows) do
if row[1] == buildingName then
local cat3 = row[13] or ""
if cat3 == "-" or cat3 == "" then
return "Buildings"
end
if cat3:find("%[%[") then
cat3 = cat3:match("%[%[(.-)%]%]")
end
return mw.text.trim(cat3)
end
end
return "Buildings"
end
--------------------------------------------------------------------------------
-- Function: relatedBuildings
-- Finds and formats related buildings based on Category3.
--------------------------------------------------------------------------------
function p.relatedBuildings(frame)
local buildingName = frame.args[1] or mw.title.getCurrentTitle().text
local buildingData, err = loadBuildingData(frame)
if not buildingData then
return "Error loading building data"
end
local rows = buildingData.rows
local targetCategory = nil
for _, row in ipairs(rows) do
if row[1] == buildingName then
targetCategory = row[13] or ""
if targetCategory == "-" or targetCategory == "" then
return "No related buildings found"
end
if targetCategory:find("%[%[") then
targetCategory = targetCategory:match("%[%[(.-)%]%]")
end
targetCategory = mw.text.trim(targetCategory)
break
end
end
if not targetCategory then
return "Building not found"
end
local relatedBuildings = {}
for _, row in ipairs(rows) do
if row[1] ~= buildingName then
local category = row[13] or ""
if category ~= "-" and category ~= "" then
if category:find("%[%[") then
category = category:match("%[%[(.-)%]%]")
end
category = mw.text.trim(category)
if category == targetCategory then
table.insert(relatedBuildings, row)
end
end
end
end
if #relatedBuildings == 0 then
return "No other " .. targetCategory .. " found"
end
local output = '<table class="infobox-dune" style="width:100%">\n'
output = output .. "<tr>\n"
output = output .. "<th style=\"text-align:left;\">Name</th>\n"
output = output .. "<th style=\"text-align:left;\">Tier</th>\n"
output = output .. "<th style=\"text-align:left;\">Description</th>\n"
output = output .. "</tr>\n"
for _, building in ipairs(relatedBuildings) do
local name = building[1] or ""
local tier = building[2] or ""
local description = building[3] or ""
if tier == "-" then tier = "" end
if description == "-" then description = "" end
local nameWithIcon = "[[" .. name .. "]]"
local iconFile = building[11] or ""
if iconFile ~= "-" and iconFile ~= "" then
nameWithIcon = "[[File:" .. iconFile .. "|20px]] [[" .. name .. "]]"
end
output = output .. "<tr>\n"
output = output .. "<td>" .. nameWithIcon .. "</td>\n"
output = output .. "<td>" .. tier .. "</td>\n"
output = output .. "<td>" .. description .. "</td>\n"
output = output .. "</tr>\n"
end
output = output .. "</table>"
return output
end
return p
