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)
m (36 revisions imported)
 
(15 intermediate revisions by 3 users not shown)
Line 1: Line 1:
-- This module implements the {{RailGauge}} template.
+
-- This module implements the {{Track gauge}} template.
-- Input can be predefined aliases (see the /data module page)
+
-- Data is in Module:Track gauge/data
 
local p = {}
 
local p = {}
 
local gaugeDataAll = nil
 
local gaugeDataAll = nil
local dataPageName = 'Module:RailGauge/data'
+
local dataPageName = 'Module:Track gauge/data' -- set /data/sandbox here
 
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- 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 rawInput in [1] for error message
            end
+
if k == 1 then
        end
+
-- Unnamed argument, the alias to be searched
        return mw.text.trim( ft .. inch .. frac )
+
-- Cleanup
    else
+
searchAlias = p.normaliseAliasInput(v)
        -- 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.
+
-- normaliseAliasInput
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function anchor( rgEntry, unit, herethere )
+
function p.normaliseAliasInput(aliasIn)
    local html = require( 'Module:HtmlBuilder' )
+
local a
    if rgEntry == nil then
+
a = mw.ustring.lower(mw.ustring.gsub(aliasIn, '[%s%,]', ''))
        return ''
+
a = mw.ustring.gsub(a, '&nbsp;', '')
    end
+
a = mw.ustring.gsub(a, 'gauge$', '')
    unit = unit or rgEntry.def1
+
a = mw.ustring.gsub(a, "'", "ft")
    local anch = formatUnitPlaintext( rgEntry,unit, 0 )
+
a = mw.ustring.gsub(a, '"', 'in')
    if herethere == 'there' then -- Untested, April 2014
+
a = mw.ustring.gsub(a, '⁄', '/')
        anch = '#' .. anch
+
a = mw.ustring.gsub(a, '&frasl;', '/')
    else
+
return a
        anch = html.create().tag( 'span' ).attr( 'id', anch )
 
    end
 
    return tostring( anch )
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- catMentionsGaugeSize -- category name for "article mentions gauge" categories
+
-- debugReturnArgs -- Debug function.
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function catMentionsGaugeSize( mmSize, catSort, pagename, show, plaintext )
+
function p.debugReturnArgs(frame)
    local cat -- hereo
+
local args = prepareArgs(frame)
    -- Category page name. mmSize has format of id ( like '16.5' ).
+
local retArgs = {}
    cat = 'Category:Articles that mention rail gauge size' .. ' ' .. mmSize .. '&nbsp;mm'
+
for k, a in pairs(args) do
    if plaintext then
+
table.insert(retArgs, k .. '=' .. a)
        return cat
+
end
    elseif show then
+
return 'Args: ' .. table.concat(retArgs, '; ')
        -- uses colon and wikilabel; no catsort
 
        local label = '|cat:mentionings'
 
        cat = '[[:'.. cat .. label .. ']]'
 
    else
 
        if ( catSort or '' ) ~= '' then
 
            catSort = '|' .. catSort
 
        else
 
            catSort = ''
 
        end
 
        cat = '[['.. cat .. catSort .. ']]'
 
    end
 
    return cat
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- catMentionsGaugeSizeParent -- Parent of all "mentions" categories
+
-- getTrackGaugeEntry -- Find entry data for a single gauge (alias)
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function catMentionsGaugeSizeParent( show )
+
function p.getTrackGaugeEntry(searchAlias)
    local cat
+
gaugeDataAll = mw.loadData(dataPageName)
    -- Category page name
+
if searchAlias == '' then
    cat = 'Category:Articles that mention a specific rail gauge'
+
return nil
    if show then
+
end
        -- uses colon and wikilabel
+
local tgEntry = nil
        cat = '[[:'.. cat .. '|Cat:Articles mentioning RailGauge]]'
+
for i, tgEntry in ipairs(gaugeDataAll) do
    end
+
for j, alias in ipairs(tgEntry.aliases) do
    return cat
+
if alias == searchAlias then
 +
return tgEntry
 +
end
 +
end
 +
end
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- prepareArgs -- Arguments coming from an #invoke or from a module
+
-- noWrap -- Add span tags to prevent a string from wrapping.
-----------------------------------------------------------------------------------
 
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 noWrap(s)
-----------------------------------------------------------------------------------
+
return mw.ustring.format('<span class="nowrap">%s</span>', s)
local function maintReportArgs()
 
    local args = prepareArgs( frame )
 
    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(tgEntry, sortlabel, doReturn)
    return mw.ustring.format( '<span class="nowrap">%s</span>', s )
+
local ns = 'Category:'
end
+
local cat
  
-----------------------------------------------------------------------------------
+
if tgEntry == nil then
-- frac -- A slimmed-down version of the {{frac}} template ( a nowrap to be added with the unit )
+
-- Parent, the container cat
-----------------------------------------------------------------------------------
+
cat = 'Articles that mention a specific track gauge'
local function frac( whole, num, den )
+
else
    return mw.ustring.format(
+
cat = 'Articles that mention track gauge ' .. tgEntry.id .. ' mm'
        '<span class="frac">%s%s<sup>%s</sup>&frasl;<sub>%s</sub></span>',
+
end
        whole or '', whole and '<span class="visualhide">&nbsp;</span>' or '', num, den
+
-- 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
 
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(tgEntry, measurementToLink, setNowrap, addUnitlink)
    local ret = {}
+
local ret = {}
    local ft = rgData.ft
+
local ft = tgEntry.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 = tgEntry['in']
    local inches = rgData['in']
+
local num = tgEntry.num
    local num = rgData.num
+
local den = tgEntry.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 tgEntry.pagename ~= nil then
    end
+
return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
end
+
else
 
+
return gaugeSize
-----------------------------------------------------------------------------------
+
end
-- formatMet -- Formats metric measurements into a single formatted element
 
-----------------------------------------------------------------------------------
 
local function formatMet( rgData, ulink, articleLink, pageName )
 
    local m = rgData.m
 
    local gaugeSize
 
 
 
    if m then
 
        local munit = ulink and not articleLink and '[[metre|m]]' or 'm'
 
        gaugeSize = noWrap( mw.ustring.format( '%s&nbsp;%s', m, munit ) )
 
    else
 
        local mm = rgData.mm
 
        mm = tonumber( mm )
 
        if mm then
 
            mm = mw.getContentLanguage():formatNum( mm )
 
        end
 
        local mmunit = ulink and not articleLink and '[[millimetre|mm]]' or 'mm'
 
        gaugeSize = noWrap( mw.ustring.format( '%s&nbsp;%s', mm, mmunit ) )
 
    end
 
 
 
    if articleLink then
 
        return '[[' .. pageName .. '|' .. gaugeSize .. ']]'
 
    else
 
        return gaugeSize
 
    end
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- compose -- Puts together the two metric, imperial measures into an output string.
+
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
local function compose( args, rgData )
+
function p.formatMet(tgEntry, measurementToLink, setNowrap, addUnitlink)
    local definition = rgData.def1
+
local m = tgEntry.m
    local pageName = rgData.pagename or nil
+
local gaugeSize
    local imp = formatImp( rgData, args.unitlink == 'on',
+
if m then
        args.lk=='on' and definition=='imp' and pageName, pageName )
+
local mUnit = addUnitlink and measurementToLink ~= 'met' and '[[metre|m]]' or 'm'
    local met = formatMet( rgData, args.unitlink == 'on',
+
gaugeSize = mw.ustring.format('%s&nbsp;%s', m, mUnit)
        args.lk=='on' and definition=='met' and pageName, pageName )
+
else
    local first = args.first or rgData.def1
+
local mm = tgEntry.mm
 
+
mm = tonumber(mm)
    if first == 'met' or first == 'metric' then
+
if mm then
        first = 'met'
+
mm = mw.getContentLanguage():formatNum(mm)
    else
+
end
        first = 'imp'
+
local mmUnit = addUnitlink and measurementToLink ~= 'met' and '[[millimetre|mm]]' or 'mm'
    end
+
gaugeSize = mw.ustring.format('%s&nbsp;%s', mm, mmUnit)
    local ret = {}
+
end
    if first == 'met' then
+
if setNowrap then
        table.insert( ret, met )
+
gaugeSize = noWrap(gaugeSize)
    else
+
end
        table.insert( ret, imp )
+
if measurementToLink == 'met' and tgEntry.pagename ~= nil then
    end
+
return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
    local disp = args.disp
+
else
    if disp ~= '1' then
+
return gaugeSize
        local formatText
+
end
        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
+
-- formatAltName
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
function p.main( frame )
+
function formatAltName(tgEntry, addGaugeName, addGaugeNameLink, disp, setNowrap, engvar)
    local args = nil
+
-- Assumed: at least one to add is true.
    local rgData = nil
+
if tgEntry.name == nil then
    local title = mw.title.getCurrentTitle()
+
-- Not checked: link does exist alone
 
+
return ''
    args = prepareArgs( frame )
+
end
    -- Get the data entry for this gauge from /data subpage
+
local retAlt = {}
    rgData = getGaugeDataSet( args.searchAlias )
+
if disp == 'br' then
 
+
table.insert(retAlt, '<br />')
    -- Categorise the page if no gauge information was found.
+
else
    if rgData == nil then
+
table.insert(retAlt, ' ')
        local category = ''
+
end
        if title:inNamespaces( 0, 14 ) then --main=0, cat=14
+
if setNowrap then
            category = mw.ustring.format(
+
table.insert(retAlt, '<span class="nowrap">')
              '[[Category:Articles with template RailGauge with unrecognized input|%s, %s]]',
+
end
              args["rawInput"] or ' ', title.text
+
if addGaugeNameLink then
            )
+
if engvar == 'en-us' then
        end
+
-- Current implementations (2016): metER for metRE (1000-met, 1009-met)
        return ( args["rawInput"] or '' ) .. category
+
table.insert(retAlt, tgEntry.en_US_link or tgEntry.link or tgEntry.name)
    end
+
else
 
+
table.insert(retAlt, tgEntry.link or tgEntry.name)
    local catMentions = ''
+
end
    if rgData.id == '1435' then
+
else -- so must be unlinked .name to add
    -- no s.g. categorization
+
if engvar == 'en-us' then
    else
+
-- Current implementations (2016): metER for metRE (1000-met, 1009-met)
        local addMentionsCat = args.addcat or 'yes'
+
table.insert(retAlt, tgEntry.en_US_name or tgEntry.name)
        if addMentionsCat ~= 'no' then
+
else
            if title:inNamespaces( 0, 14 ) then
+
table.insert(retAlt, tgEntry.name)
                catMentions = catMentionsGaugeSize( rgData.id )
+
end
            end
+
end
        end
+
if setNowrap then --close tag
    end
+
table.insert(retAlt, '</span>')
 
+
end
    -- Assemble the output.
+
return table.concat(retAlt, '')
    local ret = {}
 
    table.insert( ret, compose( args, rgData ) )
 
    local gaugeName = rgData.name
 
    local gaugeLink = rgData.link
 
    if args.allk == 'on' and gaugeLink then
 
        table.insert( ret, ' ' .. noWrap( gaugeLink ) )
 
    elseif args.al == 'on' and gaugeName then
 
        table.insert( ret, ' ' .. noWrap( gaugeName ) )
 
    end
 
    table.insert( ret, catMentions )
 
 
 
    return table.concat( ret )
 
 
end
 
end
 
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
-- checkData -- Public. Performs various checks on the /data subpage.
+
-- main -- The basic module
 
-----------------------------------------------------------------------------------
 
-----------------------------------------------------------------------------------
function p.checkData( frame )
+
function p.main(frame)
    local dataPage = frame and frame.args and frame.args[1] or dataPageName
+
-- In general: the tgEntry object (from TG/data) is passed to the functions, while arguments are processed here.
    local data = mw.loadData( dataPage )
+
local title = mw.title.getCurrentTitle()
    local exists, dupes, dupeSort, ret = {}, {}, {}, {}
+
local args = prepareArgs(frame)
 +
local tgEntry = p.getTrackGaugeEntry(args.searchAlias)
  
    -- Check for duplicate aliases.
+
-- Categorise & preview warning when no track gauge definition was found.
    for ti, t in ipairs( data ) do
+
if tgEntry == nil then
        for ai, alias in ipairs( t.aliases or {} ) do
+
local input = args[1] or ''
            if not exists[ alias ] then
+
local errorTail = ''
                exists[ alias ] = { ti, ai }
+
            else
+
if frame:preprocess("{{REVISIONID}}") == "" then  
                if not dupes[ alias ] then
+
errorTail = '<div class="hatnote" style="color:red"><strong>Warning:</strong> ' .. 'Track gauge ' .. (input .. ' ' or '') .. 'not in [[:Template:Track_gauge#List_of_defined_track_gauges|List of defined track gauges]] ([[Template talk:Track gauge|talk]]).' .. ' (This message is shown only in preview.)</div>'
                    dupes[ alias ] = { exists[ alias ] }
+
end
                end
 
                table.insert( dupes[ alias ], { ti, ai } )
 
            end
 
        end
 
    end
 
    for alias in pairs( dupes ) do
 
        table.insert( dupeSort, alias )
 
    end
 
    table.sort( dupeSort )
 
    for i1, alias in ipairs( dupeSort ) do
 
        local positions = {}
 
        for i2, aliasKeys in ipairs( dupes[ alias ] ) do
 
            local position = mw.ustring.format( 'gauge %d, alias %d ( gauge id: <code>%s</code> )', aliasKeys[ 1 ], aliasKeys[ 2 ], data[ aliasKeys[ 1 ] ].id or '' )
 
            table.insert( positions, position )
 
        end
 
        local aliasText = mw.ustring.format( 'Duplicate aliases "%s" detected at the following positions: %s.', alias, mw.text.listToText( positions, '; ' ) )
 
        table.insert( ret, aliasText )
 
    end
 
    -- Check for numerators without denominators.
 
    for ti, t in ipairs( data ) do
 
        local num = t.num
 
        local den = t.den
 
        if num and not den then
 
            table.insert( ret, mw.ustring.format( 'Numerator "%s" with no denominator detected at gauge %d ( id: <code>%s</code> ).', num, ti, t.id or '' ) )
 
        elseif den and not num then
 
            table.insert( ret, mw.ustring.format( 'Denominator "%s" with no numerator detected at gauge %d ( id: <code>%s</code> ).', den, ti, t.id or '' ) )
 
        end
 
    end
 
    -- Check for gauges with no imperial or no metric measurements.
 
    for ti, t in ipairs( data ) do
 
        if not ( t.ft or t['in'] or t.num or t.den ) then
 
            table.insert( ret, mw.ustring.format( 'No imperial measurements found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
 
        end
 
        if not ( t.m or t.mm ) then
 
            table.insert( ret, mw.ustring.format( 'No metric measurements found for gauge %d ( id: <code>%s</code> ).', ti, t.id or '' ) )
 
        end
 
    end
 
    -- Check for non-numeric measurements.
 
    local measurements = { 'ft', 'in', 'num', 'den', 'm', 'mm' }
 
    for ti, t in ipairs( data ) do
 
        for mi, measurement in ipairs( measurements ) do
 
            local measurementVal = t[ measurement ]
 
            if measurementVal and not tonumber( measurementVal ) 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 '' ) )
 
            end
 
        end
 
    end
 
    -- Check for gauges with no id.
 
    for ti, t in ipairs( data ) do
 
        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.
+
if title:inNamespaces(0, 14) then -- mainspace and category space
    for i, msg in ipairs( ret ) do
+
errorTail = errorTail .. "[[Category:Articles using template 'Track gauge' with unrecognized input]]"
        ret[ i ] = mw.ustring.format( '<span class="error">%s</span>', msg )
+
end
    end
+
return input .. errorTail
 +
end
  
    if #ret > 0 then
+
-- Check and set args & tgEntry props: disp, first, nowrap, first
        return mw.ustring.format( 'Found the following errors in %s:\n* %s', dataPageName, table.concat( ret, '\n* ' ) )
+
local disp = args.disp or ''
    else
+
local first = args.first or tgEntry.def1
        return mw.ustring.format( 'No errors found in %s.', dataPageName )
+
local unitlink = args.unitlink or ''
    end
+
local nowrap = args.nowrap or ''
end
+
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 = tgEntry.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(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
 +
else
 +
table.insert(ret,
 +
p.formatImp(tgEntry, 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 = '&nbsp;/ ' --spaces
 +
elseif disp == 'br' then
 +
joinText = '<br />('
 +
closeDisp = ')'
 +
elseif disp == '[' or disp == '[]' then
 +
joinText = ' ['
 +
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(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
 +
else
 +
table.insert(ret,
 +
p.formatImp(tgEntry, 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 == 'on' or args.allk == 'on' then
 +
local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags
 +
table.insert(ret, formatAltName(tgEntry, args.al == 'on', args.allk == 'on', disp, setNowrapAltname, args.engvar))
 +
end
 +
-- Closing nowrap tag
 +
if nowrap == 'all' then
 +
table.insert(ret, '</span>')
 +
end
 +
 +
-- Category mentionings (maintenance)
 +
if args.addcat or '' == 'no' then
 +
-- No categorization
 +
elseif title:inNamespaces(0) then
 +
-- switched off per [[Wikipedia:Categories_for_discussion/Log/2016_December_6#Category:Articles_that_mention_a_specific_track_gauge]]
 +
-- 2016-12-19
 +
-- table.insert(ret, p.catMentions(tgEntry))
 +
end
  
-----------------------------------------------------------------------------------
+
-- Now sting the table together
-- documentHeader
+
return table.concat(ret, '')
-----------------------------------------------------------------------------------
 
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
 +
--20161219: maintenance categorisation switched off per CfD
 +
--20170602: fix bug, show name when al=on
 +
--20180708: show preview warning when gauge is not in list
 +
--20190124: with disp=/ (slash) value separator: surround by spaces

Latest revision as of 17:25, 29 September 2020

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 {{Track gauge}} template.
-- Data is in Module:Track gauge/data
local p = {}
local gaugeDataAll = nil
local dataPageName = 'Module:Track gauge/data' -- set /data/sandbox here
-----------------------------------------------------------------------------------
-- 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 rawInput in [1] for error message
			if k == 1 then
			-- Unnamed argument, the alias to be searched
			-- Cleanup
				searchAlias = p.normaliseAliasInput(v)
			end
		end
	end
	args['searchAlias'] = searchAlias
	if rawDisp then args['rawDisp'] = rawDisp end
	return args
end
-----------------------------------------------------------------------------------
-- normaliseAliasInput
-----------------------------------------------------------------------------------
function p.normaliseAliasInput(aliasIn)
	local a
	a = mw.ustring.lower(mw.ustring.gsub(aliasIn, '[%s%,]', ''))
	a = mw.ustring.gsub(a, '&nbsp;', '')
	a = mw.ustring.gsub(a, 'gauge$', '')
	a = mw.ustring.gsub(a, "'", "ft")
	a = mw.ustring.gsub(a, '"', 'in')
	a = mw.ustring.gsub(a, '⁄', '/')
	a = mw.ustring.gsub(a, '&frasl;', '/')
	return a	
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
-----------------------------------------------------------------------------------
-- getTrackGaugeEntry -- Find entry data for a single gauge (alias)
-----------------------------------------------------------------------------------
function p.getTrackGaugeEntry(searchAlias)
	gaugeDataAll = mw.loadData(dataPageName)
	if searchAlias == '' then
		return nil
	end
	local tgEntry = nil
	for i, tgEntry in ipairs(gaugeDataAll) do
		for j, alias in ipairs(tgEntry.aliases) do
			if alias == searchAlias then
				return tgEntry
			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(tgEntry, sortlabel, doReturn)
	local ns = 'Category:'
	local cat

	if tgEntry == nil then
		-- Parent, the container cat
		cat = 'Articles that mention a specific track gauge'
	else
		cat = 'Articles that mention track gauge ' .. tgEntry.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(tgEntry, measurementToLink, setNowrap, addUnitlink)
	local ret = {}
	local ft = tgEntry.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 = tgEntry['in']
	local num = tgEntry.num
	local den = tgEntry.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 tgEntry.pagename ~= nil then
		return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
	else
		return gaugeSize
	end
end
-----------------------------------------------------------------------------------
-- formatMet -- Formats metric measurements into a single formatted text element. Public for autodocument
-----------------------------------------------------------------------------------
function p.formatMet(tgEntry, measurementToLink, setNowrap, addUnitlink)
	local m = tgEntry.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 = tgEntry.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 tgEntry.pagename ~= nil then
		return mw.ustring.format('[[%s|%s]]', tgEntry.pagename, gaugeSize)
	else
		return gaugeSize
	end
end
-----------------------------------------------------------------------------------
-- formatAltName
-----------------------------------------------------------------------------------
function formatAltName(tgEntry, addGaugeName, addGaugeNameLink, disp, setNowrap, engvar)
	-- Assumed: at least one to add is true.
	if tgEntry.name == nil then
		-- Not checked: link does exist alone
		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 addGaugeNameLink then
		if engvar == 'en-us' then
			-- Current implementations (2016): metER for metRE (1000-met, 1009-met)
			table.insert(retAlt, tgEntry.en_US_link or tgEntry.link or tgEntry.name)
		else
			table.insert(retAlt, tgEntry.link or tgEntry.name)
		end
	else -- so must be unlinked .name to add
			if engvar == 'en-us' then
			-- Current implementations (2016): metER for metRE (1000-met, 1009-met)
			table.insert(retAlt, tgEntry.en_US_name or tgEntry.name)
		else
			table.insert(retAlt, tgEntry.name)
		end
	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: the tgEntry object (from TG/data) is passed to the functions, while arguments are processed here.
	local title = mw.title.getCurrentTitle()
	local args = prepareArgs(frame)
	local tgEntry = p.getTrackGaugeEntry(args.searchAlias)

	-- Categorise & preview warning when no track gauge definition was found.
	if tgEntry == nil then
		local input = args[1] or ''
		local errorTail = ''
		
		if frame:preprocess("{{REVISIONID}}") == "" then 
				errorTail = '<div class="hatnote" style="color:red"><strong>Warning:</strong> ' .. 'Track gauge ' .. (input .. ' ' or '') .. 'not in [[:Template:Track_gauge#List_of_defined_track_gauges|List of defined track gauges]] ([[Template talk:Track gauge|talk]]).' .. ' (This message is shown only in preview.)</div>'
		end

		if title:inNamespaces(0, 14) then -- mainspace and category space
			errorTail = errorTail .. "[[Category:Articles using template 'Track gauge' with unrecognized input]]"
		end
		return input .. errorTail
	end

	-- Check and set args & tgEntry props: disp, first, nowrap, first
	local disp = args.disp or ''
	local first = args.first or tgEntry.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 = tgEntry.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(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
	else
		table.insert(ret,
			p.formatImp(tgEntry, 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 = '&nbsp;/ ' --spaces
		elseif disp == 'br' then
			joinText = '<br />('
			closeDisp = ')'
		elseif disp == '[' or disp == '[]' then
			joinText = ' ['
			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(tgEntry, measurementToLink, setNowrapElement, unitlink == 'on'))
		else
			table.insert(ret,
				p.formatImp(tgEntry, 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 == 'on' or args.allk == 'on' then
		local setNowrapAltname = (nowrap == '' or nowrap == 'on') -- Logic applied to prevent nested nowrap tags
		table.insert(ret, formatAltName(tgEntry, args.al == 'on', args.allk == 'on', disp, setNowrapAltname, args.engvar))
	end
	-- Closing nowrap tag
	if nowrap == 'all' then
		table.insert(ret, '</span>')
	end
	
	-- Category mentionings (maintenance)
	if args.addcat or '' == 'no' then
		-- No categorization
	elseif title:inNamespaces(0) then
		-- switched off per [[Wikipedia:Categories_for_discussion/Log/2016_December_6#Category:Articles_that_mention_a_specific_track_gauge]]
		-- 2016-12-19
		-- table.insert(ret, p.catMentions(tgEntry))
	end

	-- Now sting the table together
	return table.concat(ret, '')
end

return p
--20161219: maintenance categorisation switched off per CfD
--20170602: fix bug, show name when al=on
--20180708: show preview warning when gauge is not in list
--20190124: with disp=/ (slash) value separator: surround by spaces