Actions

Module

Module:ResourceListCSVParser

From Dune Awakening DB

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

-- Module:ResourceCSVParser
-- A Lua module that parses a MediaWiki table rather than CSV/TSV.
-- We assume each cell is on its own line, no nested tables, no multiline cells.
-- The user-provided table's lines define header vs. row data.

local p = {}

----------------------------------------------------------------------
-- 1) Helper function to load/parse the wiki table page and filter out magic-word lines
--    Then parse table markup: headers come before the first "|-" line.
--    Each subsequent "|-" signals a new row.
--    Cells are lines starting with '|'.
----------------------------------------------------------------------
local function loadResourceWikiTable()
    local title = "Page:ResourceData.csv"  -- Actually uses wiki table format, rename if needed
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Wiki table page not found!"
    end

    local lines = mw.text.split(content, "\n")
    -- Filter out magic words or blank lines
    local filtered = {}
    for _, line in ipairs(lines) do
        line = mw.text.trim(line)
        if line ~= "" and not line:match("^__") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid wiki table content found!"
    end

    local inTable = false
    local headers = {}  -- store the table headers
    local colMap = {}
    local data = {}     -- array of row arrays
    local currentRow = {}

    local readingHeader = true  -- before the first row separator ("|-"), treat cells as headers

    for _, line in ipairs(filtered) do
        if line:match("^{|") then
            -- Start of table (line starts with "{|")
            inTable = true
        elseif line:match("^|%-$") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false  -- after the first row separator, subsequent cells are row cells
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|%}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable then
            -- Process table cell lines (lines that start with "|")
            if line:match("^|") then
                -- Remove one or more leading '|' characters
                local cell = line:gsub("^|+", "")
                cell = mw.text.trim(cell)
                if readingHeader then
                    table.insert(headers, cell)
                else
                    table.insert(currentRow, cell)
                end
            end
        end
    end

    -- Build colMap from headers (map header name to its column index)
    for i, h in ipairs(headers) do
        colMap[mw.text.trim(h)] = i
    end

    return { rows = data, colMap = colMap }, nil
end

----------------------------------------------------------------------
-- 2) Alphabetical bullet list (by Name)
----------------------------------------------------------------------
function p.alphabeticalList(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    -- We need at least the "Name" column
    local nameCol = colMap["Name"]
    if not nameCol then
        return "Error: Wiki table missing 'Name' column."
    end

    -- Optional filter by name or class
    local classCol = colMap["Resource Class"]
    local filterName = mw.text.trim(frame.args.name or "")
    local filterClass = mw.text.trim(frame.args.class or "")

    local resourcesByLetter = {}
    for b = string.byte("A"), string.byte("Z") do
        resourcesByLetter[string.char(b)] = {}
    end
    local otherBucket = {}

    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[nameCol] or "")
        local rClass = ""
        if classCol then
            rClass = mw.text.trim(fields[classCol] or "")
        end

        if (filterName == "" or rName == filterName)
           and (filterClass == "" or rClass == filterClass)
        then
            local letter = mw.ustring.upper(mw.ustring.sub(rName, 1, 1))
            if resourcesByLetter[letter] then
                table.insert(resourcesByLetter[letter], rName)
            else
                table.insert(otherBucket, rName)
            end
        end
    end

    local output = ""
    for b = string.byte("A"), string.byte("Z") do
        local letter = string.char(b)
        output = output .. "* <big>" .. letter .. "</big>\n"
        for _, resourceName in ipairs(resourcesByLetter[letter]) do
            output = output .. "** [[" .. resourceName .. "]]\n"
        end
    end

    if #otherBucket > 0 then
        output = output .. "* <big>Other</big>\n"
        for _, resourceName in ipairs(otherBucket) do
            output = output .. "** [[" .. resourceName .. "]]\n"
        end
    end

    return output
end

----------------------------------------------------------------------
-- A small helper that finds [[Resource Name]] patterns and inserts an icon
-- following the naming pattern "Resource_Name_-_Icon.png" (20px, frameless).
----------------------------------------------------------------------
local function addNameIcon(text)
    local function rep(resource)
        local fileName = resource:gsub("%s+", "_") .. "_-_Icon.png"
        local fileTitle = mw.title.makeTitle("File", fileName)

        if fileTitle.exists then
            -- File actually exists, so we display the icon plus link
            return string.format(
                "[[File:%s|20px|frameless|link=%s]] [[%s]]",
                fileName, resource, resource
            )
        else
            -- If file doesn't exist, just return the plain link
            return string.format("[[%s]]", resource)
        end
    end

    -- Use gsub to replace all [[Resource]] with icon+link
    return (text:gsub("%[%[([^%]]+)%]%]", rep))
end



----------------------------------------------------------------------
-- 3) A wikitable of resources, optional filtering by name/class
----------------------------------------------------------------------
function p.resourcesTable(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    local classCol = colMap["Resource Class"]
    local nameCol = colMap["Name"]
    local rarityCol = colMap["Rarity"]
    local weightCol = colMap["Weight"]
    local xpCol = colMap["Xp Per Harvest"]

    if not (classCol and nameCol and rarityCol and weightCol and xpCol) then
        return "Error: Missing one of (Resource Class, Name, Rarity, Weight, Xp Per Harvest)."
    end

    local filterName = mw.text.trim(frame.args.name or "")
    local filterClass = mw.text.trim(frame.args.class or "")

    local output = '{| class="wikitable sortable"\n'
    output = output .. "|-\n"
    output = output .. "! Resource Class !! Name !! Rarity !! Weight !! XP/Harvest\n"

    for _, fields in ipairs(rows) do
        local rClass  = mw.text.trim(fields[classCol] or "")
        local rName   = mw.text.trim(fields[nameCol] or "")
        local rRarity = fields[rarityCol] or ""
        local rWeight = fields[weightCol] or ""
        local rXp     = fields[xpCol] or ""

        if (filterName == "" or rName == filterName)
           and (filterClass == "" or rClass == filterClass)
        then
            -- Insert the icon in the Name cell
            local nameWithIcon = addNameIcon("[["..rName.."]]")

            output = output .. "|-\n"
            output = output .. string.format("| %s || %s || %s || %s || %s\n",
                rClass, nameWithIcon, rRarity, rWeight, rXp
            )
        end
    end

    output = output .. "|}"
    return output
end


----------------------------------------------------------------------
-- 4) resourceDetail: Outputs the resource details for the main template.
--    - Displays the full image (if provided) using 400px with center alignment.
--    - Splits the "How to Obtain" field (delimited by semicolons) into a bullet list.
--    - Splits the "Locations to Harvest" field (delimited by semicolons) into a bullet list.
----------------------------------------------------------------------
function p.resourceDetail(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    local filterName  = mw.text.trim(frame.args.name or "")
    local filterClass = mw.text.trim(frame.args.class or "")

    local classCol     = colMap["Resource Class"]
    local nameCol      = colMap["Name"]
    local rarityCol    = colMap["Rarity"]
    local descCol      = colMap["Description"]
    local weightCol    = colMap["Weight"]
    local xpCol        = colMap["Xp Per Harvest"]
    local howToCol     = colMap["How to Obtain"]
    local locCol       = colMap["Locations to Harvest"]
    local notesCol     = colMap["Notes"]
    local guideCol     = colMap["Guide Link"]
    local imgCol       = colMap["Image Link"]
    local cat1Col      = colMap["Category 1"]
    local cat2Col      = colMap["Category 2"]
    local catOtherCol  = colMap["Other Categories"]

    for _, fields in ipairs(rows) do
        local rName  = mw.text.trim(fields[nameCol] or "")
        local rClass = mw.text.trim(fields[classCol] or "")
        if (filterName == "" or rName == filterName) and (filterClass == "" or rClass == filterClass) then
            local rRarity   = fields[rarityCol] or ""
            local rDesc     = fields[descCol] or ""
            local rWeight   = fields[weightCol] or ""
            local rXp       = fields[xpCol] or ""
            local rHowTo    = fields[howToCol] or ""
            local rLoc      = fields[locCol] or ""
            local rNotes    = fields[notesCol] or ""
            local rGuide    = fields[guideCol] or ""
            local rImg      = fields[imgCol] or ""
            local rCat1     = cat1Col and (fields[cat1Col] or "") or ""
            local rCat2     = cat2Col and (fields[cat2Col] or "") or ""
            local rCatOther = catOtherCol and (fields[catOtherCol] or "") or ""

            local output = ""

            -- Display the full image if provided.
            if rImg ~= "" then
                -- Changed image dimensions from "400px" to "480x320" and ensured center alignment.
                output = output .. string.format("[[File:%s|480x320|center]]\n\n", rImg)
            end

            output = output .. "; Class\n" .. rClass .. "\n\n"
            output = output .. "; Rarity\n" .. rRarity .. "\n\n"
            output = output .. "; Description\n" .. rDesc .. "\n\n"
            output = output .. "; Weight\n" .. rWeight .. "\n\n"
            output = output .. "; XP per Harvest\n" .. rXp .. "\n\n"

            -- Process "How to Obtain": now output as inline bullets.
            if rHowTo ~= "" then
                local howToList = mw.text.split(rHowTo, ";")
                output = output .. "; How to Obtain\n"
                for _, method in ipairs(howToList) do
                    method = mw.text.trim(method)
                    if method ~= "" then
                        -- Instead of a newline bullet list, output inline using the bullet icon.
                        output = output .. "&#8226; " .. method .. "  "  -- two spaces after each bullet for separation
                    end
                end
                output = output .. "\n\n"
            else
                output = output .. "; How to Obtain\nComing Soon\n\n"
            end

            -- Process "Locations to Harvest": still output as a bullet list.
            if rLoc ~= "" then
                local locList = mw.text.split(rLoc, ";")
                output = output .. "; Locations\n"
                for _, location in ipairs(locList) do
                    location = mw.text.trim(location)
                    if location ~= "" then
                        output = output .. "* " .. location .. "\n"
                    end
                end
                output = output .. "\n"
            else
                output = output .. "; Locations\nComing Soon\n\n"
            end

            output = output .. "; Notes\n" .. rNotes .. "\n\n"
            if rGuide ~= "" then
                output = output .. "; Guide\n" .. rGuide .. "\n\n"
            else
                output = output .. "; Guide\nComing Soon\n\n"
            end

            output = output .. rCat1 .. rCat2 .. rCatOther

            return output
        end
    end

    return "Error: No resource matched name='" .. filterName .. "' class='" .. filterClass .. "'"
end


--------------------------------------------------------------------
-- resourceLocationsInColumns: 
-- Splits "Locations to Harvest" into columns of 3 bullet points each,
-- using a single-row table with class="nostyle-table".
--------------------------------------------------------------------
function p.resourceLocationsInColumns(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    local resourceName = mw.text.trim(frame.args.name or mw.title.getCurrentTitle().text)
    local nameCol = colMap["Name"]
    if not nameCol then
        return "Error: Missing 'Name' column!"
    end

    -- Find row by name
    local rowFields
    for _, fields in ipairs(rows) do
        if mw.text.trim(fields[nameCol] or "") == resourceName then
            rowFields = fields
            break
        end
    end
    if not rowFields then
        return "Error: Resource '" .. resourceName .. "' not found."
    end

    local locCol = colMap["Locations to Harvest"]
    if not locCol then
        return "Error: Missing 'Locations to Harvest' column!"
    end

    local rLoc = rowFields[locCol] or ""
    local output = "=Where to Obtain=\n"

    if rLoc == "" then
        output = output .. "Coming Soon"
        return output
    end

    -- Split on semicolon, trim
    local allItems = {}
    for _, item in ipairs(mw.text.split(rLoc, ";")) do
        item = mw.text.trim(item)
        if item ~= "" then
            table.insert(allItems, item)
        end
    end

    if #allItems == 0 then
        output = output .. "Coming Soon"
        return output
    end

    -- Break into chunks of 3
    local function chunkList(items, chunkSize)
        local results = {}
        for i = 1, #items, chunkSize do
            local sub = {}
            for j = i, math.min(i + chunkSize - 1, #items) do
                table.insert(sub, items[j])
            end
            table.insert(results, sub)
        end
        return results
    end

    local chunks = chunkList(allItems, 3)

    -- Build single-row table with class="nostyle-table"
    output = output .. "{| class=\"nostyle-table\"\n|-\n"
    for _, chunk in ipairs(chunks) do
        output = output .. "| <ul>\n"
        for _, locItem in ipairs(chunk) do
            output = output .. string.format("  <li>%s</li>\n", locItem)
        end
        output = output .. "</ul>\n"
    end
    output = output .. "|}"

    return output
end

--------------------------------------------------------------------
-- resourceHowToInColumns:
-- Same approach for 'How to Obtain', with a single-row .nostyle-table
--------------------------------------------------------------------
function p.resourceHowToInColumns(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    local resourceName = mw.text.trim(frame.args.name or mw.title.getCurrentTitle().text)
    local nameCol = colMap["Name"]
    if not nameCol then
        return "Error: Missing 'Name' column!"
    end

    -- Find row
    local rowFields
    for _, fields in ipairs(rows) do
        if mw.text.trim(fields[nameCol] or "") == resourceName then
            rowFields = fields
            break
        end
    end
    if not rowFields then
        return "Error: Resource '" .. resourceName .. "' not found."
    end

    local howCol = colMap["How to Obtain"]
    if not howCol then
        return "Error: Missing 'How to Obtain' column!"
    end

    local rHow = rowFields[howCol] or ""
    local output = "=How to Obtain=\n"

    if rHow == "" then
        output = output .. "Coming Soon"
        return output
    end

    local allItems = {}
    for _, item in ipairs(mw.text.split(rHow, ";")) do
        item = mw.text.trim(item)
        if item ~= "" then
            table.insert(allItems, item)
        end
    end

    if #allItems == 0 then
        output = output .. "Coming Soon"
        return output
    end

    local function chunkList(items, chunkSize)
        local results = {}
        for i = 1, #items, chunkSize do
            local sub = {}
            for j = i, math.min(i + chunkSize - 1, #items) do
                table.insert(sub, items[j])
            end
            table.insert(results, sub)
        end
        return results
    end

    local chunks = chunkList(allItems, 3)

    output = output .. "{| class=\"nostyle-table\"\n|-\n"
    for _, chunk in ipairs(chunks) do
        output = output .. "| <ul>\n"
        for _, method in ipairs(chunk) do
            output = output .. string.format("  <li>%s</li>\n", method)
        end
        output = output .. "</ul>\n"
    end
    output = output .. "|}"

    return output
end





----------------------------------------------------------------------
-- 5) Build a ResourcePage template call from wiki table data
----------------------------------------------------------------------
function p.getResourceData(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local rows = wikiData.rows
    local colMap = wikiData.colMap

    local resourceName = frame.args.name or ""
    if resourceName == "" then
        local currentTitle = mw.title.getCurrentTitle()
        resourceName = currentTitle.text
    end

    for _, fields in ipairs(rows) do
        if (fields[colMap["Name"]] or "") == resourceName then
            local rDesc   = fields[colMap["Description"]] or ""
            local rClass  = fields[colMap["Resource Class"]] or ""
            local rWeight = fields[colMap["Weight"]] or ""
            local rXp     = fields[colMap["Xp Per Harvest"]] or ""
            local rHowTo  = fields[colMap["How to Obtain"]] or ""
            local rRarity = fields[colMap["Rarity"]] or ""
            local rImg    = fields[colMap["Image Link"]] or ""
            local rNotes  = fields[colMap["Notes"]] or ""
            local rCat1   = fields[colMap["Category 1"]] or ""
            local rCat2   = fields[colMap["Category 2"]] or ""
            local rCatO   = fields[colMap["Other Categories"]] or ""

            local templateCall = string.format(
                "{{ResourcePage" ..
                "|name=%s" ..
                "|description=%s" ..
                "|resource_class=%s" ..
                "|weight=%s" ..
                "|xp=%s" ..
                "|howto=%s" ..
                "|rarity=%s" ..
                "|image_link=%s" ..
                "|notes=%s" ..
                "|cat1=%s" ..
                "|cat2=%s" ..
                "|othercat=%s" ..
                "}}",
                resourceName,
                rDesc,
                rClass,
                rWeight,
                rXp,
                rHowTo,
                rRarity,
                rImg,
                rNotes,
                rCat1,
                rCat2,
                rCatO
            )

            return frame:preprocess(templateCall)
        end
    end

    return "Error: Resource '" .. resourceName .. "' not found in wiki table."
end

----------------------------------------------------------------------
-- 6) resourcesTableByResourceClass
----------------------------------------------------------------------
function p.resourcesTableByResourceClass(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local rows = wikiData.rows
    local colMap = wikiData.colMap

    local classCol = colMap["Resource Class"]
    local nameCol = colMap["Name"]
    local rarityCol = colMap["Rarity"]
    local weightCol = colMap["Weight"]
    local xpCol = colMap["Xp Per Harvest"]

    if not (classCol and nameCol and rarityCol and weightCol and xpCol) then
        return "Error: Missing one of (Resource Class, Name, Rarity, Weight, Xp Per Harvest)."
    end

    local resourceName = mw.text.trim(frame.args.name or "")
    if resourceName == "" then
        resourceName = mw.title.getCurrentTitle().text
    end

    local foundClass = nil
    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[nameCol] or "")
        if rName == resourceName then
            foundClass = mw.text.trim(fields[classCol] or "")
            break
        end
    end

    if not foundClass or foundClass == "" then
        return string.format("Error: Resource “%s” not found or has no class in wiki table.", resourceName)
    end

    local output = '{| class="wikitable sortable"\n'
    output = output .. "|-\n"
    output = output .. "! Resource Class !! Name !! Rarity !! Weight !! XP/Harvest\n"

    for _, fields in ipairs(rows) do
        local rClass  = mw.text.trim(fields[classCol] or "")
        local rName   = mw.text.trim(fields[nameCol] or "")
        local rRarity = fields[rarityCol] or ""
        local rWeight = fields[weightCol] or ""
        local rXp     = fields[xpCol] or ""

        if rClass == foundClass then
            output = output .. "|-\n"
            output = output .. string.format("| %s || [[%s]] || %s || %s || %s\n",
                rClass, rName, rRarity, rWeight, rXp
            )
        end
    end

    output = output .. "|}"
    return output
end

----------------------------------------------------------------------
-- resourcesTableByResourceClassTitle
-- filtering by the category
----------------------------------------------------------------------
function p.resourcesTableByResourceClassTitle(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local rows = wikiData.rows
    local colMap = wikiData.colMap

    local classCol  = colMap["Resource Class"]
    local nameCol   = colMap["Name"]
    local rarityCol = colMap["Rarity"]
    local weightCol = colMap["Weight"]
    local xpCol     = colMap["Xp Per Harvest"]

    if not (classCol and nameCol and rarityCol and weightCol and xpCol) then
        return "Error: Missing one of (Resource Class, Name, Rarity, Weight, Xp Per Harvest)."
    end

    -- Use the passed parameter 'name' if provided; otherwise, use the current page title.
    local resourceClass = mw.text.trim(frame.args.name or mw.title.getCurrentTitle().text)
    if resourceClass == "" then
        return "Error: No resource class provided."
    end

    local output = '{| class="wikitable sortable"\n'
    output = output .. "|-\n"
    output = output .. "! Resource Class !! Name !! Rarity !! Weight !! XP/Harvest\n"

    local foundAny = false
    for _, fields in ipairs(rows) do
        local rClass  = mw.text.trim(fields[classCol] or "")
        local rName   = mw.text.trim(fields[nameCol] or "")
        local rRarity = fields[rarityCol] or ""
        local rWeight = fields[weightCol] or ""
        local rXp     = fields[xpCol] or ""

        if rClass == resourceClass then
            foundAny = true
            local nameWithIcon = addNameIcon("[["..rName.."]]")
            
            output = output .. "|-\n"
            output = output .. string.format("| %s || %s || %s || %s || %s\n",
                rClass, nameWithIcon, rRarity, rWeight, rXp
            )
        end
    end

    output = output .. "|}"
    if not foundAny then
        return string.format("Error: No resources found for resource class '%s'.", resourceClass)
    end

    return output
end



----------------------------------------------------------------------
-- 7) resourcesTableByPageTitle
----------------------------------------------------------------------
function p.resourcesTableByPageTitle(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    local classCol = colMap["Resource Class"]
    local nameCol = colMap["Name"]
    local rarityCol = colMap["Rarity"]
    local weightCol = colMap["Weight"]
    local xpCol = colMap["Xp Per Harvest"]

    if not (classCol and nameCol and rarityCol and weightCol and xpCol) then
        return "Error: Missing one of (Resource Class, Name, Rarity, Weight, Xp Per Harvest)."
    end

    local filterClass = mw.text.trim(frame.args.class or "")
    if filterClass == "" then
        filterClass = mw.title.getCurrentTitle().text
    end

    local output = '{| class="wikitable sortable"\n'
    output = output .. "|-\n"
    output = output .. "! Resource Class !! Name !! Rarity !! Weight !! XP/Harvest\n"

    for _, fields in ipairs(rows) do
        local rClass  = mw.text.trim(fields[classCol] or "")
        local rName   = mw.text.trim(fields[nameCol] or "")
        local rRarity = fields[rarityCol] or ""
        local rWeight = fields[weightCol] or ""
        local rXp     = fields[xpCol] or ""

        if rClass == filterClass then
            output = output .. "|-\n"
            output = output .. string.format("| %s || [[%s]] || %s || %s || %s\n",
                rClass, rName, rRarity, rWeight, rXp
            )
        end
    end

    output = output .. "|}"
    return output
end

----------------------------------------------------------------------
-- 8) Refiner Data: three new functions
--    We do a second table from Page:RefinerData.csv
----------------------------------------------------------------------

local function loadRefinerWikiTable()
    local title = "Page:RefinerData.csv"  -- Updated to match your refiner data page name
    local content = mw.title.new(title):getContent()
    if not content then
        return nil, "Error: Refiner wiki table page not found!"
    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("^__") then
            table.insert(filtered, line)
        end
    end
    if #filtered < 2 then
        return nil, "Error: No valid refiner wiki table content 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
            -- Matches the start of table "{|"
            inTable = true
        elseif line:match("^|%-$") then
            -- Row separator ("|-")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            currentRow = {}
            readingHeader = false
        elseif line:match("^|%+") then
            -- Table caption line ("|+"), skip it
        elseif line:match("^|%}") then
            -- End of table ("|}")
            if not readingHeader and #currentRow > 0 then
                table.insert(data, currentRow)
            end
            inTable = false
        elseif inTable and line:match("^|") then
            local cell = line:gsub("^|+", "")  -- remove leading '|' characters
            cell = mw.text.trim(cell)
            if readingHeader then
                table.insert(headers, cell)
            else
                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

--------------------------------------------------------------------
-- refinerDetail: single row detail from Page:RefinerData.csv
--------------------------------------------------------------------
function p.refinerDetail(frame)
    local wikiData, err = loadRefinerWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    local resourceName = frame.args.name or ""
    if resourceName == "" then
        resourceName = mw.title.getCurrentTitle().text
    end

    local classCol  = colMap["Resource Class"]
    local nameCol   = colMap["Name"]
    local timeCol   = colMap["Time to Smelt"]
    local ingrCol   = colMap["Ingredients to Smelt"]
    local recipeCol = colMap["Recipe to Smelt"]
    local refinCol  = colMap["Refiner Needed"]

    if not (classCol and nameCol and timeCol and ingrCol and recipeCol and refinCol) then
        return "Error: Wiki table missing one of the required Refiner columns."
    end

    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[nameCol] or "")
        if rName == resourceName then
            local rClass = fields[classCol]  or ""
            local rTime  = fields[timeCol]   or ""
            local rIngr  = fields[ingrCol]   or ""
            local rRec   = fields[recipeCol] or ""
            local rRefn  = fields[refinCol]  or ""

            local output = "== Refiner Details ==\n"
            output = output .. "; Resource Class\n" .. rClass .. "\n\n"
            output = output .. "; Name\n" .. rName .. "\n\n"
            output = output .. "; Time to Smelt\n" .. rTime .. "\n\n"
            output = output .. "; Ingredients to Smelt\n" .. rIngr .. "\n\n"
            output = output .. "; Recipe to Smelt\n" .. rRec .. "\n\n"
            output = output .. "; Refiner Needed\n" .. rRefn .. "\n\n"

            return output
        end
    end

    return "Error: Resource '" .. resourceName .. "' not found in RefinerData."
end

--------------------------------------------------------------------
-- refinerTableByIngredients: shows rows that contain filterName in 'Ingredients to Smelt'
-- adds heading '=Refined Into=' if found, else returns empty.
-- Now, it adds icons (via naming convention) to the Name and Recipe to Smelt cells.
-- For any resource link ([[Resource Name]]), an icon is added:
--   File:<Resource_Name>_-_Icon.png (spaces become underscores)
-- In the Recipe cell, multiple ingredients (separated by ";")
-- are output on new lines.
--------------------------------------------------------------------
function p.refinerTableByIngredients(frame)
    -- Load the refiner table data.
    local wikiData, err = loadRefinerWikiTable()
    if not wikiData then
        return err
    end
    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    -- Use the passed parameter 'name' as filter; if empty, use the current page title.
    local filterName = frame.args.name or ""
    if filterName == "" then
        filterName = mw.title.getCurrentTitle().text
    end

    -- Get column indices for the refiner table.
    local classCol  = colMap["Resource Class"]
    local nameCol   = colMap["Name"]
    local timeCol   = colMap["Time to Smelt"]
    local ingrCol   = colMap["Ingredients to Smelt"]
    local recipeCol = colMap["Recipe to Smelt"]
    local refinCol  = colMap["Refiner Needed"]

    if not (classCol and nameCol and timeCol and ingrCol and recipeCol and refinCol) then
        return "Error: Wiki table missing one of the required Refiner columns."
    end

    -- Helper function: For any text, find all occurrences of [[Resource Name]]
    -- and replace each with an icon (using the naming convention) followed by the original link.
    local function addIcons(text)
        return (text:gsub("%[%[([^%]]+)%]%]", function(resource)
             -- Replace one or more spaces with underscores
             local resourceForFile = resource:gsub("%s+", "_")
             local iconFile = resourceForFile .. "_-_Icon.png"
             -- Return the icon image (20px, frameless, with a clickable link) followed by a nonbreaking space and the original link.
             return string.format("[[File:%s|20px|frameless|link=%s]] [[%s]]", iconFile, resource, resource)
        end))
    end

    local foundAny = false
    local tableOutput = '{| class="wikitable sortable" style="width:100%"\n'
    tableOutput = tableOutput .. "|-\n"
    tableOutput = tableOutput .. "! Resource Class !! Name !! Time to Smelt !! Recipe to Smelt !! Refiner Needed\n"

    for _, fields in ipairs(rows) do
        local rIngr  = fields[ingrCol] or ""
        local rClass = fields[classCol] or ""
        local rName  = mw.text.trim(fields[nameCol] or "")
        local rTime  = fields[timeCol] or ""
        local rRec   = fields[recipeCol] or ""
        local rRefn  = fields[refinCol] or ""

        -- Split the Ingredients cell on semicolon.
        local ingredients = mw.text.split(rIngr, ";")
        local matchFound = false
        for _, ingrItem in ipairs(ingredients) do
            local candidate = mw.text.trim(ingrItem)
            -- If the ingredient is wrapped in [[ ]], extract the name.
            local ingredientName = candidate:match("%[%[(.-)%]%]") or candidate
            if ingredientName == filterName then
                matchFound = true
                break
            end
        end

        if matchFound then
            foundAny = true

            -- Process the "Name" cell: apply addIcons.
            local nameOutput = addIcons(rName)
            
            -- Process the "Recipe to Smelt" cell:
            -- Split by semicolon and process each part individually.
            local recipeParts = mw.text.split(rRec, ";")
            local recipeOutput = ""
            for _, part in ipairs(recipeParts) do
                local trimmedPart = mw.text.trim(part)
                if trimmedPart ~= "" then
                    local partWithIcon = addIcons(trimmedPart)
                    if recipeOutput ~= "" then
                        recipeOutput = recipeOutput .. "<br/>" .. partWithIcon
                    else
                        recipeOutput = partWithIcon
                    end
                end
            end

            tableOutput = tableOutput .. "|-\n"
            tableOutput = tableOutput .. string.format("| %s || %s || %s || %s || %s\n", 
                rClass, nameOutput, rTime, recipeOutput, rRefn)
        end
    end

    tableOutput = tableOutput .. "|}"

    if foundAny then
         return "=Refined Into=\n" .. tableOutput
    else
         return ""
    end
end




--------------------------------------------------------------------
-- refinerData: example function that might build a template call, etc.
--------------------------------------------------------------------
function p.refinerData(frame)
    local wikiData, err = loadRefinerWikiTable()
    if not wikiData then
        return err
    end

    local rows = wikiData.rows
    local colMap = wikiData.colMap

    local resourceName = frame.args.name or ""
    if resourceName == "" then
        local currentTitle = mw.title.getCurrentTitle()
        resourceName = currentTitle.text
    end

    for _, fields in ipairs(rows) do
        if (fields[colMap["Name"]] or "") == resourceName then
            local rClass  = fields[colMap["Resource Class"]] or ""
            local rTime   = fields[colMap["Time to Smelt"]] or ""
            local rIngr   = fields[colMap["Ingredients to Smelt"]] or ""
            local rRec    = fields[colMap["Recipe to Smelt"]] or ""
            local rRefn   = fields[colMap["Refiner Needed"]] or ""

            local output = "== Refiner Info ==\n"
            output = output .. "; Class\n" .. rClass .. "\n\n"
            output = output .. "; Time\n" .. rTime .. "\n\n"
            output = output .. "; Ingredients\n" .. rIngr .. "\n\n"
            output = output .. "; Recipe\n" .. rRec .. "\n\n"
            output = output .. "; Refiner Needed\n" .. rRefn .. "\n\n"

            return output
        end
    end

    return "Error: Resource '"..resourceName.."' not found in the refiner table."
end

--------------------------------------------------------------------
-- debugResourceDetail: testing data outputs
--------------------------------------------------------------------

function p.debugResourceDetail(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows = wikiData.rows

    local filterName = mw.text.trim(frame.args.name or "")
    local filterClass = mw.text.trim(frame.args.class or "")

    local debugOutput = ""

    for _, fields in ipairs(rows) do
        local rClass = mw.text.trim(fields[colMap["Resource Class"]] or "")
        local rName = mw.text.trim(fields[colMap["Name"]] or "")
        -- Use the filters to select the appropriate row
        if (filterName == "" or rName == filterName)
           and (filterClass == "" or rClass == filterClass)
        then
            local rRarity = fields[colMap["Rarity"]] or ""
            local rDesc = fields[colMap["Description"]] or ""
            local rWeight = fields[colMap["Weight"]] or ""
            local rXp = fields[colMap["Xp Per Harvest"]] or ""
            local rHowTo = fields[colMap["How to Obtain"]] or ""
            local rLoc = fields[colMap["Locations to Harvest"]] or ""
            local rNotes = fields[colMap["Notes"]] or ""
            local rGuide = mw.text.trim(fields[colMap["Guide Link"]] or "")
            local rImg = fields[colMap["Image Link"]] or ""
            local rCat1 = fields[colMap["Category 1"]] or ""
            local rCat2 = fields[colMap["Category 2"]] or ""
            local rCatOther = fields[colMap["Other Categories"]] or ""

            debugOutput = debugOutput ..
                "rClass: " .. rClass .. "\n" ..
                "rName: " .. rName .. "\n" ..
                "rRarity: " .. rRarity .. "\n" ..
                "rDesc: " .. rDesc .. "\n" ..
                "rWeight: " .. rWeight .. "\n" ..
                "rXp: " .. rXp .. "\n" ..
                "rHowTo: " .. rHowTo .. "\n" ..
                "rLoc: " .. rLoc .. "\n" ..
                "rNotes: " .. rNotes .. "\n" ..
                "rGuide: " .. rGuide .. "\n" ..
                "rImg: " .. rImg .. "\n" ..
                "rCat1: " .. rCat1 .. "\n" ..
                "rCat2: " .. rCat2 .. "\n" ..
                "rCatOther: " .. rCatOther .. "\n"
            return debugOutput
        end
    end

    return "Error: No resource matched name='" .. filterName .. "' class='" .. filterClass .. "'"
end


----------------------------------------------------------------------
-- 9) resourceTableByResourceName
--    Output a wikitable for the resource filtered by the page title.
--    This function shows the icon (if provided) to the left of the resource name.
----------------------------------------------------------------------
function p.resourceTableByResourceName(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    -- Get the required column indices from the table
    local nameCol    = colMap["Name"]
    local classCol   = colMap["Resource Class"]
    local rarityCol  = colMap["Rarity"]
    local weightCol  = colMap["Weight"]
    local xpCol      = colMap["Xp Per Harvest"]
    local iconCol    = colMap["Icon Link"]

    if not (nameCol and classCol and rarityCol and weightCol and xpCol) then
        return "Error: Missing one of (Resource Class, Name, Rarity, Weight, Xp Per Harvest) columns."
    end

    -- Filter: if no parameter provided, use the current page title
    local filterName = mw.text.trim(frame.args.name or "")
    if filterName == "" then
        filterName = mw.title.getCurrentTitle().text
    end

    local output = '{| class="wikitable" style="width:100%"\n'
    output = output .. "! Item Class !! Item Name !! Rarity !! Weight !! XP/Harvest\n"
    
    local found = false
    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[nameCol] or "")
        if rName == filterName then
            found = true
            local rClass  = fields[classCol] or ""
            local rRarity = fields[rarityCol] or ""
            local rWeight = fields[weightCol] or ""
            local rXp     = fields[xpCol] or ""
            local rIcon   = fields[iconCol] or ""

            -- Build the Item Name cell:
            local itemNameOutput = ""
            if rIcon ~= "" then
                -- Use frameless to avoid the thumbnail border/background and display it inline.
                itemNameOutput = string.format("[[File:%s|20px|frameless|link=%s]] '''[[%s]]'''", rIcon, rName, rName)
            else
                itemNameOutput = string.format("'''[[%s]]'''", rName)
            end

            output = output .. "|-\n"
            output = output .. string.format("| [[%s]] || %s || %s || %s || %s\n", 
                rClass, itemNameOutput, rRarity, rWeight, rXp)
        end
    end

    output = output .. "|}"
    
    if not found then
        return "Error: No resource matched name '" .. filterName .. "'."
    end

    return output
end

----------------------------------------------------------------------
-- 10) guideVideo: Returns the full <youtube>...</youtube> markup for a resource.
--     It looks up the resource by name (defaulting to the current page title)
--     and returns the Guide Link wrapped in <youtube> tags.
--     The output is processed by frame:preprocess(), and any stray </youtube>
--     is removed.
----------------------------------------------------------------------
function p.guideVideo(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    local resourceName = mw.text.trim(frame.args.name or "")
    if resourceName == "" then
        resourceName = mw.title.getCurrentTitle().text
    end

    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[colMap["Name"]] or "")
        if rName == resourceName then
            local rGuide = mw.text.trim(fields[colMap["Guide Link"]] or "")
            if rGuide ~= "" then
                -- Build the complete <youtube> markup using the guide link.
                local ytMarkup = "<youtube>" .. rGuide .. "</youtube>"
                -- Process the markup so that it is parsed as wikitext.
                local processed = frame:preprocess(ytMarkup)
                -- Remove any stray literal closing tag.
                processed = processed:gsub("</youtube>", "")
                return processed
            else
                return "Coming Soon"
            end
        end
    end

    return "Error: Resource '" .. resourceName .. "' not found in the resource table."
end

----------------------------------------------------------------------
-- 11) getGuideLink: Returns the full <youtube> markup for a resource,
-- with dimensions and alignment attributes.
-- It looks up the resource by name (defaulting to the current page title)
-- and then wraps the Guide Link (as stored in the resource table) in a 
-- <youtube> tag with attributes.
----------------------------------------------------------------------
function p.getGuideLink(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    -- Use the passed parameter 'name' if provided; otherwise, use the current page title.
    local resourceName = mw.text.trim(frame.args.name or mw.title.getCurrentTitle().text)

    for _, fields in ipairs(rows) do
         local rName = mw.text.trim(fields[colMap["Name"]] or "")
         if rName == resourceName then
              local rGuide = mw.text.trim(fields[colMap["Guide Link"]] or "")
              if rGuide ~= "" then
                  -- Get dimensions and alignment from parameters or use defaults.
                  local dims  = frame.args.dimensions or "480x320"
                  local align = frame.args.alignment or "center"
                  -- Build the complete <youtube> markup.
                  local ytMarkup = string.format('<youtube dimensions="%s" alignment="%s">%s</youtube>', dims, align, rGuide)
                  -- Process the markup so that MediaWiki parses it as wikitext.
                  local processed = frame:preprocess(ytMarkup)
                  -- In case a stray closing tag appears, remove it.
                  processed = processed:gsub("</youtube>", "")
                  return processed
              else
                  return "=Guide=\nComing Soon"
              end
         end
    end

    return "Error: Resource '" .. resourceName .. "' not found in the resource table."
end


----------------------------------------------------------------------
-- 12) breadcrumb: Builds a breadcrumb trail using Category 1, Category 2,
-- and the current resource name.
-- It extracts the plain text from the category fields (if they are in the form [[Category:...]])
-- and outputs the breadcrumb as: [[Category1]] > [[Category2]] > ResourceName
----------------------------------------------------------------------
function p.breadcrumb(frame)
    local wikiData, err = loadResourceWikiTable()
    if not wikiData then
        return err
    end

    local colMap = wikiData.colMap
    local rows   = wikiData.rows

    -- Use the passed parameter 'name' or the current page title.
    local resourceName = mw.text.trim(frame.args.name or mw.title.getCurrentTitle().text)
    
    for _, fields in ipairs(rows) do
        local rName = mw.text.trim(fields[colMap["Name"]] or "")
        if rName == resourceName then
            -- Retrieve Category 1 and Category 2 values.
            local cat1 = mw.text.trim(fields[colMap["Category 1"]] or "")
            local cat2 = mw.text.trim(fields[colMap["Category 2"]] or "")
            
            -- If the category value is wrapped in wiki markup like [[Category:Resources]],
            -- extract only the inner text.
            if cat1:find("%[%[Category:") then
                cat1 = cat1:match("%[%[Category:%s*(.-)%s*%]%]")
            end
            if cat2:find("%[%[Category:") then
                cat2 = cat2:match("%[%[Category:%s*(.-)%s*%]%]")
            end
            
            local breadcrumbParts = {}
            if cat1 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat1 .. "]]")
            end
            if cat2 ~= "" then
                table.insert(breadcrumbParts, "[[" .. cat2 .. "]]")
            end
            -- Append the current resource name as bold text.
            table.insert(breadcrumbParts, "'''" .. resourceName .. "'''")
            
            return table.concat(breadcrumbParts, " > ")
        end
    end

    return "Breadcrumb not available"
end

----------------------------------------------------------------------
-- 13) Notes and Gallery
-- Expands based on the amount of gallery images
----------------------------------------------------------------------
function p.notesAndGalleries(frame)
    -- 1) Load the resource wiki table
    local data, err = loadResourceWikiTable()  -- or whatever your loader is named
    if not data then
        return err
    end

    local rows = data.rows
    local colMap = data.colMap

    -- 2) Identify the columns you need
    local notesCol = colMap["Notes"]
    local g1Col    = colMap["Gallery 1"]
    local g2Col    = colMap["Gallery 2"]
    local g3Col    = colMap["Gallery 3"]
    local g4Col    = colMap["Gallery 4"]

    -- If the table doesn't have these columns, fail gracefully
    if not notesCol then
        return "Error: Table missing 'Notes' column!"
    end
    -- It's okay if Gallery 1..4 might be nil; we handle that later.

    -- 3) Figure out which resource row to look for
    local resourceName = frame.args.name or mw.title.getCurrentTitle().text

    -- 4) Find the row with name = resourceName
    local nameCol = colMap["Name"]
    if not nameCol then
        return "Error: Table missing 'Name' column!"
    end

    local rowFields = nil
    for _, fields in ipairs(rows) do
        if mw.text.trim(fields[nameCol] or "") == resourceName then
            rowFields = fields
            break
        end
    end

    if not rowFields then
        return "Error: Resource '" .. resourceName .. "' not found in the table."
    end

    -- 5) Grab the notes and gallery fields
    local notes = rowFields[notesCol]    or ""
    local g1    = g1Col and (rowFields[g1Col] or "") or ""
    local g2    = g2Col and (rowFields[g2Col] or "") or ""
    local g3    = g3Col and (rowFields[g3Col] or "") or ""
    local g4    = g4Col and (rowFields[g4Col] or "") or ""

    -- 6) Determine how many galleries are non-empty
    local galleries = {}
    for _, g in ipairs({g1, g2, g3, g4}) do
        local trimmed = mw.text.trim(g)
        if trimmed ~= "" then
            table.insert(galleries, trimmed)
        end
    end

    -- 7) If no galleries, just return the Notes text
    if #galleries == 0 then
        return notes
    end

    -- 8) Otherwise, build a single-row table with columns for notes + each gallery
    local output = "{| class=\"wikitable\" style=\"width:100%\"\n|-\n"

    -- The first cell is notes
    output = output .. "| " .. notes

    -- Then each gallery is a separate cell
    for _, galleryFile in ipairs(galleries) do
        local trimmedFile = mw.text.trim(galleryFile)
        if trimmedFile ~= "" then
            output = output .. " || [[File:"..trimmedFile.."|250px]]"
        end
    end

    output = output .. "\n|}"

    return output
end











return p