Jump to content

Module:LuaTemplateTLD

From ICANNWiki

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

-- Module:LuaTemplateTLD
-- Versatile module for rendering TLD/ccTLD atricle templates with extensive normalization and dynamic content. It processes type and subtype values using canonical mapping, auto-detects IDN status (and when active, converts the page title to punycode with an "xn--" prefix); it also normalizes dates, country names, and website links. The module dynamically assigns categories based on country, PIC, and domain type, as well as integrating social media components along with an NTLDStats section for additional data when applicable.

local p = {}

-- Dependencies
local CanonicalForms        = require('Module:CanonicalForms')
local countryNormalization  = require('Module:CountryNormalization')
local dateNormalization     = require('Module:DateNormalization')
local linkParser            = require('Module:LinkParser')
local MultiCountryDisplay   = require('Module:MultiCountryDisplay')
local punycode              = require('Module:Punycode')
local socialFooter          = require('Module:SocialMedia')
local TemplateStructure     = require('Module:TemplateStructure')

--------------------------------------------------------------------------------
-- Configuration and Constants
--------------------------------------------------------------------------------
local Config = {
    typeMapping = {
        {canonical = "gTLD",
        synonyms = {"gtld", "generic", "tld", "generic top level domain", "generic top-level domain", "generic tld"},
        category = "[[Category:gTLD]]"},
        {canonical = "ccTLD",
        synonyms = {"cctld", "country", "cc", "country code top level domain", "country code top-level domain", "country tld"},
        category = "[[Category:ccTLD]]"}
    },
    subtypeMapping = {
        {canonical="geoTLD",
        synonyms={"geotld","geo tld","geo","geographic","geographical","geographic top level domain","geographic top-level domain","geographic tld"},
        css="tld-template-subtype-geotld",
        category="[[Category:geoTLD]]"},
        {canonical="dotBrand",
        synonyms={"dotbrand","brand","brandtld","brand tld","brand top level domain","brand top-level domain"},css="tld-template-subtype-brandtld",
        category="[[Category:dotBrand]]"},
        {canonical="Sponsored TLD",
        synonyms={"sponsored tld","sponsored","sponsored top level domain","sponsored top-level domain"},
        css="tld-template-subtype-sponsored",
        category="[[Category:Sponsored TLD]]"},
        {canonical="Legacy TLD",
        synonyms={"legacy","legacy tld","leagacy top level domain","legacy top-level domain"},
        css="tld-template-subtype-legacytld",
        category="[[Category:Legacy TLD]]"},
        {canonical="gTLD Round: 2012",
        synonyms={"gtld round: 2012", "gtld round 2012","2012 ngtld round","2012 ngtld","ngtld","2012"},
        css="tld-template-subtype-ngtld-round-2012",
        category="[[Category:gTLD Round: 2012]]"}
    },
    fields = {
        {key="type",label="Type"},
        {key="subtype",label="Subtype"},
        {key="status",label="Status"},
        {keys={"country", "territory"},label="Country"},
        {key="introduced",label="Introduced"},
        {keys={"date", "implemented"},label="Implemented"},
        {keys={"script", "language"},label="Script"},
        {key="translation",label="English version"},
        {key="ascii",label="Punycode"},
        {keys={"registry", "registryprovider"},label="Registry"},
        {key="website",label="Website"},
        {keys={"RVC", "PIC"},label="PIC/RVC"}
    },
    legacyTLDs = {
        com=true,net=true,org=true,edu=true,gov=true,mil=true,int=true,info=true,aero=true,asia=true,cat=true,coop=true,jobs=true,mobi=true,museum=true,post=true,tel=true,travel=true,xxx=true
    },
    patterns = {
        tldExtension="%.([^%.]+)$",
        countryDelimiter="([^;]+)"
    }
}

--------------------------------------------------------------------------------
-- Helper Functions
--------------------------------------------------------------------------------
local IDNHelpers = {
    processIDN = function(args, rawType)
        if args["idn"] then
            local idnVal = string.lower(args["idn"])
            if idnVal == "yes" or idnVal == "true" then return true end
        end
        return string.find(string.lower(rawType), "idn") and true or false
    end,
    getASCII = function(pageName)
        return "xn--" .. punycode.encode(pageName:gsub("^%.", ""))
    end
}

-- Gets the first non-empty value from a list of equivalent keys
local function getFieldValue(args, field)
    -- For fields with multiple equivalent keys
    if field.keys then
        for _, key in ipairs(field.keys) do
            if args[key] and args[key] ~= "" then
                return key, args[key]
            end
        end
        return nil, nil
    end
    -- For single-key fields
    return field.key, (args[field.key] and args[field.key] ~= "") and args[field.key] or nil
end

-- Process multiple country entries handled by the MultiCountryDisplay module

--------------------------------------------------------------------------------
-- Block Rendering Functions
--------------------------------------------------------------------------------
local function renderTitleBlock(args)
    return string.format('! colspan="2" class="template-title template-title-tld" | %s', 
        (args._type == "ccTLD") and "ccTLD" or "gTLD")
end

local function renderLogoBlock(args)
    return args["logo"] and args["logo"] ~= "" and string.format("|-\n| colspan=\"2\" class=\"tld-logo\" | [[Image:%s]]", args["logo"]) or ""
end

local function renderFlairBlock(args)
    local classes = {"tld-flair"}
    if args._idnFlag then
        table.insert(classes, args._type == "ccTLD" and "tld-template-idn-cctld" or "tld-template-idn-gtld")
    elseif args["subtype"] and args["subtype"] ~= "" then
        local _, css = CanonicalForms.normalize(args["subtype"], Config.subtypeMapping)
        if css then table.insert(classes, css) end
    end
    return string.format('|-\n| colspan="2" class="%s" |', table.concat(classes, " "))
end

local function renderFieldsBlock(args)
    local out = {}
    for _, field in ipairs(Config.fields) do
        local key, value = getFieldValue(args, field)
        if value then
            if key == "type" then
                value = select(1, CanonicalForms.normalize(value, Config.typeMapping)) or value
            elseif key == "subtype" then
                value = select(1, CanonicalForms.normalize(value, Config.subtypeMapping)) or value
            elseif key == "website" then
                value = string.format("[%s %s]", value, linkParser.strip(value))
            elseif key == "date" or key == "implemented" then
                value = tostring(dateNormalization.formatDate(value))
            elseif key == "country" or key == "territory" then
                value = MultiCountryDisplay.formatCountries(value)
            elseif (key == "RVC" or key == "PIC") and value:match("^%d+$") then
                value = string.format("[https://gtldresult.icann.org/applicationstatus/applicationdetails/%s Here]", value)
            end
            table.insert(out, string.format("|-\n| '''%s''':\n| %s", field.label, value))
        end
    end
    return table.concat(out, "\n")
end

local function renderNTLDStatsBlock(args, pageName)
    if args._type == "ccTLD" then return "" end
    local tldName = pageName:gsub("^%.", "")
    local tldExtension = tldName:match(Config.patterns.tldExtension)
    if Config.legacyTLDs[mw.ustring.lower(tldName)] or (tldExtension and Config.legacyTLDs[mw.ustring.lower(tldExtension)]) then
        return ""
    end
    return table.concat({
        "|-",
        '| colspan="2" class="template-divider" |',
        "|-",
        '| colspan="2" class="icannwiki-centered" | <span class="icannwiki-bold">Find out more:</span>',
        "|-",
        string.format('| colspan="2" | <div class="ntldstats icannwiki-centered">[[File:NTLDStatsLogo.png|100px|alt=NTLDStats|link=https://ntldstats.com/tld/%s]]</div>', tldName)
    }, "\n")
end

--------------------------------------------------------------------------------
-- Category Assignment
--------------------------------------------------------------------------------
local function computeCategories(args)
    local cats = {}
    -- Process country categories from either country or territory key
    local countryValue = args["country"] or args["territory"]
    if countryValue and countryValue ~= "" then
        local normalizedCountries = MultiCountryDisplay.getCountriesForCategories(countryValue)
        for _, countryName in ipairs(normalizedCountries) do
            table.insert(cats, string.format("[[Category:%s]]", countryName))
        end
    end
    -- Check both RVC and PIC keys for category assignment
    if (args["RVC"] and args["RVC"] ~= "") or (args["PIC"] and args["PIC"] ~= "") then 
        table.insert(cats, "[[Category:TLDs with RVCs]]") 
    end
    if args._idnFlag then
        table.insert(cats, "[[Category:IDN]]")
        if args._type == "ccTLD" then table.insert(cats, "[[Category:IDN ccTLD]]") end
    end
    for _, group in ipairs(Config.typeMapping) do
        if group.canonical == args._type then
            table.insert(cats, group.category)
            break
        end
    end
    if args["subtype"] and args["subtype"] ~= "" then
        local canonicalSubtype, _, subtypeCategory = CanonicalForms.normalize(args["subtype"], Config.subtypeMapping)
        if subtypeCategory and subtypeCategory ~= "" then
            table.insert(cats, subtypeCategory)
        elseif canonicalSubtype and canonicalSubtype ~= "" then
            table.insert(cats, string.format("[[Category:%s]]", canonicalSubtype))
        end
    end
    return table.concat(cats, "\n")
end

--------------------------------------------------------------------------------
-- Main Render Function
--------------------------------------------------------------------------------
function p.render(frame)
    local args = frame:getParent().args or {}
    local titleObj = mw.title.getCurrentTitle()
    local pageName = titleObj and titleObj.text or ""
    
    -- Process type and IDN status
    local rawType = args["type"] or ""
    args._type = select(1, CanonicalForms.normalize(rawType, Config.typeMapping))
    args._idnFlag = IDNHelpers.processIDN(args, rawType)
    
    -- Set ASCII value for IDN
    if args._idnFlag and titleObj then
        args["ascii"] = IDNHelpers.getASCII(pageName)
    end
    
    -- Configure and render template
    local config = {
        blocks = {
            function(a) return renderTitleBlock(a) end,
            function(a) return renderLogoBlock(a) end,
            function(a) return renderFlairBlock(a) end,
            function(a) return renderFieldsBlock(a) end,
            function(a) return socialFooter.render(a) or "" end,
            function(a) return renderNTLDStatsBlock(a, pageName) end
        }
    }
    
    return TemplateStructure.render(args, config) .. "\n" .. computeCategories(args)
end

return p