Difference between revisions of "Module:Track gauge"

From blackwiki
Jump to navigation Jump to search
blackwiki>DePiep
(Add auto gauge documentation, and maintenance categories. See Template_talk:RailGauge#Code_changes_2_May_2014)
blackwiki>DePiep
(Implement code changes (formatting options, allow fast new RG/extra data, code cleanup & efficiency, split documentation code into RG/autodocument). See Template_talk:RailGauge#Code_changes_28_May_2014.)
Line 1: Line 1:
 
-- This module implements the {{RailGauge}} template.
 
-- This module implements the {{RailGauge}} template.
-- Input can be predefined aliases (see the /data module page)
+
-- Data is in module:RailGauge/data and in module:RailGauge/extra
 
local p = {}
 
local p = {}
 
local gaugeDataAll = nil
 
local gaugeDataAll = nil
local dataPageName = 'Module:RailGauge/data'
+
local gaugeDataExtra = nil
 
+
local dataPageName = 'Msaodule:RailGauge/data' -- sandbox here
 +
local dataPageExtra = 'Module:RailGauge/extra'
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- formatUnitPlaintext
+
-- prepareArgs -- Normalise Arguments coming from an #invoke or from a module
-- Pattern '00016.5 mm' for tablesort and catsort. Can be wrapped in class="sortkey"
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function formatUnitPlaintext( rgData, unit, fmtZeroPadding, toFracChar )
+
local function prepareArgs(frame)
    -- Returns plaintext (ASCII) only. No css.
+
local origArgs
    if rgData == nil then
+
if frame == mw.getCurrentFrame() then
        return ''
+
origArgs = frame:getParent().args
    end
+
for k, v in pairs(frame.args) do
    if ( unit or rgData.def ) == 'imp' then
+
origArgs = frame.args
        local ft = ''
+
break
        local inch = ''
+
end
        local frac = ''
+
else
        if rgData.ft then
+
origArgs = frame
            ft = rgData.ft .. ' ft'
+
end
        end
+
local args = {}
        if rgData.num then
+
-- searchAlias is the cleaned value of args[1]. args[1] is kept as rawInput for error message
            frac = ' ' .. rgData.num .. '/' .. rgData.den
+
local searchAlias = ''
            if toFracChar then
+
local rawDisp
                -- as used in contentCat pagenames
+
for k, v in pairs(origArgs) do
                if frac == ' 1/8' then
+
if tonumber(k) == nil then -- Named argment
                    frac = ''
+
if k == 'disp' then
                elseif frac == ' 1/4' then
+
rawDisp = v -- Keep raw disp input to pass through plain (wiki)text
                    frac = '¼'
+
args[k] = mw.ustring.lower(v)
                elseif frac == ' 3/8' then
+
elseif k == 'first' then
                    frac = ''
+
v = mw.ustring.lower(v)
                elseif frac == ' 1/2' then
+
if v == 'met' or v == 'metric' then
                    frac = '½'
+
v = 'met'
                elseif frac == ' 3/4' then
+
elseif v == 'imp' or v == 'imperial' then
                    frac = '¾'
+
v = 'imp'
                elseif frac == ' 7/8' then
+
else k = 'trashparam_first' end
                    frac = ''
+
args[k] = v
                else
+
elseif k == 'nowrap' or k == 'wrap' then -- wrap=y deprecated; reading: nowrap=off
                    frac = frac .. ' (error: fraction character missing in module:RailGauge)'
+
v = mw.ustring.lower(v)
                end
+
if v == '' or v == 'off' or v == 'on' or v == 'all' then
            end
+
elseif v == 'inline' or (k == 'wrap' and v == 'y') then
            if rgData["in"] then
+
v = 'off'
                frac = ' ' .. rgData["in"] .. frac .. ' in'
+
else v = '' end
            else
+
args['nowrap'] = v
                frac = frac .. ' in'
+
else
            end
+
args[k] = mw.ustring.lower(v)
        else
+
end
            if rgData["in"] then
+
else
                inch = ' ' .. rgData["in"] .. ' in'
+
args[k] = v -- Keep raw input in [1] for error message
            end
+
-- Unnamed argument. If #1: the alias to be searched.
        end
+
-- Into lowercase, remove all whitespace & commas to create the search key
        return mw.text.trim( ft .. inch .. frac )
+
if k == 1 then
    else
+
searchAlias = mw.ustring.lower(mw.ustring.gsub(v, '[%s%,]', ''))
        -- metric ( mm )
+
end
        if fmtZeroPadding == nil or tonumber( fmtZeroPadding ) <= 0 then
+
end
            return rgData.id .. ' mm'
+
end
        else
+
args['searchAlias'] = searchAlias
            return string.rep( '0',
+
if rawDisp then args['rawDisp'] = rawDisp end
                  fmtZeroPadding - string.len( math.floor( tonumber( rgData.id ) ) ) )
+
return args
                  .. rgData.id .. ' mm'
 
        end
 
    end
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- anchor -- Anchor text *here* is: <span id="1000 mm">; anchor *there* is: #1000 mm.
+
-- debugReturnArgs -- Debug function.
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function anchor( rgEntry, unit, herethere )
+
function p.debugReturnArgs(frame)
    local html = require( 'Module:HtmlBuilder' )
+
local args = prepareArgs(frame)
    if rgEntry == nil then
+
local retArgs = {}
        return ''
+
for k, a in pairs(args) do
    end
+
table.insert(retArgs, k .. '=' .. a)
    unit = unit or rgEntry.def1
+
end
    local anch = formatUnitPlaintext( rgEntry,unit, 0 )
+
return 'Args: ' .. table.concat(retArgs, '; ')
    if herethere == 'there' then -- Untested, April 2014
 
        anch = '#' .. anch
 
    else
 
        anch = html.create().tag( 'span' ).attr( 'id', anch )
 
    end
 
    return tostring( anch )
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- catMentionsGaugeSize -- category name for "article mentions gauge" categories
+
-- getRgEntry -- Find entry data for a single gauge (alias)
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function catMentionsGaugeSize( mmSize, catSort, pagename, show, plaintext )
+
function p.getRgEntry(searchAlias)
    local cat -- hereo
+
gaugeDataAll = mw.loadData(dataPageName)
    -- Category page name. mmSize has format of id ( like '16.5' ).
+
if searchAlias == '' then
    cat = 'Category:Articles that mention rail gauge size' .. ' ' .. mmSize .. '&nbsp;mm'
+
return nil
    if plaintext then
+
end
        return cat
+
local rgEntry = nil
    elseif show then
+
for i, rgEntry in ipairs(gaugeDataAll) do
        -- uses colon and wikilabel; no catsort
+
for j, alias in ipairs(rgEntry.aliases) do
        local label = '|cat:mentionings'
+
if alias == searchAlias then
        cat = '[[:'.. cat .. label .. ']]'
+
return rgEntry
    else
+
end
        if ( catSort or '' ) ~= '' then
+
end
            catSort = '|' .. catSort
+
end
        else
+
-- No hit, so seach in datapage /extra
            catSort = ''
+
rgEntry = nil
        end
+
gaugeDataExtra = mw.loadData(dataPageExtra)
        cat = '[['.. cat .. catSort .. ']]'
+
for i, rgEntry in ipairs(gaugeDataExtra) do
    end
+
for j, alias in ipairs(rgEntry.aliases) do
    return cat
+
if alias == searchAlias then
 +
return rgEntry
 +
end
 +
end
 +
end
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- catMentionsGaugeSizeParent -- Parent of all "mentions" categories
+
-- noWrap -- Add span tags to prevent a string from wrapping.
-----------------------------------------------------------------------------------
 
local function catMentionsGaugeSizeParent( show )
 
    local cat
 
    -- Category page name
 
    cat = 'Category:Articles that mention a specific rail gauge'
 
    if show then
 
        -- uses colon and wikilabel
 
        cat = '[[:'.. cat .. '|Cat:Articles mentioning RailGauge]]'
 
    end
 
    return cat
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- prepareArgs -- Arguments coming from an #invoke or from a module
 
-----------------------------------------------------------------------------------
 
local function prepareArgs( 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, make lower-case and remove blank arguments for all arguments
 
    -- searchAlias is the cleaned value of [1]. [1] is kept as rawInput for error message
 
    local args = {}
 
    args["searchAlias"] = ''
 
    args["rawInput"] = origArgs[ 1 ] or ''
 
 
 
    for k, v in pairs( origArgs ) do
 
        v = mw.text.trim( v )
 
        if tonumber( k ) == nil then
 
            -- Named argment
 
            args[ k ] = mw.ustring.lower( v )
 
        else
 
            -- Unnamed argument, alias to be searched
 
            -- Into lowercase, remove all whitespace, commas to create the search key
 
            v = mw.ustring.lower( mw.ustring.gsub( v, '[%s%,]', '' ) )
 
            args[ k ] = v
 
            if k == 1 then
 
                args["searchAlias"] = v
 
            end
 
        end
 
    end
 
 
 
    return args
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- maintReportArgs -- debug feedback, convenience.
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function maintReportArgs()
+
local function noWrap(s)
    local args = prepareArgs( frame )
+
return mw.ustring.format('<span class="nowrap">%s</span>', s)
    local report = 'args: '
 
    for k, v in pairs( args ) do
 
        report = report .. k .. '='.. v .. '; '
 
    end
 
    return '<tt>' .. report .. '</tt><br>'
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- getGaugeDataSet -- Find entry data for a single gauge
+
-- frac -- A slimmed-down version of the {{frac}} template (a nowrap is to be added with the unit)
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function getGaugeDataSet( searchAlias )
+
local function frac(whole, num, den)
    if gaugeDataAll == nil then
+
return mw.ustring.format(
        gaugeDataAll = mw.loadData( dataPageName )
+
'<span class="frac">%s%s<sup>%s</sup>&frasl;<sub>%s</sub></span>',
    end
+
whole or '', whole and '<span class="visualhide">&nbsp;</span>' or '',
    local rgEntry = nil
+
num, den)
    for i, rgEntry in ipairs( gaugeDataAll ) do
 
        for j, alias in ipairs( rgEntry.aliases ) do
 
            if alias == searchAlias then
 
                return rgEntry
 
            end
 
        end
 
    end
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- noWrap -- Add span tags to prevent a string from wrapping.
+
-- catMentions -- Wikicode for "article mentions gauge" categories
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function noWrap( s )
+
function p.catMentions(rgEntry, sortlabel, doReturn)
    return mw.ustring.format( '<span class="nowrap">%s</span>', s )
+
local ns = 'Category:'
end
+
local cat
  
-----------------------------------------------------------------------------------
+
if rgEntry == nil then
-- frac -- A slimmed-down version of the {{frac}} template ( a nowrap to be added with the unit )
+
cat = 'Articles that mention a specific rail gauge' -- Parent, the container cat
-----------------------------------------------------------------------------------
+
else
local function frac( whole, num, den )
+
cat = 'Articles that mention rail gauge size ' .. rgEntry.id .. ' mm'
    return mw.ustring.format(
+
end
        '<span class="frac">%s%s<sup>%s</sup>&frasl;<sub>%s</sub></span>',
+
-- Argument 'label' can be used to add a catsort. Catsort is not used (as of 20 May 2014)
        whole or '', whole and '<span class="visualhide">&nbsp;</span>' or '', num, den
+
if sortlabel ~= nil then
        )
+
sortlabel = '|' .. sortlabel
 +
else
 +
sortlabel = ''
 +
end
 +
if doReturn ~= nil then
 +
if doReturn == 'fullpagename' then
 +
return ns .. cat
 +
elseif doReturn == 'pagename' then -- plaintext, no namespace
 +
return cat
 +
elseif doReturn == 'show' then -- colontrick
 +
return '[[:' .. ns .. cat .. sortlabel .. ']]'
 +
else -- unknown arg value
 +
return ns .. cat
 +
end
 +
else -- Returns straight categorisation (wikitext)
 +
return '[[' .. ns .. cat .. sortlabel .. ']]'
 +
end
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
 
-- formatImp -- Formats imperial units size into a single text element
 
-- formatImp -- Formats imperial units size into a single text element
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function formatImp( rgData, ulink, articleLink, pageName )
+
function p.formatImp(rgEntry, measurementToLink, setNowrap, addUnitlink)
    local ret = {}
+
local ret = {}
    local ft = rgData.ft
+
local ft = rgEntry.ft
 
+
if ft then
    if ft then
+
local ftlink = addUnitlink and measurementToLink ~= 'imp' and '[[Foot (unit)|ft]]' or 'ft'
        local ftlink = ulink and not articleLink and '[[Foot (unit)|ft]]' or 'ft'
+
table.insert(ret, mw.ustring.format('%s&nbsp;%s', ft, ftlink))
        table.insert( ret, mw.ustring.format( '%s&nbsp;%s', ft, ftlink ) )
+
end
    end
+
local inches = rgEntry['in']
    local inches = rgData['in']
+
local num = rgEntry.num
    local num = rgData.num
+
local den = rgEntry.den
    local den = rgData.den
+
if inches and not num and not den then
    if inches and not num and not den then
+
table.insert(ret, inches)
        table.insert( ret, inches )
+
elseif num and den then
    elseif num and den then
+
table.insert(ret, frac(inches, num, den))
        table.insert( ret, frac( inches, num, den ) )
+
end
    end
+
if inches or num and den then
    if inches or num and den then
+
local incheslink = addUnitlink and measurementToLink ~= 'imp' and '[[inch|in]]' or 'in'
        local incheslink = ulink and not articleLink and '[[inch|in]]' or 'in'
+
table.insert(ret, incheslink)
        table.insert( ret, incheslink )
+
end
    end
+
local gaugeSize
    local gaugeSize = noWrap( table.concat( ret, '&nbsp;' ) )
+
if setNowrap then
 
+
gaugeSize = noWrap(table.concat(ret, '&nbsp;'))
    if articleLink then
+
else
        return '[[' .. pageName .. '|' .. gaugeSize .. ']]'
+
gaugeSize = table.concat(ret, '&nbsp;')
    else
+
end
        return gaugeSize
+
if measurementToLink == 'imp' and rgEntry.pagename ~= nil then
    end
+
return mw.ustring.format('[[%s|%s]]', rgEntry.pagename, gaugeSize)
 +
else
 +
return gaugeSize
 +
end
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- formatMet -- Formats metric measurements into a single formatted element
+
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function formatMet( rgData, ulink, articleLink, pageName )
+
function p.formatMet(rgEntry, measurementToLink, setNowrap, addUnitlink)
    local m = rgData.m
+
local m = rgEntry.m
    local gaugeSize
+
local gaugeSize
 
+
if m then
    if m then
+
local mUnit = addUnitlink and measurementToLink ~= 'met' and '[[metre|m]]' or 'm'
        local munit = ulink and not articleLink and '[[metre|m]]' or 'm'
+
gaugeSize = mw.ustring.format('%s&nbsp;%s', m, mUnit)
        gaugeSize = noWrap( mw.ustring.format( '%s&nbsp;%s', m, munit ) )
+
else
    else
+
local mm = rgEntry.mm
        local mm = rgData.mm
+
mm = tonumber(mm)
        mm = tonumber( mm )
+
if mm then
        if mm then
+
mm = mw.getContentLanguage():formatNum(mm)
            mm = mw.getContentLanguage():formatNum( mm )
+
end
        end
+
local mmUnit = addUnitlink and measurementToLink ~= 'met' and '[[millimetre|mm]]' or 'mm'
        local mmunit = ulink and not articleLink and '[[millimetre|mm]]' or 'mm'
+
gaugeSize = mw.ustring.format('%s&nbsp;%s', mm, mmUnit)
        gaugeSize = noWrap( mw.ustring.format( '%s&nbsp;%s', mm, mmunit ) )
+
end
    end
+
if setNowrap then
 
+
gaugeSize = noWrap(gaugeSize)
    if articleLink then
+
end
        return '[[' .. pageName .. '|' .. gaugeSize .. ']]'
+
if measurementToLink == 'met' and rgEntry.pagename ~= nil then
    else
+
return mw.ustring.format('[[%s|%s]]', rgEntry.pagename, gaugeSize)
        return gaugeSize
+
else
    end
+
return gaugeSize
 +
end
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- compose -- Puts together the two metric, imperial measures into an output string.
+
-- formatAltName
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function compose( args, rgData )
+
function formatAltName(rgEntry, addArticleLink, disp, setNowrap)
    local definition = rgData.def1
+
-- Asserted: al=on
    local pageName = rgData.pagename or nil
+
if rgEntry.name == nil then
    local imp = formatImp( rgData, args.unitlink == 'on',
+
return ''
        args.lk=='on' and definition=='imp' and pageName, pageName )
+
end
    local met = formatMet( rgData, args.unitlink == 'on',
+
local retAlt = {}
        args.lk=='on' and definition=='met' and pageName, pageName )
+
if disp == 'br' then
    local first = args.first or rgData.def1
+
table.insert(retAlt, '<br>')
 
+
else
    if first == 'met' or first == 'metric' then
+
table.insert(retAlt, ' ')
        first = 'met'
+
end
    else
+
if setNowrap then
        first = 'imp'
+
table.insert(retAlt, '<span class="nowrap">')
    end
+
end
    local ret = {}
+
if addArticleLink and rgEntry.link then -- If not defined then fallback to unlinked name
    if first == 'met' then
+
table.insert(retAlt, rgEntry.link)
        table.insert( ret, met )
+
else
    else
+
table.insert(retAlt, rgEntry.name)
        table.insert( ret, imp )
+
end
    end
+
if setNowrap then --close tag
    local disp = args.disp
+
table.insert(retAlt, '</span>')
    if disp ~= '1' then
+
end
        local formatText
+
return table.concat(retAlt, '')
        if disp == 's' or disp == '/' then
 
            formatText = '/&#x200b;%s'
 
        elseif disp == 'or' then
 
            formatText = ' or %s'
 
        elseif disp == '=' then
 
            formatText = ' = %s'
 
        else
 
            formatText = ' (%s)'
 
        end
 
        if first == 'met' then
 
            table.insert( ret, mw.ustring.format( formatText, imp ) )
 
        else
 
            table.insert( ret, mw.ustring.format( formatText, met ) )
 
        end
 
    end
 
    ret = table.concat( ret )
 
 
 
    if args.wrap == 'y' then
 
        return ret
 
    else
 
        return noWrap( ret )
 
    end
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- main -- MAIN: the basic module
+
-- main -- The basic module
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
function p.main( frame )
+
function p.main(frame)
    local args = nil
+
-- In general: rgEntry (from RG/data) is passed to the functions, arguments are processed here.
    local rgData = nil
+
local title = mw.title.getCurrentTitle()
    local title = mw.title.getCurrentTitle()
+
local args = prepareArgs(frame)
 
+
local rgEntry = p.getRgEntry(args.searchAlias)
    args = prepareArgs( frame )
 
    -- Get the data entry for this gauge from /data subpage
 
    rgData = getGaugeDataSet( args.searchAlias )
 
 
 
    -- Categorise the page if no gauge information was found.
 
    if rgData == nil then
 
        local category = ''
 
        if title:inNamespaces( 0, 14 ) then --main=0, cat=14
 
            category = mw.ustring.format(
 
              '[[Category:Articles with template RailGauge with unrecognized input|%s, %s]]',
 
              args["rawInput"] or ' ', title.text
 
            )
 
        end
 
        return ( args["rawInput"] or '' ) .. category
 
    end
 
  
    local catMentions = ''
+
-- Categorise the page if no gauge information was found.
    if rgData.id == '1435' then
+
if rgEntry == nil then
    -- no s.g. categorization
+
local category = ''
    else
+
if title:inNamespaces(0, 14) then
        local addMentionsCat = args.addcat or 'yes'
+
local sort1
        if addMentionsCat ~= 'no' then
+
if (args[1] or '') == '' then -- Blank input, sort top.
            if title:inNamespaces( 0, 14 ) then
+
sort1 = ' '
                catMentions = catMentionsGaugeSize( rgData.id )
+
else
            end
+
sort1 = args[1] .. ', '
        end
+
end
    end
+
category = mw.ustring.format(
 
+
'[[Category:Articles with template RailGauge with unrecognized input|%s%s]]',
    -- Assemble the output.
+
sort1, title.text)
    local ret = {}
+
end
    table.insert( ret, compose( args, rgData ) )
+
return (args[1] or '') .. category
    local gaugeName = rgData.name
+
end
    local gaugeLink = rgData.link
+
-- useful:
    if args.allk == 'on' and gaugeLink then
+
-- -- return rgEntry.id .. ' <span style="font-size:85%; font-style:italic; font-family=courier">(RailGauge/sandbox is currently under construction)</span>'
        table.insert( ret, ' ' .. noWrap( gaugeLink ) )
+
-- Check and set args & rgEntry props: disp, first, nowrap, first
    elseif args.al == 'on' and gaugeName then
+
local disp = args.disp or ''
        table.insert( ret, ' ' .. noWrap( gaugeName ) )
+
local first = args.first or rgEntry.def1
    end
+
local unitlink = args.unitlink or ''
    table.insert( ret, catMentions )
+
local nowrap = args.nowrap or ''
 
+
local setNowrapElement = (nowrap == '' or nowrap == 'off') -- To prevent nested nowrap tags
    return table.concat( ret )
+
local measurementToLink
end
+
if args.lk == 'on' then
 
+
if disp == '1' then
-----------------------------------------------------------------------------------
+
measurementToLink = first -- Can make metric text links to the imp linked page
-- checkData -- Public. Performs various checks on the /data subpage.
+
else
-----------------------------------------------------------------------------------
+
measurementToLink = rgEntry.def1 -- When first=swapped, this could link 2nd measure.
function p.checkData( frame )
+
end
    local dataPage = frame and frame.args and frame.args[1] or dataPageName
+
end
    local data = mw.loadData( dataPage )
+
-- String the text elements together (compose the return table)
    local exists, dupes, dupeSort, ret = {}, {}, {}, {}
+
local ret = {}
 
+
-- nowrap opening tag
    -- Check for duplicate aliases.
+
if nowrap == 'all' or nowrap == 'on' then
    for ti, t in ipairs( data ) do
+
table.insert(ret, '<span class="nowrap">')
        for ai, alias in ipairs( t.aliases or {} ) do
+
end
            if not exists[ alias ] then
+
-- First measure
                exists[ alias ] = { ti, ai }
+
if first == 'met' then
            else
+
table.insert(ret,
                if not dupes[ alias ] then
+
p.formatMet(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
                    dupes[ alias ] = { exists[ alias ] }
+
else
                end
+
table.insert(ret,
                table.insert( dupes[ alias ], { ti, ai } )
+
p.formatImp(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
            end
+
end
        end
+
-- The joint and the second measure
    end
+
if disp == '1' then
    for alias in pairs( dupes ) do
+
else
        table.insert( dupeSort, alias )
+
local joinText = ''
    end
+
local closeDisp = ''
    table.sort( dupeSort )
+
if disp == 's' or disp == '/' then
    for i1, alias in ipairs( dupeSort ) do
+
joinText = '/&#x200b;'
        local positions = {}
+
elseif disp == 'br' then
        for i2, aliasKeys in ipairs( dupes[ alias ] ) do
+
joinText = '<br>('
            local position = mw.ustring.format( 'gauge %d, alias %d ( gauge id: <code>%s</code> )', aliasKeys[ 1 ], aliasKeys[ 2 ], data[ aliasKeys[ 1 ] ].id or '' )
+
closeDisp = ')'
            table.insert( positions, position )
+
elseif disp ~= '' then -- Is anytext
        end
+
joinText = ' ' .. args['rawDisp'] .. ' '
        local aliasText = mw.ustring.format( 'Duplicate aliases "%s" detected at the following positions: %s.', alias, mw.text.listToText( positions, '; ' ) )
+
else
        table.insert( ret, aliasText )
+
joinText = ' ('
    end
+
closeDisp = ')'
    -- Check for numerators without denominators.
+
end
    for ti, t in ipairs( data ) do
+
table.insert(ret, joinText)
        local num = t.num
+
if first ~= 'met' then
        local den = t.den
+
table.insert(ret,
        if num and not den then
+
p.formatMet(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
            table.insert( ret, mw.ustring.format( 'Numerator "%s" with no denominator detected at gauge %d ( id: <code>%s</code> ).', num, ti, t.id or '' ) )
+
else
        elseif den and not num then
+
table.insert(ret,
            table.insert( ret, mw.ustring.format( 'Denominator "%s" with no numerator detected at gauge %d ( id: <code>%s</code> ).', den, ti, t.id or '' ) )
+
p.formatImp(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
        end
+
end
    end
+
table.insert(ret, closeDisp) -- Could be ''
    -- Check for gauges with no imperial or no metric measurements.
+
end
    for ti, t in ipairs( data ) do
+
if nowrap == 'on' then -- Closing tag
        if not ( t.ft or t['in'] or t.num or t.den ) then
+
table.insert(ret, '</span>')
            table.insert( ret, mw.ustring.format( 'No imperial measurements found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
+
end
        end
+
-- Alternative name
        if not ( t.m or t.mm ) then
+
if args.al or args.allk ~= nil then
            table.insert( ret, mw.ustring.format( 'No metric measurements found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
+
local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags
        end
+
table.insert(ret, formatAltName(rgEntry, args.allk == 'on', disp, setNowrapAltname))
    end
+
end
    -- Check for non-numeric measurements.
+
-- Closing nowrap tag
    local measurements = { 'ft', 'in', 'num', 'den', 'm', 'mm' }
+
if nowrap == 'all' then
    for ti, t in ipairs( data ) do
+
table.insert(ret, '</span>')
        for mi, measurement in ipairs( measurements ) do
+
end
            local measurementVal = t[ measurement ]
+
-- Category mentionings (maintenance)
            if measurementVal and not tonumber( measurementVal ) then
+
if rgEntry.id == '1435' or (args.addcat or '') == 'no' then
                table.insert( ret, mw.ustring.format( 'Non-numeric <code>%s</code> measurement ( "%s" ) found for gauge %d ( id: <code>%s</code> ).', measurement, measurementVal, ti, t.id or '' ) )
+
-- No categorization
            end
+
elseif title:inNamespaces(0) then
        end
+
table.insert(ret, p.catMentions(rgEntry))
    end
+
end
    -- Check for gauges with no id.
+
-- Now sting the table together
    for ti, t in ipairs( data ) do
+
return table.concat(ret, '')
        if not t.id then
 
            local aliases = {}
 
            for i, alias in ipairs( t.aliases ) do
 
                table.insert( aliases, mw.ustring.format( '<code>%s</code>', alias ) )
 
            end
 
            aliases = mw.ustring.format( ' ( aliases: %s )', mw.text.listToText( aliases ) )
 
            table.insert( ret, mw.ustring.format( 'No id found for gauge %d%s.', ti, aliases or '' ) )
 
        end
 
    end
 
    -- Check for gauges with no aliases.
 
    for ti, t in ipairs( data ) do
 
        if type( t.aliases ) ~= 'table' then
 
            table.insert( ret, mw.ustring.format( 'No aliases found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
 
        else
 
            local isAlias = false
 
            for ai, alias in ipairs( t.aliases ) do
 
                isAlias = true
 
                break
 
            end
 
            if not isAlias then
 
                table.insert( ret, mw.ustring.format( 'No aliases found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
 
            end
 
        end
 
    end
 
    -- Check for named gauges with no links and gauges with links but no names.
 
    for ti, t in ipairs( data ) do
 
        if t.name and not t.link then
 
            table.insert( ret, mw.ustring.format( 'No link found for the named gauge "%s" at position %d ( id: <code>%s</code> ).', t.name, ti, t.id or '' ) )
 
        elseif t.link and not t.name then
 
            table.insert( ret, mw.ustring.format( 'No name found for the gauge with link "%s" at position %d ( id: <code>%s</code> ).', t.link, ti, t.id or '' ) )
 
        end
 
    end
 
    -- Check for invalid def1 values.
 
    for ti, t in ipairs( data ) do
 
        local def = t.def1
 
        if def ~= 'imp' and def ~= 'met' then
 
            table.insert( ret, mw.ustring.format( 'Invalid def1 value "%s" found for gauge %d ( id: <code>%s</code> ).', def or '', ti, t.id or '' ) )
 
        end
 
    end
 
    -- Check for unwanted whitespace.
 
    for ti, t in ipairs( data ) do
 
        for tkey, tval in pairs( t ) do
 
            if tkey == 'aliases' and type( tval ) == 'table' then
 
                for ai, alias in ipairs( tval ) do
 
                    if mw.ustring.find( alias, '%s' ) then
 
                        table.insert( ret, mw.ustring.format( 'Unwanted whitespace detected in gauge %d alias %d ( "%s", gauge id: <code>%s</code> ).', ti, ai, alias, t.id or '' ) )
 
                    end
 
                end
 
            elseif tkey == 'name' or tkey == 'link' or tkey == 'pagename' or tkey == 'contentcat' then
 
                if tval ~= mw.text.trim( tval ) then
 
                    table.insert( ret, mw.ustring.format( 'Unwanted whitespace detected in <code>%s</code> field of gauge %d ( "%s", gauge id: <code>%s</code> ).', tkey, ti, tval, t.id or '' ) )
 
                end
 
            elseif mw.ustring.find( tval, '%s' ) then
 
                table.insert( ret, mw.ustring.format( 'Unwanted whitespace detected in <code>%s</code> field of gauge %d ( "%s", gauge id: <code>%s</code> ).', tkey, ti, tval, t.id or '' ) )
 
            end
 
        end
 
    end
 
    -- Added April 2014: alias should not double with another id ( imp and mm not ambiguous )
 
    local self_id = ''
 
    local self_def = ''
 
    for ti, t in ipairs( data ) do
 
        self_id = t.id
 
        self_def = t.def1
 
        for iC, aliasCheck in ipairs( t.aliases ) do
 
            if tonumber( aliasCheck ) ~= nil then
 
                if self_id ~= aliasCheck then
 
                    for iTwo, tTwo in ipairs( data ) do
 
                        if aliasCheck == tTwo.id then
 
table.insert( ret, mw.ustring.format( 'Input alias %s ( %s ) from <code>id=%s mm</code> ambiguous with gauge id=<code>%s mm</code> ( %s )', aliasCheck, self_def, self_id, tTwo.id, tTwo.def1 ) )
 
                        end
 
                    end
 
                end
 
            end
 
        end
 
    end
 
 
 
    -- Return any errors found.
 
    for i, msg in ipairs( ret ) do
 
        ret[ i ] = mw.ustring.format( '<span class="error">%s</span>', msg )
 
    end
 
 
 
    if #ret > 0 then
 
        return mw.ustring.format( 'Found the following errors in %s:\n* %s', dataPageName, table.concat( ret, '\n* ' ) )
 
    else
 
        return mw.ustring.format( 'No errors found in %s.', dataPageName )
 
    end
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- documentHeader
 
-----------------------------------------------------------------------------------
 
local function documentHeader( numberOfEntries, docTitle, docState )
 
    if docTitle == '' then
 
        if ( numberOfEntries or 0 ) <= 1 then
 
            docTitle = 'Rail gauge'
 
        else
 
            docTitle = 'Rail gauges'
 
        end
 
    end
 
 
 
    if docState == '' then
 
        docState = 'collapsible uncollapsed'
 
    else
 
        docState = 'collapsible ' .. docState
 
    end
 
 
 
    local sortColHeaders = ''
 
    local sortClass = ''
 
    if ( numberOfEntries or 0 ) > 1 then
 
        sortClass = 'sortable'
 
        sortColHeaders = '' ..
 
        '\n|- ' ..
 
        '\n! style="line-height:90%;" | &nbsp; || || || || || || ||'
 
    end
 
 
 
    -- 8 columns
 
    local catMparent = catMentionsGaugeSizeParent( false )
 
    return '' ..
 
    '\n{| class="wikitable ' .. sortClass .. ' ' .. docState .. '" '
 
    .. 'style="text-align:right; width:100%;" ' ..
 
    '\n|+ style="background:#d8d8d8;" | ' .. docTitle ..
 
    '\n|-' ..
 
    '\n!'
 
    ..    ' style="background:#d8d8d8;" | Size<br>(mm)' --1
 
    .. '\n! style="background:#d8d8d8;" | Size<br>(ft,&nbsp;in)' --2
 
    .. '\n! style="background:#d8d8d8;" | Size<br>(inches)' --3
 
    .. '\n! style="background:#d8d8d8;" | Units<br>&nbsp;' --4
 
    .. '\n! style="background:#d8d8d8; width:6em;" | Aliases<br>(input&nbsp;options)' --5
 
    .. '\n! style="background:#d8d8d8; min-width:8em;" | Link<br>&nbsp;' --6
 
    .. '\n! style="background:#d8d8d8;" | [[:Category:Track gauges by size|Category]]<br>(content)' --7
 
    .. '\n! style="background:#d8d8d8;" | [[:' .. catMparent .. '|Mentionings]]<br>(maintenance)'
 
    .. sortColHeaders
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- documentFooter
 
-----------------------------------------------------------------------------------
 
local function documentFooter()
 
    return '\n|}' .. '\n'
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- createcatMentionsGaugeSize -- Create and use preload. Not used after initial creation.
 
-----------------------------------------------------------------------------------
 
function createCatMentionsGaugeSize( id )
 
    local preloadUrl = 'action=edit&preload=Template:RailGauge/preload-categorypage-railgauge-mentionings'
 
    local catM = catMentionsGaugeSize( id, nil, nil, nil, true )
 
    local qryString = mw.uri.parseQueryString( preloadUrl )
 
    return tostring( mw.uri.fullUrl( catM, qryString ) )
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- fromInputToId -- Used cleaned Alias as searchkey
 
-----------------------------------------------------------------------------------
 
local function fromInputToId( searchAlias )
 
    if gaugeDataAll == nil then
 
        gaugeDataAll = mw.loadData( dataPageName )
 
    end
 
    for i, rgEntry in ipairs( gaugeDataAll ) do
 
        for j, alias in ipairs( rgEntry.aliases ) do
 
            if alias == searchAlias then
 
                return rgEntry.id
 
            end
 
        end
 
    end
 
    -- Second search: by id
 
    if tonumber( searchAlias ) ~= nil then
 
        for i, rgEntry in ipairs( gaugeDataAll ) do
 
          if rgEntry.id == searchAlias then
 
                return rgEntry.id
 
            end
 
        end
 
    end
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- documentInchCount -- Gives the number of inches in decimals.
 
-----------------------------------------------------------------------------------
 
local function documentInchCount( rgEntry )
 
    local inches = 0
 
    if rgEntry["num"] ~= nil then
 
    inches = tonumber( ( rgEntry["num"] or 0 ) / ( rgEntry["den"] or 1 ) )
 
    end
 
    inches = tostring( inches + ( tonumber( rgEntry["ft"] or 0 ) * 12 )
 
            + tonumber( rgEntry["in"] or 0 ) )
 
    return inches
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- documentInchToMm -- Not used lately
 
-----------------------------------------------------------------------------------
 
local function documentInchToMm( inchCount )
 
    return tonumber( inchCount or 0 ) * 25.4
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- fromIdToEntrySet
 
-- From one id, make the set withall one-two-three-more entries ( met, inp, variants )
 
-----------------------------------------------------------------------------------
 
local function fromIdToEntrySet( id, searchedAlias )
 
local TableTools = require( 'Module:TableTools' )
 
local html = require( 'Module:HtmlBuilder' )
 
local htmlString = ''
 
local rowSplit = '<div style="border-top:1px solid #ccc; height:1px;"/>'
 
 
 
    -- From the id, build the set of existing entries ( met, imp, and variants )
 
    local entry = {}
 
    local defType = 0
 
    for i, rgEntry in ipairs( gaugeDataAll ) do
 
        if id == rgEntry.id then
 
            if rgEntry.def1 == 'met' and entry[ 1 ] == nil then
 
                entry[ 1 ] = rgEntry
 
                defType = defType +1
 
            elseif rgEntry.def1 == 'imp' and entry[ 2 ] == nil then
 
                entry[ 2 ] = rgEntry
 
                defType = defType + 2
 
            else
 
                entry[ 3 + TableTools.size( entry ) ] = rgEntry
 
            end
 
        end
 
    end
 
    entry = TableTools.compressSparseArray( entry )
 
    -- Entry set is now complete & clean
 
    -- Result: the entry table with entries present in data,
 
    -- In sequence if present ( 1. met, 2. imp, any extra )
 
    if entry[ 1 ] == nil then
 
        -- A non-existent id is entered? Unexpected here.
 
        return '' ..
 
        '\n|-' ..
 
        '\n| colspan=7 style="color:red; text-align:left;" |' ..
 
        'Error using [[Template:RailGauge/document gauge|RailGauge/document gauge]]:' ..
 
        ' No rail gauge defined for: "' .. ( searchedAlias or '' ) .. '"'
 
    end
 
 
 
    -- Build row from data ( all entries )
 
    -- String together various data elements per cell
 
    local inchCount = documentInchCount( entry[ 1 ] )
 
    -- sortKey
 
    local sortKey = formatUnitPlaintext( entry[ 1 ], 'met', 5 )
 
    htmlString = html.create()
 
        .tag( 'span' ).addClass( 'sortkey' ).wikitext( sortKey )
 
        sortKey = tostring( htmlString )
 
    -- aliases -- listing the input options
 
    local aliasList = {}
 
    for i, e in ipairs( entry ) do
 
        local alis = {}
 
        for j, v in ipairs( e.aliases ) do
 
            if tonumber( v ) == nil then -- No plain numbers
 
                table.insert( alis, tostring( v ) )
 
            end
 
        end
 
        -- cannot sort aliases here ( ? )
 
        for j, v in ipairs( alis ) do
 
            if string.match( v, '^%d' ) == nil then -- textual so to italic. Todo: better uc
 
                local txt = v
 
                htmlString = html.create()
 
                    .tag( 'span' ).wikitext( txt ).css( 'font-style', 'italic' )
 
                alis[ j ] = tostring( htmlString )
 
            end
 
        end
 
        table.insert ( aliasList, table.concat( alis, '; ' ) )
 
    end
 
    -- def -- Definition unit ( def1 in data )
 
    local def = {} -- definition code ( 'met' or 'imp' )
 
    local defText = {}
 
    for i, v in ipairs ( entry ) do
 
        table.insert( def, v.def1 )
 
        if v.def1 == 'imp' then
 
            table.insert( defText, 'imperial' )
 
        else
 
            table.insert( defText, 'metric' )
 
        end
 
    end
 
    -- mm; ft in -- Measurement ( number & unit; met and imp; anchor to here )
 
    local measure = {}
 
    local unitanchor = { '', '' }
 
    measure[ 1 ] = formatMet( entry[ 1 ] )
 
    measure[ 2 ] = formatImp( entry[ 1 ] ) -- both met and imp from entry[ 1 ]
 
    if math.fmod( defType, 2 ) == 1 then
 
        htmlString = html.create()
 
            .tag( 'span' ).wikitext( measure[ 1 ] ).css( 'font-weight', 'bold' )
 
        measure[ 1 ] = tostring( htmlString )
 
        unitanchor[ 1 ] = anchor( entry[ 1 ], 'met' )
 
    end
 
    if defType >= 2 then
 
        htmlString = html.create()
 
            .tag( 'span' ).wikitext( measure[ 2 ] ).css( 'font-weight', 'bold' )
 
        measure[ 2 ] = tostring( htmlString )
 
        unitanchor[ 2 ] = anchor( entry[ 1 ], 'imp' )
 
    end
 
    -- Linked page
 
    local linkPage = {}
 
    for i, e in ipairs( entry ) do
 
        table.insert( linkPage, e.pagename )
 
    end
 
    if #linkPage == 2 then
 
        if linkPage[ 1 ] == linkPage[ 2 ] then
 
            linkPage[ 2 ] = nil
 
        end
 
    end
 
    for i, lp in ipairs( linkPage ) do
 
        local fmtLp = ''
 
        if string.len( lp ) > 15 then
 
            fmtLp = '[[' .. lp .. ']]'
 
        else
 
            htmlString = html.create()
 
                .tag( 'span' ).wikitext( lp ).addClass( 'nowrap' )
 
            htmlString = tostring( htmlString )
 
            fmtLp = '[[' .. lp .. '|' .. htmlString .. ']]'
 
        end
 
        htmlString = html.create()
 
            .tag( 'span' ).css( 'text-align', 'left' ).wikitext( fmtLp )
 
        linkPage[ i ] = tostring( htmlString )
 
    end
 
    -- catContent -- This is a possible category with content (not maintenance)
 
    -- can be hardcoded in the data, or test by size pattern
 
    local catContent = {}
 
    local catTtl
 
    local label
 
    local catCsuffix = ' gauge railways'
 
    for i, e in ipairs( entry ) do
 
      if e.contentcat ~= nil then
 
          label = string.match( e.contentcat, '([%S]*)' ) or 'nomatch'
 
          table.insert( catContent,
 
                      '[[:Category:' .. e.contentcat .. '|cat:' .. label .. ']]' )
 
      end
 
    end
 
    if #catContent == 0 then
 
        if math.fmod( defType, 2 ) == 1 then
 
            label = formatUnitPlaintext( entry[ 1 ], 'met' )
 
            catTtl = mw.title.makeTitle( 14, label .. catCsuffix )
 
            if catTtl.exists then
 
                table.insert( catContent,
 
                            '[[:' .. catTtl.fullText .. '|cat:' .. noWrap( label ) .. ']]' )
 
            end
 
        end
 
        if defType >= 2 then
 
            label = formatUnitPlaintext( entry[ 1 ], 'imp', nil, true )
 
            catTtl = mw.title.makeTitle( 14, label .. catCsuffix )
 
            if catTtl.exists then
 
                table.insert( catContent,
 
                            '[[:' .. catTtl.fullText .. '|cat:' .. noWrap( label ) .. ']]' )
 
            end
 
        end
 
    end
 
    -- Mentions category
 
    local catMentions = catMentionsGaugeSize( id, catSort, title, true )
 
    htmlString = html.create()
 
        .tag( 'span' ).css( 'text-align', 'left' ).wikitext( catMentions )
 
    catMentions = tostring( htmlString )
 
    -- Create this cat (temporal, initial only)
 
    local testTitle = mw.title.getCurrentTitle()
 
    local createCat = ''
 
    if testTitle.namespace == 2 then
 
        createCat = '[' .. createCatMentionsGaugeSize( id ) .. ' c]'
 
    end
 
 
 
    -- Compose the id-row with all cell values
 
    local row = {}
 
    table.insert( row, sortKey .. unitanchor[ 1 ] .. measure[ 1 ] ) --1 met
 
    table.insert( row, sortKey .. unitanchor[ 2 ] .. measure[ 2 ] ) --2 imp
 
    table.insert( row, sortKey .. inchCount ) --3
 
    table.insert( row, table.concat( defText, rowSplit ) ) --4
 
    table.insert( row, table.concat( aliasList, rowSplit ) ) --5
 
    table.insert( row, table.concat( linkPage, rowSplit ) ) --6
 
    table.insert( row, table.concat( catContent, rowSplit ) ) --7
 
    table.insert( row, catMentions ) --8
 
 
 
    return '' ..
 
    '\n|- style="background:#e8e8e8; border-top:2px solid #aaa;" | ' ..
 
    '\n|' .. table.concat( row, ' || ' )
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- documentGauge -- Selfdocument gauge data ( one, multiple, range, all )
 
-----------------------------------------------------------------------------------
 
function p.documentGauge( frame )
 
    local TableTools = require( 'Module:TableTools' )
 
    local args = prepareArgs( frame )
 
 
 
    gaugeDataAll = mw.loadData( dataPageName )
 
    -- Series from the list
 
    -- idFrom and idTo are numerical
 
    local rgList = {}
 
    local idFrom = -1
 
    local idTo = -1
 
    for i, v in ipairs( args ) do
 
        if v == 'all' then
 
            idFrom = -math.huge
 
            idTo = math.huge
 
            break
 
        end
 
    end
 
    if args.docfrom ~= nil then
 
        idFrom = tonumber( fromInputToId( args.docfrom )
 
                or mw.ustring.gsub( '0' .. args.docfrom, 'mm', '' ) )
 
        idTo = math.huge
 
    end
 
    if args.docto ~= nil then
 
        idTo = tonumber( fromInputToId( args.docto )
 
              or mw.ustring.gsub( '0' .. args.docto, 'mm', '' ) )
 
    end
 
    if idTo > 0 then -- some list is requested from the whole data set
 
        if idFrom > idTo then
 
            local dummy = idFrom
 
            idFrom = idTo
 
            idTo = dummy
 
        end
 
        for i, rgEntry in ipairs( gaugeDataAll ) do
 
            if ( tonumber( rgEntry.id ) >= idFrom )
 
            and ( tonumber( rgEntry.id ) <= idTo ) then
 
                table.insert( rgList, tonumber( rgEntry.id ) )
 
            end
 
        end
 
        rgList = TableTools.removeDuplicates( rgList )
 
        table.sort( rgList )
 
    end
 
 
 
    -- Individual entries can be mentioned in args ( all unnamed = numbered params )
 
    -- Need a straight table to keep sequence right
 
    local argsAliases = TableTools.compressSparseArray( args )
 
    for i, argsAlias in ipairs( argsAliases ) do
 
        id = fromInputToId( argsAlias )
 
        if id ~= nil then
 
            -- Add to the top
 
            table.insert( rgList, i, tonumber( id ) )
 
        end
 
    end
 
    -- Now loop through the prepared rgList[id] and add rows to result table
 
    -- One row contains all available entries for the id ( met, imp, a third variant )
 
    local rowRGid = {}
 
    for i, numId in ipairs( rgList ) do
 
        table.insert( rowRGid, fromIdToEntrySet( tostring( numId ) ) )
 
    end
 
 
 
    return documentHeader( #rgList, args.doctitle or '', args.docstate or '' ) ..
 
        table.concat( rowRGid, '' ) .. documentFooter()
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- gaugeSizeFromTitle
 
-- Currently finds "1620 mm" when at end of title, then returns "1620". Blank when not found.
 
-- Used for cat:mentions category page.
 
-----------------------------------------------------------------------------------
 
function p.gaugeSizeFromTitle()
 
    local title = mw.title.getCurrentTitle()
 
    return string.match( title.text, '%s(%d+%.?%d*)%smm$' ) or ''
 
end
 
 
 
-----------------------------------------------------------------------------------
 
-- catSortFromTitle
 
-- Currently finds "600 mm" when at end of title, then returns "0600 mm" (for catSort).
 
-- Blank when not found. Used for cat:mentions category page.
 
-----------------------------------------------------------------------------------
 
function p.catSortFromTitle()
 
    local title = mw.title.getCurrentTitle()
 
    local catSort = string.match( title.text, '%s(%d+%.?%d*)%smm$' ) or ''
 
    if catSort ~= '' then
 
        catSort = string.rep( '0',
 
                  4 - string.len( math.floor( tonumber( catSort ) ) ) )
 
                  .. catSort .. ' mm'
 
    end
 
    if catSort == '' then
 
        return '*'
 
    else
 
        return catSort
 
    end
 
 
end
 
end
  
 
return p
 
return p

Revision as of 01:09, 28 May 2014

This module implements the {{Track gauge}} template. Please see the template page for documentation on how to use the main TrackGauge function.

Gauge data

The gauge information is stored at Module:Track gauge/data; to add new gauges, see the instructions there.

Data checks

This module includes a function that checks the data page for errors. It is used with the following code:

{{#invoke:Track gauge/autodocument|checkData|name of data page}}

The first positional parameter is the name of the data page that you want to be checked. If this is omitted, the module checks Module:Track gauge/data.

Tracking/maintenance category


-- This module implements the {{RailGauge}} template.
-- Data is in module:RailGauge/data and in module:RailGauge/extra
local p = {}
local gaugeDataAll = nil
local gaugeDataExtra = nil
local dataPageName = 'Msaodule:RailGauge/data' -- sandbox here
local dataPageExtra = 'Module:RailGauge/extra'
-----------------------------------------------------------------------------------
-- prepareArgs -- Normalise Arguments coming from an #invoke or from a module
-----------------------------------------------------------------------------------
local function prepareArgs(frame)
	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
	local args = {}
	-- searchAlias is the cleaned value of args[1]. args[1] is kept as rawInput for error message
	local searchAlias = ''
	local rawDisp
	for k, v in pairs(origArgs) do
		if tonumber(k) == nil then -- Named argment
			if k == 'disp' then
				rawDisp = v -- Keep raw disp input to pass through plain (wiki)text
				args[k] = mw.ustring.lower(v)
			elseif k == 'first' then
				v = mw.ustring.lower(v)
				if v == 'met' or v == 'metric' then
					v = 'met'
				elseif v == 'imp' or v == 'imperial' then
					v = 'imp'
				else k = 'trashparam_first' end
				args[k] = v
			elseif k == 'nowrap' or k == 'wrap' then -- wrap=y deprecated; reading: nowrap=off
				v = mw.ustring.lower(v)
				if v == '' or v == 'off' or v == 'on' or v == 'all' then
				elseif v == 'inline' or (k == 'wrap' and v == 'y') then
					v = 'off'
				else v = '' end
				args['nowrap'] = v
			else
				args[k] = mw.ustring.lower(v)
			end
		else
			args[k] = v -- Keep raw input in [1] for error message
			-- Unnamed argument. If #1: the alias to be searched.
			-- Into lowercase, remove all whitespace & commas to create the search key
			if k == 1 then
				searchAlias = mw.ustring.lower(mw.ustring.gsub(v, '[%s%,]', ''))
			end
		end
	end
	args['searchAlias'] = searchAlias
	if rawDisp then args['rawDisp'] = rawDisp end
	return args
end
-----------------------------------------------------------------------------------
-- debugReturnArgs -- Debug function.
-----------------------------------------------------------------------------------
function p.debugReturnArgs(frame)
	local args = prepareArgs(frame)
	local retArgs = {}
	for k, a in pairs(args) do
		table.insert(retArgs, k .. '=' .. a)
	end
	return 'Args: ' .. table.concat(retArgs, '; ')
end
-----------------------------------------------------------------------------------
-- getRgEntry -- Find entry data for a single gauge (alias)
-----------------------------------------------------------------------------------
function p.getRgEntry(searchAlias)
	gaugeDataAll = mw.loadData(dataPageName)
	if searchAlias == '' then
		return nil
	end
	local rgEntry = nil
	for i, rgEntry in ipairs(gaugeDataAll) do
		for j, alias in ipairs(rgEntry.aliases) do
			if alias == searchAlias then
				return rgEntry
			end
		end
	end
	-- No hit, so seach in datapage /extra
	rgEntry = nil
	gaugeDataExtra = mw.loadData(dataPageExtra)
	for i, rgEntry in ipairs(gaugeDataExtra) do
		for j, alias in ipairs(rgEntry.aliases) do
			if alias == searchAlias then
				return rgEntry
			end
		end
	end
end
-----------------------------------------------------------------------------------
-- noWrap -- Add span tags to prevent a string from wrapping.
-----------------------------------------------------------------------------------
local function noWrap(s)
	return mw.ustring.format('<span class="nowrap">%s</span>', s)
end
-----------------------------------------------------------------------------------
-- frac -- A slimmed-down version of the {{frac}} template (a nowrap is to be added with the unit)
-----------------------------------------------------------------------------------
local function frac(whole, num, den)
	return mw.ustring.format(
		'<span class="frac">%s%s<sup>%s</sup>&frasl;<sub>%s</sub></span>',
		whole or '', whole and '<span class="visualhide">&nbsp;</span>' or '',
		num, den)
end
-----------------------------------------------------------------------------------
-- catMentions -- Wikicode for "article mentions gauge" categories
-----------------------------------------------------------------------------------
function p.catMentions(rgEntry, sortlabel, doReturn)
	local ns = 'Category:'
	local cat

	if rgEntry == nil then
		cat = 'Articles that mention a specific rail gauge' -- Parent, the container cat
	else
		cat = 'Articles that mention rail gauge size ' .. rgEntry.id .. ' mm'
	end
	-- Argument 'label' can be used to add a catsort. Catsort is not used (as of 20 May 2014)
	if sortlabel ~= nil then
		sortlabel = '|' .. sortlabel
	else
		sortlabel = ''
	end
	if doReturn ~= nil then
		if doReturn == 'fullpagename' then
			return ns .. cat
		elseif doReturn == 'pagename' then -- plaintext, no namespace
			return cat
		elseif doReturn == 'show' then -- colontrick
			return '[[:' .. ns .. cat .. sortlabel .. ']]'
		else -- unknown arg value
			return ns .. cat
		end
	else -- Returns straight categorisation (wikitext)
		return '[[' .. ns .. cat .. sortlabel .. ']]'
	end
end
-----------------------------------------------------------------------------------
-- formatImp -- Formats imperial units size into a single text element
-----------------------------------------------------------------------------------
function p.formatImp(rgEntry, measurementToLink, setNowrap, addUnitlink)
	local ret = {}
	local ft = rgEntry.ft
	if ft then
		local ftlink = addUnitlink and measurementToLink ~= 'imp' and '[[Foot (unit)|ft]]' or 'ft'
		table.insert(ret, mw.ustring.format('%s&nbsp;%s', ft, ftlink))
	end
	local inches = rgEntry['in']
	local num = rgEntry.num
	local den = rgEntry.den
	if inches and not num and not den then
		table.insert(ret, inches)
	elseif num and den then
		table.insert(ret, frac(inches, num, den))
	end
	if inches or num and den then
		local incheslink = addUnitlink and measurementToLink ~= 'imp' and '[[inch|in]]' or 'in'
		table.insert(ret, incheslink)
	end
	local gaugeSize
	if setNowrap then
		gaugeSize = noWrap(table.concat(ret, '&nbsp;'))
	else
		gaugeSize = table.concat(ret, '&nbsp;')
	end
	if measurementToLink == 'imp' and rgEntry.pagename ~= nil then
		return mw.ustring.format('[[%s|%s]]', rgEntry.pagename, gaugeSize)
	else
		return gaugeSize
	end
end
-----------------------------------------------------------------------------------
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument
-----------------------------------------------------------------------------------
function p.formatMet(rgEntry, measurementToLink, setNowrap, addUnitlink)
	local m = rgEntry.m
	local gaugeSize
	if m then
		local mUnit = addUnitlink and measurementToLink ~= 'met' and '[[metre|m]]' or 'm'
		gaugeSize = mw.ustring.format('%s&nbsp;%s', m, mUnit)
	else
		local mm = rgEntry.mm
		mm = tonumber(mm)
		if mm then
			mm = mw.getContentLanguage():formatNum(mm)
		end
		local mmUnit = addUnitlink and measurementToLink ~= 'met' and '[[millimetre|mm]]' or 'mm'
		gaugeSize = mw.ustring.format('%s&nbsp;%s', mm, mmUnit)
	end
	if setNowrap then
		gaugeSize = noWrap(gaugeSize)
	end
	if measurementToLink == 'met' and rgEntry.pagename ~= nil then
		return mw.ustring.format('[[%s|%s]]', rgEntry.pagename, gaugeSize)
	else
		return gaugeSize
	end
end
-----------------------------------------------------------------------------------
-- formatAltName
-----------------------------------------------------------------------------------
function formatAltName(rgEntry, addArticleLink, disp, setNowrap)
	-- Asserted: al=on
	if rgEntry.name == nil then
		return ''
	end
	local retAlt = {}
	if disp == 'br' then
		table.insert(retAlt, '<br>')
	else
		table.insert(retAlt, ' ')
	end
	if setNowrap then
		table.insert(retAlt, '<span class="nowrap">')
	end
	if addArticleLink and rgEntry.link then -- If not defined then fallback to unlinked name
		table.insert(retAlt, rgEntry.link)
	else
		table.insert(retAlt, rgEntry.name)
	end
	if setNowrap then --close tag
		table.insert(retAlt, '</span>')
	end
	return table.concat(retAlt, '')
end
-----------------------------------------------------------------------------------
-- main -- The basic module
-----------------------------------------------------------------------------------
function p.main(frame)
	-- In general: rgEntry (from RG/data) is passed to the functions, arguments are processed here.
	local title = mw.title.getCurrentTitle()
	local args = prepareArgs(frame)
	local rgEntry = p.getRgEntry(args.searchAlias)

	-- Categorise the page if no gauge information was found.
	if rgEntry == nil then
		local category = ''
		if title:inNamespaces(0, 14) then
			local sort1
			if (args[1] or '') == '' then -- Blank input, sort top.
				sort1 = ' '
			else
				sort1 = args[1] .. ', '
			end
			category = mw.ustring.format(
				'[[Category:Articles with template RailGauge with unrecognized input|%s%s]]',
				sort1, title.text)
		end
		return (args[1] or '') .. category
	end
	-- useful:
	-- -- return rgEntry.id .. ' <span style="font-size:85%; font-style:italic; font-family=courier">(RailGauge/sandbox is currently under construction)</span>'
	-- Check and set args & rgEntry props: disp, first, nowrap, first
	local disp = args.disp or ''
	local first = args.first or rgEntry.def1
	local unitlink = args.unitlink or ''
	local nowrap = args.nowrap or ''
	local setNowrapElement = (nowrap == '' or nowrap == 'off') -- To prevent nested nowrap tags
	local measurementToLink
	if args.lk == 'on' then
		if disp == '1' then
			measurementToLink = first -- Can make metric text links to the imp linked page
		else
			measurementToLink = rgEntry.def1 -- When first=swapped, this could link 2nd measure.
		end
	end
	-- String the text elements together (compose the return table)
	local ret = {}
	-- nowrap opening tag
	if nowrap == 'all' or nowrap == 'on' then
		table.insert(ret, '<span class="nowrap">')
	end
	-- First measure
	if first == 'met' then
		table.insert(ret,
			p.formatMet(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
	else
		table.insert(ret,
			p.formatImp(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
	end
	-- The joint and the second measure
	if disp == '1' then
	else
		local joinText = ''
		local closeDisp = ''
		if disp == 's' or disp == '/' then
			joinText = '/&#x200b;'
		elseif disp == 'br' then
			joinText = '<br>('
			closeDisp = ')'
		elseif disp ~= '' then -- Is anytext
			joinText = ' ' .. args['rawDisp'] .. ' '
		else
			joinText = ' ('
			closeDisp = ')'
		end
		table.insert(ret, joinText)
		if first ~= 'met' then
			table.insert(ret,
				p.formatMet(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
		else
			table.insert(ret,
				p.formatImp(rgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
		end
		table.insert(ret, closeDisp) -- Could be ''
	end
	if nowrap == 'on' then -- Closing tag
		table.insert(ret, '</span>')
	end
	-- Alternative name
	if args.al or args.allk ~= nil then
		local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags
		table.insert(ret, formatAltName(rgEntry, args.allk == 'on', disp, setNowrapAltname))
	end
	-- Closing nowrap tag
	if nowrap == 'all' then
		table.insert(ret, '</span>')
	end
	-- Category mentionings (maintenance)
	if rgEntry.id == '1435' or (args.addcat or '') == 'no' then
		-- No categorization
	elseif title:inNamespaces(0) then
		table.insert(ret, p.catMentions(rgEntry))
	end
	-- Now sting the table together
	return table.concat(ret, '')
end

return p