มอดูล:Track gauge/autodocument

-- This module documents the track gauges
-- as defined in module:Track gauge/data.
-- General note: "id" is the size-id (in mm). With this id, definitions can vary (mm, ft/in, name)
-- Alias (the normalised input value) is the primary search term
local p = {}
local getArgs = require('Module:Arguments').getArgs
local modMath = require('Module:Math')
local modTrackGauge = require('Module:Track gauge') -- sandbox here
local dataPageName = 'Module:Track gauge/data' -- sandbox here
local gaugeDataAll = nil
local tableTools = require('Module:tableTools')
-- global counters (to keep between the id-row building calls)
local ttlSizeClassCount = {}
local ttlAliasCount = 0
local ttlEntries = 0
local ttlUnitCount = {}
local ttlAltNameCount = 0
local ttlAltName = {}
local ttlLinkCount = 0
local ttlContentCatsCount = 0
local ttlMentioningCatsCount = 0
local ttlMentioningPageCount = 0
local ttlListedRange = {}
-----------------------------------------------------------------------------------
-- prepareArgs -- Arguments coming from an #invoke or from a module
-----------------------------------------------------------------------------------
local function prepareArgs(frame)
	local origArgs = getArgs(frame)
	-- 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' = origArgs1 or 
	for k, v in pairs(origArgs) do
		if tonumber(k) == nil then
			-- Named argument
			if k == 'docsortlabel' then -- not in TG
				argsk = v
			else
				argsk = mw.ustring.lower(v)
			end
		else
			-- Unnamed argument, alias to be searched
			argsk = modTrackGauge.normaliseAliasInput(v)
			if k == 1 then
				args'searchAlias' = args1
			end
		end
	end
	return args
end
-----------------------------------------------------------------------------------
-- formatUnitPlaintext
-- Pattern '00016.5 mm' for table.sort and catsort.
-----------------------------------------------------------------------------------
local function formatUnitPlaintext(tgEntry, unit, fmtZeroPadding, toFracChar)
	-- Returns plaintext (ASCII) only. No css.
	if tgEntry == nil then
		return 
	end
	if (unit or tgEntry.def) == 'imp' then
		-- imperial
		local ft = 
		local inch = 
		local frac = 
		if tgEntry.ft then
			ft = tgEntry.ft .. ' ft'
		end
		if tgEntry.num then
			frac = ' ' .. tgEntry.num .. '/' .. tgEntry.den
			if toFracChar then
				-- as used in contentCat pagenames
				if frac == ' 1/8' then frac = '⅛'
				elseif frac == ' 1/4' then frac = '¼'
				elseif frac == ' 3/8' then frac = '⅜'
				elseif frac == ' 1/2' then frac = '½'
				elseif frac == ' 3/4' then frac = '¾'
				elseif frac == ' 7/8' then frac = '⅞'
				else
					frac = frac .. ' (error: fraction character missing in module:Track gauge)'
				end
			end
			if tgEntry'in' then
				frac = ' ' .. tgEntry'in' .. frac .. ' in'
			else
				frac = ' ' .. frac .. ' in'
			end
		else
			if tgEntry'in' then
				inch = ' ' .. tgEntry'in' .. ' in'
			end
		end
		return mw.text.trim(ft .. inch .. frac)
	else
		-- metric (mm)
		if fmtZeroPadding == nil or tonumber(fmtZeroPadding)  1435 then
		j = 5
	elseif size == 1435 then
		j = 4
	elseif size > 500 then
		j = 3
	elseif size >= 100 then
		j = 2
	elseif size > 0 then
		j = 1
	else
		j = 6
	end
	ttlSizeClassCountj2 = ttlSizeClassCountj2 +1
	ttlSizeClassCountj4 = ttlSizeClassCountj4 + (countMentionings or 0)
	return  .. ttlSizeClassCountj1 ..  --(20190920: linter closing span added)
end
-----------------------------------------------------------------------------------
-- anchor -- Anchor text *here* is: ; anchor *there* should be: #1000 mm.
-----------------------------------------------------------------------------------
local function anchor(tgEntry, unit, herethere)
	if tgEntry == nil then
		return 
	end
	unit = unit or tgEntry.def1
	local anch = formatUnitPlaintext(tgEntry, unit, 0)
	if herethere == 'there' then -- Untested, April 2014
		anch = '#' .. anch
	else
		anch = mw.html.create():tag('span'):attr('id', anch)
	end
	return tostring(anch)
end
-----------------------------------------------------------------------------------
-- noWrap -- Add span tags to prevent a string from wrapping.
-----------------------------------------------------------------------------------
local function noWrap(s)
	return mw.ustring.format('%s', s)
end
-----------------------------------------------------------------------------------
-- frac -- A slimmed-down version of the  template (a single nowrap to be added with the unit)
-----------------------------------------------------------------------------------
local function frac(whole, num, den)
	return mw.ustring.format(
		'%s%s%s⁄%s',
		whole or , whole and ' ' or , num, den
		)
end
-----------------------------------------------------------------------------------
-- debugReturnArgs
-----------------------------------------------------------------------------------
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
-----------------------------------------------------------------------------------
-- checkData -- Public. Performs various checks on the /data subpage.
-- not maintained since ca. 2015
-----------------------------------------------------------------------------------
function p.checkData(frame)
	--To be allowed: entry.link empty; then use entry.name.
	local dataPage = frame and frame.args and frame.args1 or dataPageName
	local data = mw.loadData(dataPage)
	local exists, dupes, dupeSort, ret = {}, {}, {}, {}
	-- Check for duplicate aliases.
	for ti, t in ipairs(data) do
		for ai, alias in ipairs(t.aliases or {}) do
			if not existsalias then
				existsalias = { ti, ai }
			else
				if not dupesalias then
					dupesalias = { existsalias }
				end
				table.insert(dupesalias, { 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(dupesalias) do
			local position = mw.ustring.format('gauge %d, alias %d (gauge id: %s)', aliasKeys1, aliasKeys2, dataaliasKeys1.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: %s).', 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: %s).', 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: %s).', 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: %s).', 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 = tmeasurement
			if measurementVal and not tonumber(measurementVal) then
				table.insert(ret, mw.ustring.format('Non-numeric %s measurement ("%s") found for gauge %d (id: %s).', 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('%s', alias))
			end
			aliases = mw.ustring.format(' (aliases: %s)', mw.text.listToText(aliases))
			table.insert(ret, mw.ustring.format('No id found for track 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: %s).', 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: %s).', ti, t.id or ))
			end
		end
	end
	-- Check for named gauges with no links and gauges with links but no names.
	-- 20140520: no link? could be acceptable. Code falls back to the unlinked name (in test now).
	if false then -- skipped 2014-05-25
	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: %s).', 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: %s).', t.link, ti, t.id or ))
		end
	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: %s).', 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: %s).', 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 %s field of gauge %d ("%s", gauge id: %s).', tkey, ti, tval, t.id or ))
				end
			elseif mw.ustring.find(tval, '%s') then
				table.insert(ret, mw.ustring.format('Unwanted whitespace detected in %s field of gauge %d ("%s", gauge id: %s).', 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 id=%s mm ambiguous with gauge id=%s mm (%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
		reti = mw.ustring.format('%s', msg)
	end
	if #ret > 0 then
		return mw.ustring.format('Found the following errors in %s:\n* %s', dataPage, table.concat(ret, '\n* '))
	else
		return mw.ustring.format('No errors found in %s.', dataPage)
	end
end
-----------------------------------------------------------------------------------
-- catContent -- content category for the gauge
-----------------------------------------------------------------------------------
function p.catContent(frame)
	-- catContent (content category for this alias)
	-- can be hardcoded in the data, or build by size (pattern)
	local args = prepareArgs(frame)
	local tgEntry = modTrackGauge.getTrackGaugeEntry(args.searchAlias)
	if tgEntry == nil then
		return args'displaynotfound' or 'No gauge entry found for ' .. (args1 or '""')
	end
	local catTitle
	local label
	local catC
	local docsortlabel = 
	if args.docsortlabel ~= nil then
		docsortlabel = '|' .. args.docsortlabel
	end
	if tgEntry.contentcat ==  then
		catC = 
	elseif tgEntry.contentcat ~= nil then
		catC = ':Category:' .. tgEntry.contentcat .. docsortlabel .. 
	else -- no name given, try default name:
		local catCsuffix = ' gauge railways'
		if tgEntry.def1 == 'met' then
			label = formatUnitPlaintext(tgEntry, 'met')
			catTitle = mw.title.makeTitle(14, label .. catCsuffix)
			if catTitle.exists then
				catC = ':' .. catTitle.fullText .. docsortlabel .. 
			end
		elseif tgEntry.def1 == 'imp' then
			label = formatUnitPlaintext(tgEntry, 'imp', nil, true)
			catTitle = mw.title.makeTitle(14, label .. catCsuffix)
			if catTitle.exists then
				catC = ':' .. catTitle.fullText .. docsortlabel .. 
			end
		end
	end
	return catC
end
-----------------------------------------------------------------------------------
-- catMentions -- maintenance only
-----------------------------------------------------------------------------------
function p.catMentions(frame)
	local args = prepareArgs(frame)
	local tgEntry = modTrackGauge.getTrackGaugeEntry(args.searchAlias)
	if tgEntry == nil then
		return args'displaynotfound' or 'No gauge entry found for ' .. (args1 or '""')
	end
	local catM = modTrackGauge.catMentions(tgEntry, args.docsortlabel, 'show')
	return catM
end
-----------------------------------------------------------------------------------
-- fromInputToId -- Used cleaned Alias as searchkey
-----------------------------------------------------------------------------------
local function fromInputToId(searchAlias)
	gaugeDataAll = mw.loadData(dataPageName)
	for i, tgEntry in ipairs(gaugeDataAll) do
		for j, alias in ipairs(tgEntry.aliases) do
			if alias == searchAlias then
				return tgEntry.id
			end
		end
	end
	-- Next search: by id (autodocument only, not in main RG)
	if tonumber(searchAlias) ~= nil then
		for i, tgEntry in ipairs(gaugeDataAll) do
			if tgEntry.id == searchAlias then
				return tgEntry.id
			end
		end
	end
end
-----------------------------------------------------------------------------------
-- documentInchCount -- Number of inches in decimals.
-----------------------------------------------------------------------------------
local function documentInchCount(tgEntry)
	local inches = 0
	if tgEntry'num' ~= nil then
		inches = modMath._round(tonumber((tgEntry'num' or 0) / (tgEntry'den' or 1)), 4)
	end
	inches = tostring((tonumber(tgEntry'ft' or 0) * 12)
		+ tonumber(tgEntry'in' or 0) + inches)
	return inches
end
-----------------------------------------------------------------------------------
-- documentInchToMm -- Not used lately
-----------------------------------------------------------------------------------
local function documentInchToMm(inchCount)
	return tonumber(inchCount or 0) * 25.4
end
-----------------------------------------------------------------------------------
-- documentGaugeSizeFromTitle -- Currently finds "1620 mm" when at end of title,
-- then returns "1620". Blank when not found.
-----------------------------------------------------------------------------------
function p.documentGaugeSizeFromTitle()
	local title = mw.title.getCurrentTitle()
	return string.match(title.text, '%s(%d+%.?%d*)%smm$') or 
end
-----------------------------------------------------------------------------------
-- documentBuildTgList -- The table of id's to fill the table
-----------------------------------------------------------------------------------
function documentBuildTgList(args)
	-- Build series from the list. idFrom and idTo are numerical
	local tgList = {}
	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(args.docfrom, 'mm', ))
		idTo = math.huge
	end
	if args.docto ~= nil then
		idTo = tonumber(fromInputToId(args.docto)
			or mw.ustring.gsub(args.docto, 'mm', ))
	end
	if idTo > 0 then -- Some subset is requested from the whole data set
		if idFrom > idTo then
			local dummy = idFrom
			idFrom = idTo
			idTo = dummy
		end
		for i, tgEntry in ipairs(gaugeDataAll) do
			if (tonumber(tgEntry.id) >= idFrom) and (tonumber(tgEntry.id)  1 then
			ttlListedRange1 = tgList1 .. ' mm – ' .. tgList#tgList .. ' mm '
		end
	end
	-- Individual entries can be mentioned in args (all unnamed = numbered params)
	-- Need a straight table.to keep sequence right
	local id
	local argsAliasesIn = tableTools.compressSparseArray(args)
	for i, argsAlias in ipairs(argsAliasesIn) do
		id = fromInputToId(argsAlias)
		if id ~= nil then
			table.insert(tgList, i, tonumber(id))
			table.insert(ttlListedRange, i, id .. ' mm; ')
		end
	end
	ttlListedRange = tableTools.compressSparseArray(ttlListedRange)
	ttlListedRange = tableTools.removeDuplicates(ttlListedRange)
	tgList = tableTools.compressSparseArray(tgList)
	tgList = tableTools.removeDuplicates(tgList)
	return tgList
end
-----------------------------------------------------------------------------------
-- documentPostListStats -- build footer table, after list only
-----------------------------------------------------------------------------------
local function documentPostListStats(countTgList)
	-- Report data counters
	-- Data
	local retFoot = {}
	table.insert(retFoot, '\n*Sources')
	table.insert(retFoot, ':Data pages: :' .. dataPageName .. )
	table.insert(retFoot, '*Data')
	table.insert(retFoot, ':Listed: ' .. table.concat(ttlListedRange, ) .. ' (' .. countTgList .. ' rows)')
	table.insert(retFoot, ":Entries (defined gauges, per unit): " .. ttlEntries)
	table.insert(retFoot, ":Gauges (defined gauges, per size): " .. countTgList)
	for i, stat in ipairs (ttlUnitCount) do
		table.insert(retFoot, ':' .. stat2 .. ': ' .. stat1)
	end
	table.insert(retFoot, ':Aliases (input options): ' .. ttlAliasCount)
	table.insert(retFoot, ':Named definitions (as output link; ' .. ttlAltNameCount .. '): ' .. table.concat(ttlAltName, '; '))
	table.insert(retFoot, ':Entries with an article link: ' .. ttlLinkCount)
	-- TODO table.insert(retFoot, '*Named gauges (named input)') -- todo
	-- Categories (content, maintenance)
	table.insert(retFoot, '*Categories')
	table.insert(retFoot, ':Content categories: ' .. ttlContentCatsCount)
	table.insert(retFoot, ':"Article mentions track gauge" categories: ' .. ttlMentioningCatsCount)
	table.insert(retFoot, ':Articles listed in "mentions" categories: ' .. ttlMentioningPageCount .. ' (not unique)')
	-- Size classes (narrow, broad, ..)
	table.insert(retFoot, '*Size classes')
	for i, stat in ipairs (ttlSizeClassCount) do
		if stat2 ~= 0 then
			table.insert(retFoot, ':' .. stat2 .. ' ' .. stat3 .. ' (' .. stat4 .. ' mentionings)')
		end
	end
	local anchor = tostring(mw.html.create():tag('span'):attr('id', 'Statistics'))
	-- help:using colors. Hue=190 (blue)
	local statTable = anchor .. '\n'
	
	return statTable
end
-----------------------------------------------------------------------------------
-- documentHeader
-----------------------------------------------------------------------------------
local function documentHeader(numberOfEntries, docTitle, docState)
	local docBgHeader = '#cef2e0' -- Green. See template:documentation
	-- Header row 1 (title)
	local pagetitle = mw.title.getCurrentTitle()
	urlPurgePage = 'https://en.wikipedia.org/w/index.php?title=' .. pagetitle.nsText .. ':' .. pagetitle:partialUrl()  .. '&action=purge'
	urlPurgePage =  .. urlPurgePage .. ' (purge)'
	
	if docTitle ==  then
		docTitle = 'Track gauges' -- (' .. dataPageName .. ')' -- optional, sandbox here
	end
	docTitle = docTitle .. ' ' .. urlPurgePage
	if docState ==  then
		docState = 'uncollapsed'
	end
	
	-- Header row 2 (sort buttons, blank cells)
	local sortColHeaders = 
	local sortClass = 
	if (numberOfEntries or 0) > 1 then
		sortClass = 'sortable'
		local sortCell = '! style="background:' .. docBgHeader .. ';"'
		-- todo: 10 cols with bg color
		sortColHeaders = '\n
			.. '\n!   








'
	end
	
	-- Header row 3 (column headers)
	local catMparent = modTrackGauge.catMentions(nil, 'Mentionings', 'show')
	
	--10 columns:
	local tableStyle = 'style="text-align:right; width:100%; font-size:85%;" '
	local retHdr = {}
		table.insert(retHdr, '\n'}
end
-----------------------------------------------------------------------------------
-- documentFromIdToEntrySet -- from fromIdToEntrySet
-- From one id, make the set with all one-two-three-more entries (met, inp, variants)
-----------------------------------------------------------------------------------
local function documentFromIdToEntrySet(id, searchedAlias)
	local docBgColor = '#e6fff2' -- Green. See header bg color
	local rowSplit = 
	
	-- From the size-id, build the set of existing entries (met, imp, and variants)
	local entry = {}
	local defType = 0
	-- data
	for i, tgEntry in ipairs(gaugeDataAll) do
		if id == tgEntry.id then
			if tgEntry.def1 == 'met' and entry1 == nil then
				entry1 = tgEntry
				defType = defType + 1
			elseif tgEntry.def1 == 'imp' and entry2 == nil then
				entry2 = tgEntry
				defType = defType + 2
			else
				entry3 + tableTools.size(entry) = tgEntry
			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)
	-- (to build into a single row, maybe with split cells)
	
	--Build cell elements, then string row together.
	local inchCount = documentInchCount(entry1)
	local datasortvalue = documentdatasortvalue(entry1, 'met', 5)
	local aliasList = {}
	local tempEntryAltName = {}
	local entryAltName = {}
	local hasAltName = false
	for i, e in ipairs(entry) do
		local alis = {}
		for j, v in ipairs(e.aliases) do
			if tonumber(v) == nil then -- (plain numbers are not shown)
				table.insert(alis, tostring(v))
			end
		end
		for j, v in ipairs(alis) do
			if string.match(v, '^%d') == nil then -- textual so to italic.
				alisj = tostring(mw.html.create():tag('span'):wikitext(v):css('font-style', 'italic'))
			end
		end
		table.insert (aliasList, table.concat(alis, '; '))
		ttlAliasCount = ttlAliasCount + #alis
		-- process Alt name links
		if e.name or  ~=  then
			tempEntryAltNamei = tostring(mw.html.create():tag('span'):wikitext(e.link):css('font-weight', 'bold'))
			table.insert(ttlAltName, e.id .. ': ' .. e.link)
			ttlAltNameCount = ttlAltNameCount + 1
			hasAltName = true
		end
	end
	if hasAltName then
		local text
		for i, v in ipairs(entry) do
			table.insert(entryAltName, i, tempEntryAltNamei or ' ')
		end
	end
	local def = {} -- Definition unit 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, 'imp')
			ttlUnitCount21 = ttlUnitCount21 + 1
		elseif v.def1 == 'met' then
			table.insert(defText, 'met')
			ttlUnitCount11 = ttlUnitCount11 + 1
		end
	end
	if #entry >= 2 then
		if #entry == 2 and entry1.def1 ~= entry2.def1 then -- Regular pair: def in met and in imp
			ttlUnitCount31 = ttlUnitCount31 + 1
		else -- More than 2, or a double unit definition
			ttlUnitCount41 = ttlUnitCount41 .. ' ' .. id .. ' mm (' .. #entry ..');'
		end
	end
	-- mm; ft in -- Measurement (number & unit; met and imp; anchor to here)
	local measure = {}
	local unitanchor = { ,  }
	measure1 = modTrackGauge.formatMet(entry1)
	measure2 = modTrackGauge.formatImp(entry1) -- both met and imp from entry1
	if modMath._mod(defType, 2) == 1 then
		measure1 = tostring(mw.html.create():tag('span'):wikitext(measure1):css('font-weight', 'bold'))
		unitanchor1 = anchor(entry1, 'met')
	end
	if defType >= 2 then
		measure2 = tostring(mw.html.create():tag('span'):wikitext(measure2):css('font-weight', 'bold'))
		unitanchor2 = anchor(entry1, 'imp')
	end
	-- Linked article
	local linkArticle = {}
	for i, e in ipairs(entry) do
		table.insert(linkArticle, e.pagename)
	end
	ttlLinkCount = ttlLinkCount + #linkArticle
	local eq
	if #linkArticle >= 2 then
		eq = true
		for i, v in ipairs(linkArticle) do
			if v ~= linkArticle1 then
				eq = false
				break
			end
		end
		if eq == true then
			for i, v in ipairs(linkArticle) do
				if i > 1 then
					linkArticlei = nil
				end
			end
		end
	end
	for i, lp in ipairs(linkArticle) do
		local fmtLp = 
		fmtLp =  .. lp .. 
		linkArticlei = tostring(mw.html.create():tag('span'):css('text-align', 'left'):wikitext(fmtLp))
	end
	-- catContent (content category for this alias). note: function p.catContent is a reduced code of this.
	-- can be hardcoded in the data, or build by size (pattern)
	local catContent = {}
	local catTitle
	local label
	local skipCheck = false
	for i, e in ipairs(entry) do
		if e.contentcat ==  then
			-- no cat; option to prevent expensive calls
			skipCheck = true
		elseif e.contentcat ~= nil then
			label = string.match(e.contentcat, '(%S*)') or 'nomatch'
			table.insert(catContent,
				'cat:' .. label .. ' ...')
		end
	end
	if #catContent >= 2 then
		eq = true
		for i, v in ipairs(catContent) do
			if v ~= catContent1 then
				eq = false
				break
			end
		end
		if eq == true then
			for i, v in ipairs(catContent) do
				if i > 1 then
					catContenti = nil
				end
			end
		end
	end
	if #catContent == 0 and not skipCheck then
		local catCsuffix = ' gauge railways'
		if modMath._mod(defType, 2) == 1 then
			label = formatUnitPlaintext(entry1, 'met')
			catTitle = mw.title.makeTitle(14, label .. catCsuffix)
			if catTitle.exists then
				table.insert(catContent,
					'cat:' .. noWrap(label) .. )
			end
		end
		if defType >= 2 then
			label = formatUnitPlaintext(entry1, 'imp', nil, true)
			catTitle = mw.title.makeTitle(14, label .. catCsuffix)
			if catTitle.exists then
				table.insert(catContent, 'cat:' .. noWrap(label) .. )
			end
		end
	end
	ttlContentCatsCount = ttlContentCatsCount + #catContent
	-- Mentions category
	local catMentions = modTrackGauge.catMentions(entry1, "cat:mnt", 'show')
	local catCount = mw.site.stats.pagesInCategory(
	modTrackGauge.catMentions(entry1, nil, 'pagename'), pages)
	ttlMentioningCatsCount = ttlMentioningCatsCount + 1 -- Exists
	ttlMentioningPageCount = ttlMentioningPageCount + catCount
	-- class: Counter SizeClass (narrow, broad, ...)
	local rgSizeClass = documentGaugeClass(entry1, catCount)
	ttlEntries = ttlEntries + #entry
	sortCount  = mw.text.truncate('00000' .. tostring(catCount), -5, )
	sortCount = 
	catCount = sortCount .. catCount .. ' P' ..  --(20190920: linter closing span added)
	-- Compose the size-id row with all cell values (10 columns)
	local row = {}
		table.insert(row, datasortvalue .. unitanchor1 .. measure1)
		table.insert(row, datasortvalue .. unitanchor2 .. measure2)
		table.insert(row, table.concat(entryAltName, rowSplit))
		table.insert(row, datasortvalue .. inchCount)
		table.insert(row, table.concat(defText, rowSplit))
		table.insert(row, table.concat(aliasList, rowSplit))
		table.insert(row, rgSizeClass)
		table.insert(row, table.concat(linkArticle, rowSplit))
		table.insert(row, table.concat(catContent, rowSplit))
		table.insert(row, catCount .. ' ' .. catMentions)
	return '\n
		.. '\n|'
		.. table.concat(row, ' 
')
end
-----------------------------------------------------------------------------------
-- documentGauge -- Selfdocument gauge data (one, multiple, range, all)
-----------------------------------------------------------------------------------
function p.documentGauge(frame)
	local args = prepareArgs(frame)
	gaugeDataAll = mw.loadData(dataPageName)
		-- Init glolbal counters by table:
		ttlUnitCount =
			{
			1 = {0, 'Entries defined metric'},
			2 = {0, 'Entries defined imperial'},
			3 = {0, 'Gauge sizes defined both metric and imperial'},
			4 = {, 'Gauge sizes with multiple entries in one unit'}
			}
		ttlSizeClassCount =
			{
			1 = {'scaled', 0, 'scaled or model gauges', 0},
			2 = {'min', 0, 'minimum gauges', 0},
			3 = {'narrow', 0, 'narrow gauges', 0},
			4 = {'s.g.', 0, 'standard gauge', 0},
			5 = {'broad', 0, 'broad gauges', 0},
			6 = {'unk', 0, 'unknown', 0}
			}
	local tgList = documentBuildTgList(args)
	-- Now loop through the prepared tgListid and add rows to result 	le
	-- One row contains all available entries for the id (met, imp, a third variant)
	local rowTGid = {}
	for i, numId in ipairs(tgList) do
		table.insert(rowTGid, documentFromIdToEntrySet(tostring(numId)))
	end
	-- Return args
	local retArgs = 
	if args.docreturnargs == 'on' then
		retArgs = '\n' .. p.debugReturnArgs(frame)
	end
	-- Build statistics footer
	local retStats = 
	if args.docstats == 'on' then
		retStats = documentPostListStats(#tgList)
	end
	-- Build up
	return documentHeader(#tgList, args.doctitle or , args.docstate or )
		.. table.concat(rowTGid, )
		.. table.concat(documentFooter(), )
		.. retStats
		.. retArgs
end
--------------------------------------------------------
-- doc
--------------------------------------------------------
function p.docFracAliases(frame)
	local args = prepareArgs(frame)
	gaugeDataAll = mw.loadData(dataPageName)
	local tgList = documentBuildTgList(args)
	local ttlHitCount =0
	local rowIMP = {}
	local fracAlias =
	for i, id in ipairs(tgList) do
		for j, tgEntry in pairs(gaugeDataAll) do
			if nil and tostring(id) == '53.975' and tostring(id) == tgEntry.id then
				fracAlias = anchor(tgEntry, 'imp', 'there')
				fracAlias = mw.ustring.lower(mw.ustring.gsub(fracAlias, '%s%,%#', ))
					table.insert(rowIMP, fracAlias .. '
' .. i .. '
' .. id ..
					'
' .. modTrackGauge.formatImp(tgEntry) .. ' 
plus ' .. tgEntry.num)
			end
			if tostring(id) == tgEntry.id and tgEntry.def1 == 'imp' and tgEntry.num ~= nil then
				if tgEntry.ft ~= nil then
					ttlHitCount = ttlHitCount + 1
					fracAlias = anchor(tgEntry, 'imp', 'there')
					fracAlias = mw.ustring.lower(mw.ustring.gsub(fracAlias, '%s%,%#', ))
					fracAlias = mw.ustring.gsub(fracAlias, '⁄', '/')
					table.insert(rowIMP, fracAlias .. '
' .. i .. '
' .. id ..
						'
' .. modTrackGauge.formatImp(tgEntry))
				
					fracAlias = tostring((tonumber((tgEntry.ft) or 0) * 12) + tonumber(tgEntry"in" or 0)) .. tgEntry.num .. '/' .. tgEntry.den .. 'in'
					table.insert(rowIMP, fracAlias .. '
' .. i .. '
' .. id ..
						'
' .. modTrackGauge.formatImp(tgEntry))
				end
			end
		end
	end
--	return '\n|' .. ttlHitCount .. ' hits. ' .. #rowIMP
	return '\n\n
		
end
	
return p