Module:Message box: Difference between revisions

    From Nonbinary Wiki
    m>Mr. Stradivarius
    (fix stray nsid variable to self.nsid)
    m>Mr. Stradivarius
    (switch indentation to tabs)
    Line 21: Line 21:


    local function getTitleObject(page)
    local function getTitleObject(page)
        if type(page) == 'string' then
    if type(page) == 'string' then
            -- Get the title object, passing the function through pcall  
    -- Get the title object, passing the function through pcall  
            -- in case we are over the expensive function count limit.
    -- in case we are over the expensive function count limit.
            local success, title = pcall(mw.title.new, page)
    local success, title = pcall(mw.title.new, page)
            if success then
    if success then
                return title
    return title
            end
    end
        end
    end
    end
    end


    local function union(t1, t2)
    local function union(t1, t2)
        -- Returns the union of two arrays.
    -- Returns the union of two arrays.
        local vals = {}
    local vals = {}
        for i, v in ipairs(t1) do
    for i, v in ipairs(t1) do
            vals[v] = true
    vals[v] = true
        end
    end
        for i, v in ipairs(t2) do
    for i, v in ipairs(t2) do
            vals[v] = true
    vals[v] = true
        end
    end
        local ret = {}
    local ret = {}
        for k in pairs(vals) do
    for k in pairs(vals) do
            tinsert(ret, k)
    tinsert(ret, k)
        end
    end
        table.sort(ret)
    table.sort(ret)
        return ret
    return ret
    end
    end


    local function getArgNums(args, prefix)
    local function getArgNums(args, prefix)
        local nums = {}
    local nums = {}
        for k, v in pairs(args) do
    for k, v in pairs(args) do
            local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$')
    local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$')
            if num then
    if num then
                tinsert(nums, tonumber(num))
    tinsert(nums, tonumber(num))
            end
    end
        end
    end
        table.sort(nums)
    table.sort(nums)
        return nums
    return nums
    end
    end


    function box.getNamespaceId(ns)
    function box.getNamespaceId(ns)
        if not ns then return end
    if not ns then return end
        if type(ns) == 'string' then
    if type(ns) == 'string' then
            ns = lang:ucfirst(mw.ustring.lower(ns))
    ns = lang:ucfirst(mw.ustring.lower(ns))
            if ns == 'Main' then
    if ns == 'Main' then
                ns = 0
    ns = 0
            end
    end
        end
    end
        local nsTable = mw.site.namespaces[ns]
    local nsTable = mw.site.namespaces[ns]
        if nsTable then
    if nsTable then
            return nsTable.id
    return nsTable.id
        end
    end
    end
    end


    function box.getMboxType(nsid)
    function box.getMboxType(nsid)
        -- Gets the mbox type from a namespace number.
    -- Gets the mbox type from a namespace number.
        if nsid == 0 then
    if nsid == 0 then
            return 'ambox' -- main namespace
    return 'ambox' -- main namespace
        elseif nsid == 6 then
    elseif nsid == 6 then
            return 'imbox' -- file namespace
    return 'imbox' -- file namespace
        elseif nsid == 14 then
    elseif nsid == 14 then
            return 'cmbox' -- category namespace
    return 'cmbox' -- category namespace
        else
    else
            local nsTable = mw.site.namespaces[nsid]
    local nsTable = mw.site.namespaces[nsid]
            if nsTable and nsTable.isTalk then
    if nsTable and nsTable.isTalk then
                return 'tmbox' -- any talk namespace
    return 'tmbox' -- any talk namespace
            else
    else
                return 'ombox' -- other namespaces or invalid input
    return 'ombox' -- other namespaces or invalid input
            end
    end
        end
    end
    end
    end


    function box:addCat(ns, cat, sort)
    function box:addCat(ns, cat, sort)
        if type(cat) ~= 'string' then return end
    if type(cat) ~= 'string' then return end
        local nsVals = {'main', 'template', 'all'}
    local nsVals = {'main', 'template', 'all'}
        local tname
    local tname
        for i, val in ipairs(nsVals) do
    for i, val in ipairs(nsVals) do
            if ns == val then
    if ns == val then
                tname = ns .. 'Cats'
    tname = ns .. 'Cats'
            end
    end
        end
    end
        if not tname then
    if not tname then
            for i, val in ipairs(nsVals) do
    for i, val in ipairs(nsVals) do
                nsVals[i] = format('"%s"', val)
    nsVals[i] = format('"%s"', val)
            end
    end
            error('invalid ns parameter passed to box:addCat; valid values are ' .. mw.text.listToText(nsVals, nil, ' or '))
    error('invalid ns parameter passed to box:addCat; valid values are ' .. mw.text.listToText(nsVals, nil, ' or '))
        end
    end
        self[tname] = self[tname] or {}
    self[tname] = self[tname] or {}
        if type(sort) == 'string' then
    if type(sort) == 'string' then
            tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort))
    tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort))
        else
    else
            tinsert(self[tname], format('[[Category:%s]]', cat))
    tinsert(self[tname], format('[[Category:%s]]', cat))
        end
    end
    end
    end


    function box:addClass(class)
    function box:addClass(class)
        if type(class) ~= 'string' then return end
    if type(class) ~= 'string' then return end
        self.classes = self.classes or {}
    self.classes = self.classes or {}
        tinsert(self.classes, class)
    tinsert(self.classes, class)
    end
    end


    function box:setTitle(args)
    function box:setTitle(args)
        -- Get the title object and the namespace.
    -- Get the title object and the namespace.
        self.pageTitle = getTitleObject(args.page ~= '' and args.page)
    self.pageTitle = getTitleObject(args.page ~= '' and args.page)
        self.title = self.pageTitle or mw.title.getCurrentTitle()
    self.title = self.pageTitle or mw.title.getCurrentTitle()
        self.demospace = args.demospace ~= '' and args.demospace or nil
    self.demospace = args.demospace ~= '' and args.demospace or nil
        self.nsid = box.getNamespaceId(self.demospace) or self.title.namespace
    self.nsid = box.getNamespaceId(self.demospace) or self.title.namespace
    end
    end


    function box:getConfig(boxType)
    function box:getConfig(boxType)
        -- Get the box config data from the data page.
    -- Get the box config data from the data page.
        if boxType == 'mbox' then
    if boxType == 'mbox' then
            boxType = box.getMboxType(self.nsid)
    boxType = box.getMboxType(self.nsid)
        end
    end
        local cfg = cfgTables[boxType]
    local cfg = cfgTables[boxType]
        if not cfg then
    if not cfg then
            local boxTypes = {}
    local boxTypes = {}
            for k, v in pairs(dataTables) do
    for k, v in pairs(dataTables) do
                tinsert(boxTypes, format('"%s"', k))
    tinsert(boxTypes, format('"%s"', k))
            end
    end
            tinsert(boxTypes, '"mbox"')
    tinsert(boxTypes, '"mbox"')
            error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2)
    error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2)
        end
    end
        return cfg
    return cfg
    end
    end
       
     
    function box:removeBlankArgs(cfg, args)
    function box:removeBlankArgs(cfg, args)
        -- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
    -- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
        local newArgs = {}
    local newArgs = {}
        for k, v in pairs(args) do
    for k, v in pairs(args) do
            if v ~= '' then
    if v ~= '' then
                newArgs[k] = v
    newArgs[k] = v
            end
    end
        end
    end
        for i, param in ipairs(cfg.allowBlankParams or {}) do
    for i, param in ipairs(cfg.allowBlankParams or {}) do
            newArgs[param] = args[param]
    newArgs[param] = args[param]
        end
    end
        return newArgs
    return newArgs
    end
    end


    function box:setBoxParameters(cfg, args)
    function box:setBoxParameters(cfg, args)
        -- Get type data.
    -- Get type data.
        self.type = args.type
    self.type = args.type
        local typeData = cfg.types[self.type]
    local typeData = cfg.types[self.type]
        self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false
    self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false
        typeData = typeData or cfg.types[cfg.default]
    typeData = typeData or cfg.types[cfg.default]
        self.typeClass = typeData.class
    self.typeClass = typeData.class
        self.typeImage = typeData.image
    self.typeImage = typeData.image
     
    -- Find if the box has been wrongly substituted.
    if cfg.substCheck and args.subst == 'SUBST' then
    self.isSubstituted = true
    end


        -- Find if the box has been wrongly substituted.
    -- Find whether we are using a small message box.
        if cfg.substCheck and args.subst == 'SUBST' then
    if cfg.allowSmall and (
            self.isSubstituted = true
    cfg.smallParam and args.small == cfg.smallParam
        end
    or not cfg.smallParam and yesno(args.small)
    )
    then
    self.isSmall = true
    else
    self.isSmall = false
    end


        -- Find whether we are using a small message box.
    -- Add attributes, classes and styles.
        if cfg.allowSmall and (
    if cfg.allowId then
                cfg.smallParam and args.small == cfg.smallParam
    self.id = args.id
                or not cfg.smallParam and yesno(args.small)
    end
        )
    self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
        then
    for _, class in ipairs(cfg.classes or {}) do
            self.isSmall = true
    self:addClass(class)
        else
    end
            self.isSmall = false
    if self.isSmall then
        end
    self:addClass(cfg.smallClass or 'mbox-small')
    end
    self:addClass(self.typeClass)
    self:addClass(args.class)
    self.style = args.style


        -- Add attributes, classes and styles.
    -- Set text style.
        if cfg.allowId then
    self.textstyle = args.textstyle
            self.id = args.id
        end
        self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
        for _, class in ipairs(cfg.classes or {}) do
            self:addClass(class)
        end
        if self.isSmall then
            self:addClass(cfg.smallClass or 'mbox-small')
        end
        self:addClass(self.typeClass)
        self:addClass(args.class)
        self.style = args.style


        -- Set text style.
    -- Process data for collapsible text fields. At the moment these are only used in {{ambox}}.
        self.textstyle = args.textstyle
    self.useCollapsibleTextFields = cfg.useCollapsibleTextFields
    if self.useCollapsibleTextFields then
    self.name = args.name
    local nameTitle = getTitleObject(self.name)
    self.isTemplatePage = nameTitle and self.title.prefixedText == ('Template:' .. nameTitle.text) and true or false


        -- Process data for collapsible text fields. At the moment these are only used in {{ambox}}.
    -- Get the self.issue value.
        self.useCollapsibleTextFields = cfg.useCollapsibleTextFields
    if self.isSmall and args.smalltext then
        if self.useCollapsibleTextFields then
    self.issue = args.smalltext
            self.name = args.name
    else
            local nameTitle = getTitleObject(self.name)
    local sect
            self.isTemplatePage = nameTitle and self.title.prefixedText == ('Template:' .. nameTitle.text) and true or false
    if args.sect == '' then
    sect = 'This ' .. (cfg.sectionDefault or 'page')
    elseif type(args.sect) == 'string' then
    sect = 'This ' .. args.sect
    end
    local issue = args.issue
    issue = type(issue) == 'string' and issue or nil
    local text = args.text
    text = type(text) == 'string' and text or nil
    local issues = {}
    tinsert(issues, sect)
    tinsert(issues, issue)
    tinsert(issues, text)
    self.issue = tconcat(issues, ' ')
    end


            -- Get the self.issue value.
    -- Get the self.talk value.
            if self.isSmall and args.smalltext then
    local talk = args.talk
                self.issue = args.smalltext
    if talk == '' and self.isTemplatePage then
            else
    talk = '#'
                local sect
    end
                if args.sect == '' then
    if talk then
                    sect = 'This ' .. (cfg.sectionDefault or 'page')
    -- See if the talk link exists and is for a talk or a content namespace.
                elseif type(args.sect) == 'string' then
    local talkTitle = getTitleObject(talk)
                    sect = 'This ' .. args.sect
    if not talkTitle or not talkTitle.isTalkPage then
                end
    -- If we couldn't process the talk page link, get the talk page of the current page.
                local issue = args.issue
    local success
                issue = type(issue) == 'string' and issue or nil
    success, talkTitle = pcall(self.title.talkPageTitle, self.title)
                local text = args.text
    if not success then
                text = type(text) == 'string' and text or nil
    talkTitle = nil
                local issues = {}
    end
                tinsert(issues, sect)
    end
                tinsert(issues, issue)
    if talkTitle and talkTitle.exists then
                tinsert(issues, text)
    local talkText = 'Relevant discussion may be found on'
                self.issue = tconcat(issues, ' ')
    if talkTitle.isTalkPage then
            end
    talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText)
    else
    talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk)
    end
    self.talk = talkText
    end
    end


            -- Get the self.talk value.
    -- Get other values.
            local talk = args.talk
    self.fix = args.fix
            if talk == '' and self.isTemplatePage then
    local date
                talk = '#'
    if args.date and args.date ~= '' then
            end
    date = args.date
            if talk then
    elseif args.date == '' and self.isTemplatePage then
                -- See if the talk link exists and is for a talk or a content namespace.
    date = lang:formatDate('F Y')
                local talkTitle = getTitleObject(talk)
    end
                if not talkTitle or not talkTitle.isTalkPage then
    if date then
                    -- If we couldn't process the talk page link, get the talk page of the current page.
    self.date = format(" <small>''(%s)''</small>", date)
                    local success
    end
                    success, talkTitle = pcall(self.title.talkPageTitle, self.title)
    self.info = args.info
                    if not success then
    end
                        talkTitle = nil
                    end
                end
                if talkTitle and talkTitle.exists then
                    local talkText = 'Relevant discussion may be found on'
                    if talkTitle.isTalkPage then
                        talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText)
                    else
                        talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk)
                    end
                    self.talk = talkText
                end
            end


            -- Get other values.
    -- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
            self.fix = args.fix
    -- and also by ambox when small=yes.
            local date
    if self.isSmall then
            if args.date and args.date ~= '' then
    self.text = args.smalltext or args.text
                date = args.date
    else
            elseif args.date == '' and self.isTemplatePage then
    self.text = args.text
                date = lang:formatDate('F Y')
    end
            end
            if date then
                self.date = format(" <small>''(%s)''</small>", date)
            end
            self.info = args.info
        end


        -- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
    -- Set the below row.
        -- and also by ambox when small=yes.
    self.below = cfg.below and args.below
        if self.isSmall then
            self.text = args.smalltext or args.text
        else
            self.text = args.text
        end


        -- Set the below row.
    -- General image settings.
        self.below = cfg.below and args.below
    self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
    self.imageEmptyCell = cfg.imageEmptyCell
    if cfg.imageEmptyCellStyle then
    self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
    end


        -- General image settings.
    -- Left image settings.
        self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
    local imageLeft = self.isSmall and args.smallimage or args.image
        self.imageEmptyCell = cfg.imageEmptyCell
    if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none'
        if cfg.imageEmptyCellStyle then
    or not cfg.imageCheckBlank and imageLeft ~= 'none'
            self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
    then
        end
    self.imageLeft = imageLeft
    if not imageLeft then
    local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
    self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
    end
    end


        -- Left image settings.
    -- Right image settings.
        local imageLeft = self.isSmall and args.smallimage or args.image
    local imageRight = self.isSmall and args.smallimageright or args.imageright
        if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none'
    if not (cfg.imageRightNone and imageRight == 'none') then
            or not cfg.imageCheckBlank and imageLeft ~= 'none'
    self.imageRight = imageRight
        then
    end
            self.imageLeft = imageLeft
            if not imageLeft then
                local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
                self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
            end
        end


        -- Right image settings.
    -- Add mainspace categories. At the moment these are only used in {{ambox}}.
        local imageRight = self.isSmall and args.smallimageright or args.imageright
    if cfg.allowMainspaceCategories then
        if not (cfg.imageRightNone and imageRight == 'none') then
    if args.cat then
            self.imageRight = imageRight
    args.cat1 = args.cat
        end
    end
    self.catNums = getArgNums(args, 'cat')
    if args.category then
    args.category1 = args.category
    end
    self.categoryNums = getArgNums(args, 'category')
    if args.all then
    args.all1 = args.all
    end
    self.allNums = getArgNums(args, 'all')
    self.categoryParamNums = union(self.catNums, self.categoryNums)
    self.categoryParamNums = union(self.categoryParamNums, self.allNums)
    -- The following is roughly equivalent to the old {{Ambox/category}}.
    local date = args.date
    date = type(date) == 'string' and date
    local preposition = 'from'
    for _, num in ipairs(self.categoryParamNums) do
    local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)]
    local allCat = args['all' .. tostring(num)]
    mainCat = type(mainCat) == 'string' and mainCat
    allCat = type(allCat) == 'string' and allCat
    if mainCat and date and date ~= '' then
    local catTitle = format('%s %s %s', mainCat, preposition, date)
    self:addCat('main', catTitle)
    catTitle = getTitleObject('Category:' .. catTitle)
    if not catTitle or not catTitle.exists then
    self:addCat('main', 'Articles with invalid date parameter in template')
    end
    elseif mainCat and (not date or date == '') then
    self:addCat('main', mainCat)
    end
    if allCat then
    self:addCat('main', allCat)
    end
    end
    end


        -- Add mainspace categories. At the moment these are only used in {{ambox}}.
    -- Add template-namespace categories.
        if cfg.allowMainspaceCategories then
    self.isTemplatePage = type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name)
            if args.cat then
    if cfg.templateCategory then
                args.cat1 = args.cat
    if self.name then
            end
    if self.isTemplatePage then
            self.catNums = getArgNums(args, 'cat')
    self:addCat('template', cfg.templateCategory)
            if args.category then
    end
                args.category1 = args.category
    elseif not self.title.isSubpage then
            end
    self:addCat('template', cfg.templateCategory)
            self.categoryNums = getArgNums(args, 'category')
    end
            if args.all then
    end
                args.all1 = args.all
            end
            self.allNums = getArgNums(args, 'all')
            self.categoryParamNums = union(self.catNums, self.categoryNums)
            self.categoryParamNums = union(self.categoryParamNums, self.allNums)
            -- The following is roughly equivalent to the old {{Ambox/category}}.
            local date = args.date
            date = type(date) == 'string' and date
            local preposition = 'from'
            for _, num in ipairs(self.categoryParamNums) do
                local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)]
                local allCat = args['all' .. tostring(num)]
                mainCat = type(mainCat) == 'string' and mainCat
                allCat = type(allCat) == 'string' and allCat
                if mainCat and date and date ~= '' then
                    local catTitle = format('%s %s %s', mainCat, preposition, date)
                    self:addCat('main', catTitle)
                    catTitle = getTitleObject('Category:' .. catTitle)
                    if not catTitle or not catTitle.exists then
                        self:addCat('main', 'Articles with invalid date parameter in template')
                    end
                elseif mainCat and (not date or date == '') then
                    self:addCat('main', mainCat)
                end
                if allCat then
                    self:addCat('main', allCat)
                end
            end
        end


        -- Add template-namespace categories.
    -- Add template error category.
        self.isTemplatePage = type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name)
    if cfg.templateErrorCategory then
        if cfg.templateCategory then
    local templateErrorCategory = cfg.templateErrorCategory
            if self.name then
    local templateCat, templateSort
                if self.isTemplatePage then
    if not self.name and not self.title.isSubpage then
                    self:addCat('template', cfg.templateCategory)
    templateCat = templateErrorCategory
                end
    elseif type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name) then
            elseif not self.title.isSubpage then
    local paramsToCheck = cfg.templateErrorParamsToCheck or {}
                self:addCat('template', cfg.templateCategory)
    local count = 0
            end
    for i, param in ipairs(paramsToCheck) do
        end
    if not args[param] then
       
    count = count + 1
        -- Add template error category.
    end
        if cfg.templateErrorCategory then
    end
            local templateErrorCategory = cfg.templateErrorCategory
    if count > 0 then
            local templateCat, templateSort
    templateCat = templateErrorCategory
            if not self.name and not self.title.isSubpage then
    templateSort = tostring(count)
                templateCat = templateErrorCategory
    end
            elseif type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name) then
    if self.categoryNums and #self.categoryNums > 0 then
                local paramsToCheck = cfg.templateErrorParamsToCheck or {}
    templateCat = templateErrorCategory
                local count = 0
    templateSort = 'C'
                for i, param in ipairs(paramsToCheck) do
    end
                    if not args[param] then
    end
                        count = count + 1
    self:addCat('template', templateCat, templateSort)
                    end
    end
                end
                if count > 0 then
                    templateCat = templateErrorCategory
                    templateSort = tostring(count)
                end
                if self.categoryNums and #self.categoryNums > 0 then
                    templateCat = templateErrorCategory
                    templateSort = 'C'
                end
            end
            self:addCat('template', templateCat, templateSort)
        end


        -- Categories for all namespaces.
    -- Categories for all namespaces.
        if self.invalidTypeError then
    if self.invalidTypeError then
            local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText
    local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText
            self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
    self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
        end
    end
        if self.isSubstituted then
    if self.isSubstituted then
            self:addCat('all', 'Pages with incorrectly substituted templates')
    self:addCat('all', 'Pages with incorrectly substituted templates')
        end
    end


        -- Convert category tables to strings and pass them through [[Module:Category handler]].
    -- Convert category tables to strings and pass them through [[Module:Category handler]].
        self.categories = categoryHandler{
    self.categories = categoryHandler{
            main = tconcat(self.mainCats or {}),
    main = tconcat(self.mainCats or {}),
            template = tconcat(self.templateCats or {}),
    template = tconcat(self.templateCats or {}),
            all = tconcat(self.allCats or {}),
    all = tconcat(self.allCats or {}),
            nocat = args.nocat,
    nocat = args.nocat,
            demospace = self.demospace,
    demospace = self.demospace,
            page = self.pageTitle and self.pageTitle.prefixedText or nil
    page = self.pageTitle and self.pageTitle.prefixedText or nil
        }
    }
    end
    end


    function box:export()
    function box:export()
        local root = htmlBuilder.create()
    local root = htmlBuilder.create()


        -- Add the subst check error.
    -- Add the subst check error.
        if self.isSubstituted and self.name then
    if self.isSubstituted and self.name then
            root
    root
                .tag('b')
    .tag('b')
                    .addClass('error')
    .addClass('error')
                    .wikitext(format(
    .wikitext(format(
                        'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.',
    'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.',
                        mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
    mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
                    ))
    ))
        end
    end


        -- Create the box table.
    -- Create the box table.
        local boxTable = root.tag('table')
    local boxTable = root.tag('table')
        boxTable
    boxTable
            .attr('id', self.id)
    .attr('id', self.id)
        for i, class in ipairs(self.classes or {}) do
    for i, class in ipairs(self.classes or {}) do
            boxTable
    boxTable
                .addClass(class)
    .addClass(class)
        end
    end
        boxTable
    boxTable
            .cssText(self.style)
    .cssText(self.style)
            .attr('role', 'presentation')
    .attr('role', 'presentation')


        -- Add the left-hand image.
    -- Add the left-hand image.
        local row = boxTable.tag('tr')
    local row = boxTable.tag('tr')
        if self.imageLeft then
    if self.imageLeft then
            local imageLeftCell = row.tag('td').addClass('mbox-image')
    local imageLeftCell = row.tag('td').addClass('mbox-image')
            if self.imageCellDiv then
    if self.imageCellDiv then
                -- If we are using a div, redefine imageLeftCell so that the image is inside it.
    -- If we are using a div, redefine imageLeftCell so that the image is inside it.
                -- Divs use style="width: 52px;", which limits the image width to 52px. If any
    -- Divs use style="width: 52px;", which limits the image width to 52px. If any
                -- images in a div are wider than that, they may overlap with the text or cause
    -- images in a div are wider than that, they may overlap with the text or cause
                -- other display problems.
    -- other display problems.
                imageLeftCell = imageLeftCell.tag('div').css('width', '52px')  
    imageLeftCell = imageLeftCell.tag('div').css('width', '52px')  
            end
    end
            imageLeftCell
    imageLeftCell
                .wikitext(self.imageLeft)
    .wikitext(self.imageLeft)
        elseif self.imageEmptyCell then
    elseif self.imageEmptyCell then
            -- Some message boxes define an empty cell if no image is specified, and some don't.
    -- Some message boxes define an empty cell if no image is specified, and some don't.
            -- The old template code in templates where empty cells are specified gives the following hint:
    -- The old template code in templates where empty cells are specified gives the following hint:
            -- "No image. Cell with some width or padding necessary for text cell to have 100% width."
    -- "No image. Cell with some width or padding necessary for text cell to have 100% width."
            row.tag('td')
    row.tag('td')
                .addClass('mbox-empty-cell')  
    .addClass('mbox-empty-cell')  
                .cssText(self.imageEmptyCellStyle)
    .cssText(self.imageEmptyCellStyle)
        end
    end


        -- Add the text.
    -- Add the text.
        local textCell = row.tag('td').addClass('mbox-text')
    local textCell = row.tag('td').addClass('mbox-text')
        if self.useCollapsibleTextFields then
    if self.useCollapsibleTextFields then
            -- The message box uses advanced text parameters that allow things to be collapsible. At the
    -- The message box uses advanced text parameters that allow things to be collapsible. At the
            -- moment, only ambox uses this.
    -- moment, only ambox uses this.
            textCell
    textCell
                .cssText(self.textstyle)
    .cssText(self.textstyle)
            local textCellSpan = textCell.tag('span')
    local textCellSpan = textCell.tag('span')
            textCellSpan
    textCellSpan
                .addClass('mbox-text-span')
    .addClass('mbox-text-span')
                .wikitext(self.issue)
    .wikitext(self.issue)
            if not self.isSmall then
    if not self.isSmall then
                textCellSpan
    textCellSpan
                    .tag('span')
    .tag('span')
                        .addClass('hide-when-compact')
    .addClass('hide-when-compact')
                        .wikitext(self.talk and ' ' .. self.talk)
    .wikitext(self.talk and ' ' .. self.talk)
                        .wikitext(self.fix and ' ' .. self.fix)
    .wikitext(self.fix and ' ' .. self.fix)
            end
    end
            textCellSpan
    textCellSpan
                .wikitext(self.date and ' ' .. self.date)
    .wikitext(self.date and ' ' .. self.date)
            if not self.isSmall then
    if not self.isSmall then
                textCellSpan
    textCellSpan
                    .tag('span')
    .tag('span')
                        .addClass('hide-when-compact')
    .addClass('hide-when-compact')
                        .wikitext(self.info and ' ' .. self.info)
    .wikitext(self.info and ' ' .. self.info)
            end
    end
        else
    else
            -- Default text formatting - anything goes.
    -- Default text formatting - anything goes.
            textCell
    textCell
                .cssText(self.textstyle)
    .cssText(self.textstyle)
                .wikitext(self.text)
    .wikitext(self.text)
        end
    end


        -- Add the right-hand image.
    -- Add the right-hand image.
        if self.imageRight then
    if self.imageRight then
            local imageRightCell = row.tag('td').addClass('mbox-imageright')
    local imageRightCell = row.tag('td').addClass('mbox-imageright')
            if self.imageCellDiv then
    if self.imageCellDiv then
                imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
    imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
            end
    end
            imageRightCell
    imageRightCell
                .wikitext(self.imageRight)
    .wikitext(self.imageRight)
        end
    end


        -- Add the below row.
    -- Add the below row.
        if self.below then
    if self.below then
            boxTable.tag('tr')
    boxTable.tag('tr')
                .tag('td')
    .tag('td')
                    .attr('colspan', self.imageRight and '3' or '2')
    .attr('colspan', self.imageRight and '3' or '2')
                    .addClass('mbox-text')
    .addClass('mbox-text')
                    .cssText(self.textstyle)
    .cssText(self.textstyle)
                    .wikitext(self.below)
    .wikitext(self.below)
        end
    end


        -- Add error message for invalid type parameters.
    -- Add error message for invalid type parameters.
        if self.invalidTypeError then
    if self.invalidTypeError then
            root
    root
                .tag('div')
    .tag('div')
                    .css('text-align', 'center')
    .css('text-align', 'center')
                    .wikitext(format('This message box is using an invalid "type=%s" parameter and needs fixing.', self.type or ''))
    .wikitext(format('This message box is using an invalid "type=%s" parameter and needs fixing.', self.type or ''))
        end
    end


        -- Add categories.
    -- Add categories.
        root
    root
            .wikitext(self.categories)
    .wikitext(self.categories)


        return tostring(root)
    return tostring(root)
    end
    end


    local function main(boxType, args)
    local function main(boxType, args)
        box:setTitle(args)
    box:setTitle(args)
        local cfg = box:getConfig(boxType)
    local cfg = box:getConfig(boxType)
        args = box:removeBlankArgs(cfg, args)
    args = box:removeBlankArgs(cfg, args)
        box:setBoxParameters(cfg, args)
    box:setBoxParameters(cfg, args)
        return box:export()
    return box:export()
    end
    end


    local function makeWrapper(boxType)
    local function makeWrapper(boxType)
        return function (frame)
    return function (frame)
            -- If called via #invoke, use the args passed into the invoking
    -- If called via #invoke, use the args passed into the invoking
            -- template, or the args passed to #invoke if any exist. Otherwise
    -- template, or the args passed to #invoke if any exist. Otherwise
            -- assume args are being passed directly in from the debug console
    -- assume args are being passed directly in from the debug console
            -- or from another Lua module.
    -- or from another Lua module.
            local origArgs
    local origArgs
            if frame == mw.getCurrentFrame() then
    if frame == mw.getCurrentFrame() then
                origArgs = frame:getParent().args
    origArgs = frame:getParent().args
                for k, v in pairs(frame.args) do
    for k, v in pairs(frame.args) do
                    origArgs = frame.args
    origArgs = frame.args
                    break
    break
                end
    end
            else
    else
                origArgs = frame
    origArgs = frame
            end
    end
            -- Trim whitespace.
    -- Trim whitespace.
            local args = {}
    local args = {}
            for k, v in pairs(origArgs) do
    for k, v in pairs(origArgs) do
                if type(v) == 'string' then
    if type(v) == 'string' then
                    v = trim(v)
    v = trim(v)
                end
    end
                args[k] = v
    args[k] = v
            end
    end
            return main(boxType, args)
    return main(boxType, args)
        end
    end
    end
    end


    local p = {
    local p = {
        main = main,
    main = main,
        mbox = makeWrapper('mbox')
    mbox = makeWrapper('mbox')
    }
    }


    for boxType in pairs(cfgTables) do
    for boxType in pairs(cfgTables) do
        p[boxType] = makeWrapper(boxType)
    p[boxType] = makeWrapper(boxType)
    end
    end


    return p
    return p

    Revision as of 14:23, 13 October 2013

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

    -- This is a meta-module for producing message box templates, including {{mbox}}, {{ambox}}, {{imbox}}, {{tmbox}}, {{ombox}}, {{cmbox}} and {{fmbox}}.
    
    -- Require necessary modules.
    local htmlBuilder = require('Module:HtmlBuilder')
    local categoryHandler = require('Module:Category handler').main
    local yesno = require('Module:Yesno')
    
    -- Load the configuration page.
    local cfgTables = mw.loadData('Module:Message box/configuration')
    
    -- Get a language object for formatDate and ucfirst.
    local lang = mw.language.getContentLanguage()
    
    -- Set aliases for often-used functions to reduce table lookups.
    local format = mw.ustring.format
    local tinsert = table.insert
    local tconcat = table.concat
    local trim = mw.text.trim
    
    local box = {}
    
    local function getTitleObject(page)
    	if type(page) == 'string' then
    		-- Get the title object, passing the function through pcall 
    		-- in case we are over the expensive function count limit.
    		local success, title = pcall(mw.title.new, page)
    		if success then
    			return title
    		end
    	end
    end
    
    local function union(t1, t2)
    	-- Returns the union of two arrays.
    	local vals = {}
    	for i, v in ipairs(t1) do
    		vals[v] = true
    	end
    	for i, v in ipairs(t2) do
    		vals[v] = true
    	end
    	local ret = {}
    	for k in pairs(vals) do
    		tinsert(ret, k)
    	end
    	table.sort(ret)
    	return ret
    end
    
    local function getArgNums(args, prefix)
    	local nums = {}
    	for k, v in pairs(args) do
    		local num = mw.ustring.match(tostring(k), '^' .. prefix .. '([1-9]%d*)$')
    		if num then
    			tinsert(nums, tonumber(num))
    		end
    	end
    	table.sort(nums)
    	return nums
    end
    
    function box.getNamespaceId(ns)
    	if not ns then return end
    	if type(ns) == 'string' then
    		ns = lang:ucfirst(mw.ustring.lower(ns))
    		if ns == 'Main' then
    			ns = 0
    		end
    	end
    	local nsTable = mw.site.namespaces[ns]
    	if nsTable then
    		return nsTable.id
    	end
    end
    
    function box.getMboxType(nsid)
    	-- Gets the mbox type from a namespace number.
    	if nsid == 0 then
    		return 'ambox' -- main namespace
    	elseif nsid == 6 then
    		return 'imbox' -- file namespace
    	elseif nsid == 14 then
    		return 'cmbox' -- category namespace
    	else
    		local nsTable = mw.site.namespaces[nsid]
    		if nsTable and nsTable.isTalk then
    			return 'tmbox' -- any talk namespace
    		else
    			return 'ombox' -- other namespaces or invalid input
    		end
    	end
    end
    
    function box:addCat(ns, cat, sort)
    	if type(cat) ~= 'string' then return end
    	local nsVals = {'main', 'template', 'all'}
    	local tname
    	for i, val in ipairs(nsVals) do
    		if ns == val then
    			tname = ns .. 'Cats'
    		end
    	end
    	if not tname then
    		for i, val in ipairs(nsVals) do
    			nsVals[i] = format('"%s"', val)
    		end
    		error('invalid ns parameter passed to box:addCat; valid values are ' .. mw.text.listToText(nsVals, nil, ' or '))
    	end
    	self[tname] = self[tname] or {}
    	if type(sort) == 'string' then
    		tinsert(self[tname], format('[[Category:%s|%s]]', cat, sort))
    	else
    		tinsert(self[tname], format('[[Category:%s]]', cat))
    	end
    end
    
    function box:addClass(class)
    	if type(class) ~= 'string' then return end
    	self.classes = self.classes or {}
    	tinsert(self.classes, class)
    end
    
    function box:setTitle(args)
    	-- Get the title object and the namespace.
    	self.pageTitle = getTitleObject(args.page ~= '' and args.page)
    	self.title = self.pageTitle or mw.title.getCurrentTitle()
    	self.demospace = args.demospace ~= '' and args.demospace or nil
    	self.nsid = box.getNamespaceId(self.demospace) or self.title.namespace
    end
    
    function box:getConfig(boxType)
    	-- Get the box config data from the data page.
    	if boxType == 'mbox' then
    		boxType = box.getMboxType(self.nsid)
    	end
    	local cfg = cfgTables[boxType]
    	if not cfg then
    		local boxTypes = {}
    		for k, v in pairs(dataTables) do
    			tinsert(boxTypes, format('"%s"', k))
    		end
    		tinsert(boxTypes, '"mbox"')
    		error(format('invalid message box type "%s"; valid types are %s', tostring(boxType), mw.text.listToText(boxTypes)), 2)
    	end
    	return cfg
    end
    
    function box:removeBlankArgs(cfg, args)
    	-- Only allow blank arguments for the parameter names listed in cfg.allowBlankParams.
    	local newArgs = {}
    	for k, v in pairs(args) do
    		if v ~= '' then
    			newArgs[k] = v
    		end
    	end
    	for i, param in ipairs(cfg.allowBlankParams or {}) do
    		newArgs[param] = args[param]
    	end
    	return newArgs
    end
    
    function box:setBoxParameters(cfg, args)
    	-- Get type data.
    	self.type = args.type
    	local typeData = cfg.types[self.type]
    	self.invalidTypeError = cfg.showInvalidTypeError and self.type and not typeData and true or false
    	typeData = typeData or cfg.types[cfg.default]
    	self.typeClass = typeData.class
    	self.typeImage = typeData.image
    
    	-- Find if the box has been wrongly substituted.
    	if cfg.substCheck and args.subst == 'SUBST' then
    		self.isSubstituted = true
    	end
    
    	-- Find whether we are using a small message box.
    	if cfg.allowSmall and (
    			cfg.smallParam and args.small == cfg.smallParam
    			or not cfg.smallParam and yesno(args.small)
    		)
    	then
    		self.isSmall = true
    	else
    		self.isSmall = false
    	end
    
    	-- Add attributes, classes and styles.
    	if cfg.allowId then
    		self.id = args.id
    	end
    	self:addClass(cfg.usePlainlinksParam and yesno(args.plainlinks or true) and 'plainlinks')
    	for _, class in ipairs(cfg.classes or {}) do
    		self:addClass(class)
    	end
    	if self.isSmall then
    		self:addClass(cfg.smallClass or 'mbox-small')
    	end
    	self:addClass(self.typeClass)
    	self:addClass(args.class)
    	self.style = args.style
    
    	-- Set text style.
    	self.textstyle = args.textstyle
    
    	-- Process data for collapsible text fields. At the moment these are only used in {{ambox}}.
    	self.useCollapsibleTextFields = cfg.useCollapsibleTextFields 
    	if self.useCollapsibleTextFields then
    		self.name = args.name
    		local nameTitle = getTitleObject(self.name)
    		self.isTemplatePage = nameTitle and self.title.prefixedText == ('Template:' .. nameTitle.text) and true or false
    
    		-- Get the self.issue value.
    		if self.isSmall and args.smalltext then
    			self.issue = args.smalltext
    		else
    			local sect
    			if args.sect == '' then
    				sect = 'This ' .. (cfg.sectionDefault or 'page')
    			elseif type(args.sect) == 'string' then
    				sect = 'This ' .. args.sect
    			end
    			local issue = args.issue
    			issue = type(issue) == 'string' and issue or nil
    			local text = args.text
    			text = type(text) == 'string' and text or nil
    			local issues = {}
    			tinsert(issues, sect)
    			tinsert(issues, issue)
    			tinsert(issues, text)
    			self.issue = tconcat(issues, ' ')
    		end
    
    		-- Get the self.talk value.
    		local talk = args.talk
    		if talk == '' and self.isTemplatePage then
    			talk = '#'
    		end
    		if talk then
    			-- See if the talk link exists and is for a talk or a content namespace.
    			local talkTitle = getTitleObject(talk)
    			if not talkTitle or not talkTitle.isTalkPage then
    				-- If we couldn't process the talk page link, get the talk page of the current page.
    				local success
    				success, talkTitle = pcall(self.title.talkPageTitle, self.title)
    				if not success then
    					talkTitle = nil
    				end
    			end
    			if talkTitle and talkTitle.exists then
    				local talkText = 'Relevant discussion may be found on'
    				if talkTitle.isTalkPage then
    					talkText = format('%s [[%s|%s]].', talkText, talk, talkTitle.prefixedText)
    				else
    					talkText = format('%s the [[%s#%s|talk page]].', talkText, talkTitle.prefixedText, talk)
    				end
    				self.talk = talkText
    			end
    		end
    
    		-- Get other values.
    		self.fix = args.fix
    		local date
    		if args.date and args.date ~= '' then
    			date = args.date
    		elseif args.date == '' and self.isTemplatePage then
    			date = lang:formatDate('F Y')
    		end
    		if date then
    			self.date = format(" <small>''(%s)''</small>", date)
    		end
    		self.info = args.info
    	end
    
    	-- Set the non-collapsible text field. At the moment this is used by all box types other than ambox,
    	-- and also by ambox when small=yes.
    	if self.isSmall then
    		self.text = args.smalltext or args.text
    	else
    		self.text = args.text
    	end
    
    	-- Set the below row.
    	self.below = cfg.below and args.below
    
    	-- General image settings.
    	self.imageCellDiv = not self.isSmall and cfg.imageCellDiv and true or false
    	self.imageEmptyCell = cfg.imageEmptyCell
    	if cfg.imageEmptyCellStyle then
    		self.imageEmptyCellStyle = 'border:none;padding:0px;width:1px'
    	end
    
    	-- Left image settings.
    	local imageLeft = self.isSmall and args.smallimage or args.image
    	if cfg.imageCheckBlank and imageLeft ~= 'blank' and imageLeft ~= 'none'
    		or not cfg.imageCheckBlank and imageLeft ~= 'none'
    	then
    		self.imageLeft = imageLeft
    		if not imageLeft then
    			local imageSize = self.isSmall and (cfg.imageSmallSize or '30x30px') or '40x40px'
    			self.imageLeft = format('[[File:%s|%s|link=|alt=]]', self.typeImage or 'Imbox notice.png', imageSize)
    		end
    	end
    
    	-- Right image settings.
    	local imageRight = self.isSmall and args.smallimageright or args.imageright
    	if not (cfg.imageRightNone and imageRight == 'none') then
    		self.imageRight = imageRight
    	end
    
    	-- Add mainspace categories. At the moment these are only used in {{ambox}}.
    	if cfg.allowMainspaceCategories then
    		if args.cat then
    			args.cat1 = args.cat
    		end
    		self.catNums = getArgNums(args, 'cat')
    		if args.category then
    			args.category1 = args.category
    		end
    		self.categoryNums = getArgNums(args, 'category')
    		if args.all then
    			args.all1 = args.all
    		end
    		self.allNums = getArgNums(args, 'all')
    		self.categoryParamNums = union(self.catNums, self.categoryNums)
    		self.categoryParamNums = union(self.categoryParamNums, self.allNums)
    		-- The following is roughly equivalent to the old {{Ambox/category}}.
    		local date = args.date
    		date = type(date) == 'string' and date
    		local preposition = 'from'
    		for _, num in ipairs(self.categoryParamNums) do
    			local mainCat = args['cat' .. tostring(num)] or args['category' .. tostring(num)]
    			local allCat = args['all' .. tostring(num)]
    			mainCat = type(mainCat) == 'string' and mainCat
    			allCat = type(allCat) == 'string' and allCat
    			if mainCat and date and date ~= '' then
    				local catTitle = format('%s %s %s', mainCat, preposition, date)
    				self:addCat('main', catTitle)
    				catTitle = getTitleObject('Category:' .. catTitle)
    				if not catTitle or not catTitle.exists then
    					self:addCat('main', 'Articles with invalid date parameter in template')
    				end
    			elseif mainCat and (not date or date == '') then
    				self:addCat('main', mainCat)
    			end
    			if allCat then
    				self:addCat('main', allCat)
    			end
    		end
    	end
    
    	-- Add template-namespace categories.
    	self.isTemplatePage = type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name)
    	if cfg.templateCategory then
    		if self.name then
    			if self.isTemplatePage then
    				self:addCat('template', cfg.templateCategory)
    			end
    		elseif not self.title.isSubpage then
    			self:addCat('template', cfg.templateCategory)
    		end
    	end
    
    	-- Add template error category.
    	if cfg.templateErrorCategory then
    		local templateErrorCategory = cfg.templateErrorCategory
    		local templateCat, templateSort
    		if not self.name and not self.title.isSubpage then
    			templateCat = templateErrorCategory
    		elseif type(self.name) == 'string' and self.title.prefixedText == ('Template:' .. self.name) then
    			local paramsToCheck = cfg.templateErrorParamsToCheck or {}
    			local count = 0
    			for i, param in ipairs(paramsToCheck) do
    				if not args[param] then
    					count = count + 1
    				end
    			end
    			if count > 0 then
    				templateCat = templateErrorCategory
    				templateSort = tostring(count)
    			end
    			if self.categoryNums and #self.categoryNums > 0 then
    				templateCat = templateErrorCategory
    				templateSort = 'C'
    			end
    		end
    		self:addCat('template', templateCat, templateSort)
    	end
    
    	-- Categories for all namespaces.
    	if self.invalidTypeError then
    		local allSort = (self.nsid == 0 and 'Main:' or '') .. self.title.prefixedText
    		self:addCat('all', 'Wikipedia message box parameter needs fixing', allSort)
    	end
    	if self.isSubstituted then
    		self:addCat('all', 'Pages with incorrectly substituted templates')
    	end
    
    	-- Convert category tables to strings and pass them through [[Module:Category handler]].
    	self.categories = categoryHandler{
    		main = tconcat(self.mainCats or {}),
    		template = tconcat(self.templateCats or {}),
    		all = tconcat(self.allCats or {}),
    		nocat = args.nocat,
    		demospace = self.demospace,
    		page = self.pageTitle and self.pageTitle.prefixedText or nil
    	}
    end
    
    function box:export()
    	local root = htmlBuilder.create()
    
    	-- Add the subst check error.
    	if self.isSubstituted and self.name then
    		root
    			.tag('b')
    			.addClass('error')
    			.wikitext(format(
    				'Template <code>%s[[Template:%s|%s]]%s</code> has been incorrectly substituted.',
    				mw.text.nowiki('{{'), self.name, self.name, mw.text.nowiki('}}')
    			))
    	end
    
    	-- Create the box table.
    	local boxTable = root.tag('table')
    	boxTable
    		.attr('id', self.id)
    	for i, class in ipairs(self.classes or {}) do
    		boxTable
    			.addClass(class)
    	end
    	boxTable
    		.cssText(self.style)
    		.attr('role', 'presentation')
    
    	-- Add the left-hand image.
    	local row = boxTable.tag('tr')
    	if self.imageLeft then
    		local imageLeftCell = row.tag('td').addClass('mbox-image')
    		if self.imageCellDiv then
    			-- If we are using a div, redefine imageLeftCell so that the image is inside it.
    			-- Divs use style="width: 52px;", which limits the image width to 52px. If any
    			-- images in a div are wider than that, they may overlap with the text or cause
    			-- other display problems.
    			imageLeftCell = imageLeftCell.tag('div').css('width', '52px') 
    		end
    		imageLeftCell
    			.wikitext(self.imageLeft)
    	elseif self.imageEmptyCell then
    		-- Some message boxes define an empty cell if no image is specified, and some don't.
    		-- The old template code in templates where empty cells are specified gives the following hint:
    		-- "No image. Cell with some width or padding necessary for text cell to have 100% width."
    		row.tag('td')
    			.addClass('mbox-empty-cell') 
    			.cssText(self.imageEmptyCellStyle)
    	end
    
    	-- Add the text.
    	local textCell = row.tag('td').addClass('mbox-text')
    	if self.useCollapsibleTextFields then
    		-- The message box uses advanced text parameters that allow things to be collapsible. At the
    		-- moment, only ambox uses this.
    		textCell
    			.cssText(self.textstyle)
    		local textCellSpan = textCell.tag('span')
    		textCellSpan
    			.addClass('mbox-text-span')
    			.wikitext(self.issue)
    		if not self.isSmall then
    			textCellSpan
    				.tag('span')
    					.addClass('hide-when-compact')
    					.wikitext(self.talk and ' ' .. self.talk)
    					.wikitext(self.fix and ' ' .. self.fix)
    		end
    		textCellSpan
    			.wikitext(self.date and ' ' .. self.date)
    		if not self.isSmall then
    			textCellSpan
    				.tag('span')
    				.addClass('hide-when-compact')
    				.wikitext(self.info and ' ' .. self.info)
    		end
    	else
    		-- Default text formatting - anything goes.
    		textCell
    			.cssText(self.textstyle)
    			.wikitext(self.text)
    	end
    
    	-- Add the right-hand image.
    	if self.imageRight then
    		local imageRightCell = row.tag('td').addClass('mbox-imageright')
    		if self.imageCellDiv then
    			imageRightCell = imageRightCell.tag('div').css('width', '52px') -- If we are using a div, redefine imageRightCell so that the image is inside it.
    		end
    		imageRightCell
    			.wikitext(self.imageRight)
    	end
    
    	-- Add the below row.
    	if self.below then
    		boxTable.tag('tr')
    			.tag('td')
    				.attr('colspan', self.imageRight and '3' or '2')
    				.addClass('mbox-text')
    				.cssText(self.textstyle)
    				.wikitext(self.below)
    	end
    
    	-- Add error message for invalid type parameters.
    	if self.invalidTypeError then
    		root
    			.tag('div')
    				.css('text-align', 'center')
    				.wikitext(format('This message box is using an invalid "type=%s" parameter and needs fixing.', self.type or ''))
    	end
    
    	-- Add categories.
    	root
    		.wikitext(self.categories)
    
    	return tostring(root)
    end
    
    local function main(boxType, args)
    	box:setTitle(args)
    	local cfg = box:getConfig(boxType)
    	args = box:removeBlankArgs(cfg, args)
    	box:setBoxParameters(cfg, args)
    	return box:export()
    end
    
    local function makeWrapper(boxType)
    	return function (frame)
    		-- If called via #invoke, use the args passed into the invoking
    		-- template, or the args passed to #invoke if any exist. Otherwise
    		-- assume args are being passed directly in from the debug console
    		-- or from another Lua module.
    		local origArgs
    		if frame == mw.getCurrentFrame() then
    			origArgs = frame:getParent().args
    			for k, v in pairs(frame.args) do
    				origArgs = frame.args
    				break
    			end
    		else
    			origArgs = frame
    		end
    		-- Trim whitespace.
    		local args = {}
    		for k, v in pairs(origArgs) do
    			if type(v) == 'string' then
    				v = trim(v)
    			end
    			args[k] = v
    		end
    		return main(boxType, args)
    	end
    end
    
    local p = {
    	main = main,
    	mbox = makeWrapper('mbox')
    }
    
    for boxType in pairs(cfgTables) do
    	p[boxType] = makeWrapper(boxType)
    end
    
    return p