Module:BSicon

local p = {}

-- local test = {}

local getArgs = require('Module:Arguments').getArgs

local function makeInvokeFunction(funcName) -- makes a function that can be returned from #invoke, using -- Module:Arguments. return function(frame) local args = getArgs(frame, {parentOnly = true}) return p[funcName](args) end end

p.category = makeInvokeFunction('_category')

-- permutations of category titles that do not have the '/set ..' titleparts -- in lexicographical order ('u','g','f','mixed' different, but also specific) -- are filled with a category redirect if created with content function sort_set_titleparts(cat_title, oc_cat) local _ocols = { ins = table.insert, srt = table.sort, rm = table.remove } cat_title:gsub("/set ([^/]+)", function (c) _ocols:ins(c) end) _ocols:srt(function (c1, c2) return c2=="mixed"			or c2=="f" and not ("f:mixed"):find(c1,1,true)			or ("g:f"):find(c2,1,true) and not ("g:f:mixed"):find(c1,1,true)			or ("u:g:f"):find(c2,1,true) and not ("u:g:f:mixed"):find(c1,1,true)			or not ("u:g:f:mixed"):find(c2,1,true) and				not ("u:g:f:mixed"):find(c1,1,true) and c1 < c2		end	) local um = not oc_cat and #_ocols == 2 and _ocols[1]=="u" and _ocols[2]=="mixed" return cat_title:gsub("(/set )[^/]+", function (s) local c = _ocols:rm(1)		return um and #_ocols > 0 and '' or s..c	end) end function redirect(title, oc_cat) local t = sort_set_titleparts(title, oc_cat) local n, msg1 t, n = mw.ustring.gsub(t, '/railway(.*/level crossing)', '/road–rail%1') if n > 0 then msg1 = function return '\n' .. mw.getCurrentFrame :expandTemplate{ title="notice", args = { "BSicons depicting non-road, same-leveled railway crossings usually categorize into simply '../crossing', " .. "while the term 'level crossing' binds with the more specific, but also more frequent road–rail level crossings " .. "for the purpose of categorizing within Category:BSicon/road–rail subtree." }			}		end end -- see 'level crossing' entry in roots table below, no need to have the term twice in a title t = t:find('/level crossing',1,true) and mw.ustring.gsub(t..'/', '/crossing/', '/'):gsub('/$', '') or t	-- sometimes 'one quarter' cats are created as 'one quarters', catch and redirect.. t = mw.ustring.gsub(t, '(/one quarter)s', '%1') if title ~= t then return mw.getCurrentFrame :expandTemplate{ title="category redirect", args = { t } } .. (msg1 and msg1 or '') end end

-- used to categorize BSicon graphics, it populates the category tree and -- fills a category header with some basic information, categories by nature -- can be monotone - to display icons from different cats on the same page -- either BSicon/Catalogue or other _gallery_ pages can be used function p._category(args) local title, categories, color, result, tmp = mw.title.getCurrentTitle, , {}, '' if title.namespace ~= 14 then return error("This template should only be used on category pages") end title = title.text local oc_cat = title:match("/other[^/]*colors$") local redir = redirect(title, oc_cat) if redir then return redir end title = mw.ustring.gsub(title, '%+', '#') title = mw.ustring.gsub(title, '%-', '_') local title2 = mw.clone(title) local rgb, contrast = require('Module:Routemap')._RGBbyCode, require('Module:Color contrast')._ratio local function rgb_cell(x) x = tostring(x) or 'BE2D2C' return ' #'..x..' ' end local function hex(x, ex) x = rgb{mw.ustring.gsub((ex and 'ex_' or )..(tostring(x) or ), '^ex_([ufg]?)$', '%1ex', 1)} return rgb_cell(x) end local roots = { BHF = 'stations and stops/', HST = 'stations and stops/', DST = 'stations and stops/', BST = 'stations and stops/', INT = 'stations and stops/', ACC = 'stations and stops/', INTACC = 'stations and stops/', HSTACC = 'stations and stops/', suburban = 'stations and stops/', SBHF = 'stations and stops/', SHST = 'stations and stops/', ['S#BHF'] = 'stations and stops/', ['S#HST'] = 'stations and stops/', HSTACC = 'stations and stops/', RD = 'generic road/', RP1 = 'generic road/', RP2 = 'generic road/', RP4 = 'generic road/', fork = 'junction/', wye = 'junction/', split = 'junction/', formations = 'legende/', ['level crossing'] = 'crossing/', ['straight#curve'] = 'curve/', ['straight#shift'] = 'shift/', ['straight#junction'] = 'junction/', ['straight#corner'] = 'corner/', }	local compounds = { ['road–rail'] = 'railway/road', INTACC = 'INT/ACC', HSTACC = 'HST/ACC', ['S#BHF'] = 'suburban/BHF', ['S#HST'] = 'suburban/HST', SBHF = 'suburban/BHF', SHST = 'suburban/HST', ['curve#corner'] = 'curve/corner', ['crossing#corner'] = 'crossing/corner', ['crossing#junction'] = 'crossing/junction', ['junction#corner'] = 'junction/corner', }	local used_roots = {} local matches = {} local used_compounds = {} title = title..'/' for k, v in pairs(roots) do		if mw.ustring.match(title, '/'..k..'/') then title = mw.ustring.gsub(title, k, v..k)			table.insert(used_roots, k)			used_roots[k] = true end end title = mw.ustring.gsub(title, '/$', '') for k, v in pairs(compounds) do		if mw.ustring.match(title, k) then title = mw.ustring.gsub(title, k, v)			table.insert(matches, k)			table.insert(used_compounds, v)		end end local legende_color if mw.ustring.match(title, 'water') then result = result..'\n water'..rgb_cell('007CC3') end if mw.ustring.match(title, 'tunnel to') or mw.ustring.match(title, 'portal') or mw.ustring.match(title, 'elevated') or mw.ustring.match(title, 'bridge') or mw.ustring.match(title, 'crossing') or mw.ustring.match(title, '/tower') or mw.ustring.match(title, 'cutting') or mw.ustring.match(title, 'embankment') then result = result..'\n structure'..rgb_cell('80A080') legende_color = true end if mw.ustring.match(title, 'line endings') then result = result..'\n line ending (open)'..rgb_cell('000')..'  line ending (closed)'..rgb_cell('AAA') legende_color = true end if mw.ustring.match(title, 'border') then result = result..'\n border (active)'..rgb_cell('000')..'  border (inactive)'..rgb_cell('AAA') legende_color = true end if mw.ustring.match(title, 'platform') then result = result..'\n platform (open)'..rgb_cell('888')..'  platform (closed)'..rgb_cell('CCC') legende_color = true end if mw.ustring.match(title, 'mask') then result = result..'\n mask'..rgb_cell('F9F9F9') legende_color = true end if mw.ustring.match(title, 'INT') then result = result..'\n INT (open)'..rgb_cell('000')..'  INT (closed)'..rgb_cell('AAA') legende_color = true end if mw.ustring.match(title, 'ACC') then result = result..'\n ACC (open)'..rgb_cell('034EA2')..'  ACC (closed)'..rgb_cell('6592C5') legende_color = true end if mw.ustring.match(title, 'CPIC') then result = result..'\n cross-platform interchange'..rgb_cell('000')..' '..rgb_cell('B3B3B3') end if mw.ustring.match(title, 'S#?BHF') or mw.ustring.match(title, 'S#?HST') or mw.ustring.match(title, 'suburban') then result = result..'\n S-Bahn (open)'..rgb_cell('006E34')..'  S-Bahn (closed)'..rgb_cell('5ABF89') legende_color = true end if mw.ustring.match(title, 'DST') or mw.ustring.match(title, 'BST') or mw.ustring.match(title, 'ACC') or mw.ustring.match(title, 'INT') or mw.ustring.match(title, 'S#?BHF') or mw.ustring.match(title, 'S#?HST') or mw.ustring.match(title, 'suburban') then result = result..'\n fill'..rgb_cell('FFF') end legende_color = legende_color and mw.ustring.match(title, 'legende') local r = { ins = table.insert, rm = table.remove } r:ins(mw.getCurrentFrame:expandTemplate{ title = 'BS-set' }) if oc_cat then r:ins("\n") r:ins(mw.getCurrentFrame:expandTemplate{ title = 'Collapse', args = {				"\n" .. mw.getCurrentFrame:expandTemplate{ title = 'BS-colorlist', args = {} },				title = 'BSicon color list '			}		}) r:ins("\n \n") end r:ins("These BSicons are to be used with route diagram templates. ") r:ins("For an overview, see Category:BSicon.\n") r:ins(' ")	end	if oc_cat then		r:ins("\nThis category is on categories holding icons ")		r:ins(title[#title]:find('mixed',1,true) -- if '/other mixed colors' ..			and 'combining the set color(s) given above with an additional one'			or 'replacing the set color given above with one'		)		r:ins(" from BSicon color list.")	end	local set_subkey = '0'	table.remove(title, 1)	for k, v in ipairs(title) do		if not ((mw.ustring.match((title[2] or ), '^set ') or mw.ustring.match((title[3] or ), '^set ')) and title[k] == 'railway')		and not ((mw.ustring.match((title[2] or ), 'R([A-Z0-9]+)$') or mw.ustring.match((title[3] or ), 'R([A-Z0-9]+)$') or mw.ustring.match((title[4] or ), 'R([A-Z0-9]+)$') or mw.ustring.match((title[5] or ), 'R([A-Z0-9]+)$') or title[2] == 'generic road' or title[3] == 'generic road' or title[4] == 'generic road') and title[k] == 'road')		and not ((title_string or ''):find('/level crossing',1,true) and title[k] == 'road' and title[k-1] and title[k-1] == 'railway') and (#title 1 or (title[2] == 'road' and k == 1) or (title[1] ~= 'railway' and title[1] ~= 'road' and title[1] ~= 'canal')) and not (mw.ustring.match((title[k+1] or ), ' quarters?$') and (not (title_string or ):find('/straight#',1,true))) and not (title[k] == 'stations and stops' and (title[k+1] == 'interchange' or title[k+1] == 'express')) and not (title[k] == 'interchange' and (title[k+1] == 'CPIC')) and not (title[k] == 'uw' and title[k+1] == 'double') and not (title[k] == 'parallel lines' and ((title_string or ):find('/straight#shift',1,true) or (title_string or ):find('/double/',1,true))) and not (title[k] == 'tunnel' and (title[k+1] == 'portal' or title[k+2] == 'portal')) and not (oc_cat and title[#title] ~= v) then tmp = mw.clone(title) tmp = 'BSicon/'..table.concat(tmp, '/') for _, x in ipairs(used_compounds) do				if mw.ustring.match(x, v) then x = mw.ustring.gsub(x, v, '') x = mw.ustring.gsub(x, '/', '') local tmp_root = roots[x] if tmp_root then tmp = mw.ustring.gsub(tmp, tmp_root, '') end end end tmp = tmp..'/' tmp = mw.ustring.gsub(tmp, '/'..v..'/', '/') tmp = mw.ustring.gsub(tmp, '/+', '/') for k, v in ipairs(matches) do				tmp = mw.ustring.gsub(tmp, compounds[v], v)			end for k, v in ipairs(used_roots) do				if mw.ustring.match(tmp, '/'..v..'/') then tmp = mw.ustring.gsub(tmp, '/'..roots[v], '/') end end tmp = mw.ustring.gsub(tmp, '/junction/crossing/', '/crossing+junction/') tmp = mw.ustring.gsub(tmp, '/$', '') if title2 ~= tmp then local tmpsub, omctitle = '', '/other mixed colors' local c, n = mw.ustring.gsub(title[k], '^set ([ufg])$', '!++++'..set_subkey..'%1') if n < 1 then c, n = mw.ustring.gsub(c, '^set mixed', '!+++'..set_subkey) end if n < 1 then c, n = mw.ustring.gsub(c, '^set ex', '!+'..set_subkey) end if n < 1 then c, n = mw.ustring.gsub(c, '^set ', '!++'..set_subkey) end if n > 0 then set_subkey = string.char(set_subkey:byte + 1) if title[1] == 'railway' and title[k] ~= 'set u' and title[k] ~= 'set mixed' then tmpsub = tmp:find('/set ',1,true) and omctitle or '/other colors' end end if n < 1 then c, n = mw.ustring.gsub(c, '^other.*colors$', '!,other') end if n < 1 then c, n = mw.ustring.gsub(c, '^railway$',       '!'..'%0') end if n < 1 then c, n = mw.ustring.gsub(c, '^R([A-Z0-9]+)$',  '$'..'%1') end if n < 1 then c, n = mw.ustring.gsub(c, '^(generic) road$', '$'..'%1') end if n < 1 then c, n = mw.ustring.gsub(c, '^road$',          '$'..'%0') end if n < 1 then c, n = mw.ustring.gsub(c, '^(.*width)$',     '*'..'%1') end if n < 1 then c, n = mw.ustring.gsub(c, '^parallel lines$', '*'..'%0') end if n < 1 then c, n = mw.ustring.gsub(c, '^([a-z]+) quarters?$',					{ one=1, two=2, three=3, four=4, five=5, six=6, seven=7, eight=8 }) end categories = categories..'\n' if oc_cat then c, n = categories, 0 for _, v in ipairs(color) do						if v=='u' then n = n + 1 end if v=='' then n = n + 2 end end if n > 2 then categories = c:gsub('/set u([^|]*)', '%1'..omctitle) .. c:gsub('/set mixed([^|]*)', '%1'..omctitle) elseif n == 1 then categories = c .. c:gsub('/set u', '/set mixed') elseif n == 2 then if #color == 1 then r:ins(" It also contains the (indirect) categories on mixing non-default set colors with another one from this list.") end elseif n == 0 then if #color == 1 then categories = '[[Category:'..tmp:gsub("/set "..color[1], "/set mixed")..omctitle..'|'								.. (#color[1] < 2 and '@' or (color[1]:sub(1,2) == 'ex' and '\\' or '')) .. color[1]:upper								..']]\n' .. c						end end end end end end categories = mw.ustring.gsub(categories, '#', '+') categories = mw.ustring.gsub(categories, '_', '-') return table.concat(r)..categories end

p.categorize = makeInvokeFunction('_categorize')

-- NOT COMPLETE YET. THIS WILL NOT WORK ON A LOT OF ICONS AND NEEDS A LOT OF CATEGORY TITLE PARTS TO BE ADDED.

function p._categorize(args) local title = (mw.ustring.match(mw.title.getCurrentTitle.text, '^BSicon (.*)%.svg$') or 'BHF') local category = {['Category:BSicon'] = true} local titleparts local tmp_set local tmp_match = '' local result = {} if not (mw.ustring.match(title, '[^~]R[ABDEGMPRY]') or mw.ustring.match(title, '^R[ABDEGMPRY]') or mw.ustring.match(title, 'WASSER') or mw.ustring.match(title, 'WABZ')) then category['railway'] = true titleparts = mw.text.split(title, ' ') if titleparts[2] then for k = 2, #titleparts do				if mw.ustring.match(titleparts[k], '^[a-z]+$') then category['set '..titleparts[k]] = true category['other colors'] = true else tmp_set = mw.ustring.match(titleparts[k], '^[a-z]+') if tmp_set then category['set '..tmp_set] = true end titleparts[1] = titleparts[1]..string.sub(titleparts[k], string.len(tmp_set or '')+1) end end end titleparts = titleparts[1] titleparts = '|'..mw.ustring.gsub(titleparts, '.', '%0|') titleparts = mw.ustring.gsub(titleparts, '([A-ZÜ])|([A-ZÜ])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '([A-ZÜ])|([A-ZÜ])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '|n|u|m|', '|NUM|') titleparts = mw.ustring.gsub(titleparts, '|([A-ZÜ][A-ZÜ]+)(SPL)|', '|%1|%2|') titleparts = mw.ustring.gsub(titleparts, '|([A-ZÜ][A-ZÜ]+)(SHI)|', '|%1|%2|') titleparts = mw.ustring.gsub(titleparts, '(SHI)|([1-8])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(BRÜCKE)|([1-3]|[^%+])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(BRÜCKE)|([1-3]|)$', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(VIADUKT)|([1-3]|[^%+])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(VIADUKT)|([1-3]|)$', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(TUNNEL)|([12]|[^%+])', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(TUNNEL)|([12]|)$', '%1%2') -- Other roots titleparts = mw.ustring.gsub(titleparts, '|?(SW)([A-HJ-ZÜ][A-HJ-ZÜ])', '|!SW|%2') titleparts = mw.ustring.gsub(titleparts, '|?S|%+|([A-ZÜ][A-ZÜ])', '|S+%1') titleparts = mw.ustring.gsub(titleparts, '|LL?([A-KMNPQS-ZÜ])', '|L|%1') titleparts = mw.ustring.gsub(titleparts, '|M([A-ZÜ])', '|M|%1') titleparts = mw.ustring.gsub(titleparts, '|M|ASK', '|MASK') titleparts = mw.ustring.gsub(titleparts, '|T([A-DF-QSTV-ZÜ])', '|T|%1') titleparts = mw.ustring.gsub(titleparts, '|K([A-LN-QS-ZÜ])', '|K|%1') titleparts = mw.ustring.gsub(titleparts, '|X([A-ZÜ])', '|X|%1') -- Other capital prefix and combining suffix searches -- Discard x, since it isn't used for categorization and only has one meaning titleparts = mw.ustring.gsub(titleparts, '|x|', '|') titleparts = mw.ustring.gsub(titleparts, '^([ufgelhatpnk|]*|)(c?)|?(d?)|?(b?)|', '%1%2%3%4|') titleparts = mw.ustring.gsub(titleparts, '|([%+%-])|([lhtnCDLM]?)|?([ck])|([1-4])|?([1-4]?)|?([1-4]?)|?([1-4]?)|', '|%1%3%4%5%6%7|%2|') titleparts = mw.ustring.gsub(titleparts, '|([lhtnCDLM]?)|?c|([1-4])|?([1-4]?)|?([1-4]?)|?([1-4]?)|', '|c%2%3%4%5|%1|') titleparts = mw.ustring.gsub(titleparts, '(|[%-~@%+])|([LRFGM]+|)', '%1%2') titleparts = mw.ustring.gsub(titleparts, '(|%-)([LRFGM]+|[A-ZÜ]+)', '%1|%2') -- split prefix L/F/M from parallel lines syntax titleparts = mw.ustring.gsub(titleparts, '|([~@])|([lrfgm])|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|', '|%1%2%3%4%5%6|') titleparts = mw.ustring.gsub(titleparts, '|%(|([LRFGM]*)|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|?([lrfgm]*)|%)|', '|(%1%2%3%4%5%6)|') titleparts = mw.ustring.gsub(titleparts, '^|([ufg])|', '|#%1|') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)([fg]|.*|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)([fg]|.*|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)([fg]|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)([fg]|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)([fg]|.*|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)([fg]|.*|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)([fg]|[A-ZÜ][A-ZÜ])', '%1#%2') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)([fg]|[A-ZÜ][A-ZÜ])', '%1#%2') if mw.ustring.match(titleparts, '|m|') then category['set mixed'] = true titleparts = mw.ustring.gsub(titleparts, '|m|', '|') end titleparts = mw.ustring.gsub(titleparts, '|([%-%+])|([fg]|.*|[A-ZÜ][A-ZÜ])', '|%1|#%2') titleparts = mw.ustring.gsub(titleparts, '|([%-%+])|([fg]|[A-ZÜ][A-ZÜ])', '|%1|#%2') titleparts = mw.ustring.gsub(titleparts, '|([^%-]+)|([htCD])|([1-4lrfgm])|([ae])|$', '|%2|%1|%3|%4|') titleparts = mw.ustring.gsub(titleparts, '|([^%-]+)|([htCD])|([1-4lrfgm])|([ae])|([^A-Z%-]+)|', '|%2|%1|%3|%5|%4|') titleparts = mw.ustring.gsub(titleparts, '|%+|([LRFG]+)|', '|+%1|') -- remove q from KRZ/KRX because it will trip up other regexes titleparts = mw.ustring.gsub(titleparts, '(KR[XZ])|q|', '%1|') titleparts = mw.ustring.gsub(titleparts, '|([CDW])ABZ|', '|%1|ABZ|') titleparts = mw.ustring.gsub(titleparts, '|([CDW])WYE|', '|%1|WYE|') titleparts = mw.ustring.gsub(titleparts, '|ABZ|([gq]?)|?([1-4lrfgm%+])|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|', '|ABZ|%1%2%3%4%5%6%7%8|') titleparts = mw.ustring.gsub(titleparts, '|WYE|([gq]?)|?([1-4lrfgm%+])|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|?([1-4lrfgm%+]?)|', '|WYE|%1%2%3%4%5|') titleparts = mw.ustring.gsub(titleparts, '(KR[ZX])([CDLMW])|', '%1|%2|') titleparts = mw.ustring.gsub(titleparts, '|([CDW])(KR[ZX])|', '|%1|%2|') titleparts = mw.ustring.gsub(titleparts, '|([CDW])STR|', '|%1|STR|') titleparts = mw.ustring.gsub(titleparts, '|(SHI%d)|(g?)|?([lr%+])|?([lr%+]?)|?([lr%+]?)|?([lr%+]?)|?([lr%+]?)|?(q?)|', '|%1|%2%3%4%5%6%7%8|') titleparts = mw.ustring.gsub(titleparts, '|(SHI%d)|(g?[lr]?[lr]?)(q?)|', '|%1|%2+%3|') titleparts = mw.ustring.gsub(titleparts, '|SPL|([ae])|?([lr]?)|?([lr]?)|?(%+?)|?([lr]?)|?([lr]?)|?(%+?)|?([gq]?)|', '|SPL|%1%2%3%4%5%6%7%8|') titleparts = mw.ustring.gsub(titleparts, '|SPL|([ae])|g|(%+?)|?([lr]?)|?([lr]?)|?(q?)|', '|SPL|%1g%2%3%4%5|') titleparts = mw.ustring.gsub(titleparts, '|KRW|(g?)|?([lr%+])|?([lr%+]?)|?([lr%+]?)|?([lr%+]?)|?([lr%+]?)|?(q?)|', '|KRW|%1%2%3%4%5%6%7|') titleparts = mw.ustring.gsub(titleparts, '|ÜWB|([lr]?)|?(%+?)|?([lr]?)|?([lr]?)|?(q?)|', '|ÜWB|%1%2%3%4%5|') titleparts = mw.ustring.gsub(titleparts, '|ÜWB|([lr]?)(q?)|', '|ÜWB|%1+%2|') titleparts = mw.ustring.gsub(titleparts, '|([A-Z][A-Z%+]+[1-3]?)|([1-4lrfgm])|?([ou]?)|?([htCD]?)|%+|([1-4lrfgm])|?([ou]?)|', '|%1|%2+%5|%3%6|%4|') titleparts = mw.ustring.gsub(titleparts, '|([A-Z][A-Z%+]+[1-3]?)|%+|([1-4lrfgm])|?([ou]?)|', '|%1|+%2|%3|') titleparts = mw.ustring.gsub(titleparts, '|([A-Z][A-Z%+]+[1-3]?)|([1-4lrfg])|?([ou]?)|', '|%1|%2+|%3|') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)(u)|', '%1#%2|') titleparts = mw.ustring.gsub(titleparts, '(|[%-%+]|[^A-ZÜ%-%+]+|)(u)|', '%1#%2|') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)(u)|', '%1#%2|') titleparts = mw.ustring.gsub(titleparts, '^(|[^A-ZÜ%-%+]+|)(u)|', '%1#%2|') titleparts = mw.ustring.gsub(titleparts, '|([%-%+])|(u)|', '|%1|#%2|') -- Add handling for icon names containing more than one root -- Affixes always treated the same for k, v in pairs({			['|c|'] = 'quarter-width',			['|d|'] = 'half-width',			['|cd|'] = 'three-quarter-width',			['|b|'] = 'double-width',			['|v|'] = 'parallel lines',			['|%-|'] = 'parallel lines',			['|l|'] = 'legende',			['|h|'] = 'elevated',			['|t|'] = 'tunnel',			['|p|'] = 'express',			['|n|'] = 'narrow',			['|C|'] = 'cutting',			['|D|'] = 'embankment',			['|k|'] = 'k',			['|3|'] = '3',			['|L|'] = 'interruption',			['|M|'] = 'mask',			['|T|'] = 'crossing',			['|W|'] = 'water',			['|[%+%-]?k[1-4][1-4]?[1-4]?[1-4]?|'] = 'k',			['|[%+%-]?[ck][1-4][1-4]?[1-4]?[1-4]?|'] = 'corner',			['|#u|'] = 'set u',			['|#f|'] = 'set f',			['|#g|'] = 'set g',			['|[cdbswv%|]+|e?|?#[ufg]|'] = 'set mixed',			['|%-|e?|?#[ufg]|'] = 'set mixed',			['|X|'] = 'interchange',			['|[X]|'] = 'CPIC',			['|ABZ|'] = 'junction',			['|WYE|'] = 'wye',			['|BHF|'] = 'BHF',			['|HST|'] = 'HST',			['|DST|'] = 'DST', ['|BST|'] = 'BST', ['|INT|'] = 'INT', ['|ACC|'] = 'ACC', ['|INTACC|'] = 'INTACC', ['|HSTACC|'] = 'HSTACC', ['|SBHF|'] = 'SBHF', ['|S%+BHF|'] = 'S+BHF', ['|SHST|'] = 'SHST', ['|S%+HST|'] = 'S+HST', ['|HUB|'] = 'hub', ['|CONT|'] = 'continuation', ['|ENDE|'] = 'line endings', ['|GRZ|'] = 'border', ['|KR[XZ]|'] = 'crossing', ['|KRX|'] = 'uw', ['|KMW|'] = 'milepost', ['|SPL|'] = 'split', ['|SHI%d|'] = 'shift', ['|SHI1|'] = 'one quarter', ['|SHI2|'] = 'two quarters', ['|SHI3|'] = 'three quarters', ['|SHI4|'] = 'four quarters', ['|KRW|'] = 'krw', ['|SHI5|'] = 'five quarters', ['|SHI6|'] = 'six quarters', ['|SHI7|'] = 'seven quarters', ['|SHI8|'] = 'eight quarters', ['|WASSER|'] = 'water', ['|WSL|'] = 'loop', ['|ZOLL|'] = 'customs', ['|HUB|'] = 'hub', ['|%-[LMR]+|'] = 'interchange', ['|MASK|'] = 'mask', ['|[uo]+|'] = 'crossing', }) do			if mw.ustring.match(titleparts, k) then category[v] = true end		end		if category['set mixed'] and category['set u'] and not category['other colors'] then category['set u'] = nil end		if category['legende'] and (category['elevated'] or category['cutting'] or category['embankment']) then			category['legende'] = nil			category['formations'] = true			if category['BHF'] then				category['BHF'] = nil				category['stations and stops'] = true			end		end		if category['3'] then			category['parallel lines'] = nil			if not category['junction'] then category['curve'] = true end		elseif category['k'] then			if not (category['junction'] or category['wye']) then				tmp_match = mw.ustring.match(titleparts, '|[A-Z][A-Z%+]+[1-3]?|([1-4lrfgm]?%+[1-4lrfgm])|') or mw.ustring.match(titleparts, '|[A-Z][A-Z%+]+[1-3]?|([1-4lrfgm]%+[1-4lrfgm]?)|') or ''				if mw.ustring.match(tmp_match, '[1-4]') then category['curve'] = true end end elseif category['shift'] then tmp_match = mw.ustring.match(titleparts, '|SHI%d|(g?[lr]?[lr]?%+?[lr]?[lr]?q?)|') or '' if mw.ustring.match(tmp_match, 'g') or mw.ustring.match(tmp_match, 'lr') or mw.ustring.match(tmp_match, 'rl') then category['junction'] = true if mw.ustring.match(tmp_match, 'l.*%+.*l') or mw.ustring.match(tmp_match, 'r.*%+.*r') then category['crossing'] = true end if category['one quarter'] and (mw.ustring.match(tmp_match, 'lr') or mw.ustring.match(tmp_match, 'rl')) then category['split'], category['junction'] = true, nil end elseif mw.ustring.match(tmp_match, 'l.*%+.*l') or mw.ustring.match(tmp_match, 'r.*%+.*r') then category['crossing'] = true category['curve'] = true elseif not category['corner'] then category['curve'] = true end elseif category['split'] then tmp_match = mw.ustring.match(titleparts, '|SPL|([^|]+)|') or '' if mw.ustring.match(tmp_match, '[1-4]') then category['uw'] = true elseif mw.ustring.match(tmp_match, '^a[lr]%+q') or mw.ustring.match(tmp_match, '^a%+[lr]%+g') or mw.ustring.match(tmp_match, '^e[lr]%+g') or mw.ustring.match(tmp_match, '^e%+[lr]%+q') or mw.ustring.match(tmp_match, '^[ae]g%+?[lr][lr]?q?$') or mw.ustring.match(tmp_match, '^[ae]q?$') then category['shift'] = true category['one quarter'] = true end elseif mw.ustring.match(titleparts, '|ÜWB|') then if category['parallel lines'] then tmp_match = mw.ustring.match(titleparts, '|ÜWB|[lr]?%+([lr]?[lr]?)q?|') or '' if mw.ustring.match(tmp_match, '[lr]') then category['junction'] = true else category['curve'] = true end category['shift'] = true tmp_match = mw.ustring.len(mw.ustring.gsub((mw.ustring.match(titleparts, '|v|[v|?]*') or ), '|', )) if tmp_match > 1 then if tmp_match == 2 then category['four quarters'] = true elseif tmp_match == 3 then category['six quarters'] = true elseif tmp_match == 4 then category['eight quarters'] = true end else category['two quarters'] = true end category['crossing'] = true else category['track change'] = true end else if category['junction'] or category['wye'] then -- add other junctions? if mw.ustring.match(titleparts, '|[A-ZÜ]+|[^%-ck]*[1234]') then category['uw'] = true if mw.ustring.match(titleparts, '|u+|') then category['corner'] = true end if category['parallel lines'] and not (mw.ustring.match(titleparts, '|%-|')) then category['double'] = true end elseif category['junction'] and not category['wye'] then tmp_match = mw.ustring.match(titleparts, '|[A-Z][A-Z%+]+[1-3]?|([gq][lr]?[lr]?%+?[lr]?[lr]?)|') or '' if tmp_match ~= '' then if tmp_match == 'gl+l' or tmp_match == 'gr+r' or tmp_match == 'qlr' or tmp_match == 'qrl' or tmp_match == 'q+lr' or tmp_match == 'q+rl' then category['wye'], category['junction'] = true, nil end end end else tmp_match = mw.ustring.match(titleparts, '|[A-Z][A-Z%+]+[1-3]?|([1-4lrfgm]?%+[1-4lrfgm])|') or mw.ustring.match(titleparts, '|[A-Z][A-Z%+]+[1-3]?|([1-4lrfgm]%+[1-4lrfgm]?)|') or '' if mw.ustring.match(tmp_match, '[1-4]') then category['uw'] = true if not ((category['half-width'] and mw.ustring.match(tmp_match, 'm'))						or ((not mw.ustring.match(titleparts, '^[#ufgelhatpnk|]*|[ocdbsw]')) and (tmp_match == '3+1' or tmp_match == '2+4' or tmp_match == '1+3' or tmp_match == '4+2'))						or mw.ustring.match(titleparts, '|K|[^|]+|[1-4]%+||$') or mw.ustring.match(titleparts, '|K|[^|]+|[1-4]%+||[~@][fglmrFGLMR]')						or mw.ustring.match(titleparts, '|CONT|[1-4]%+||$') or mw.ustring.match(titleparts, '|CONT|[1-4]%+||[~@][fglmrFGLMR]')						or mw.ustring.match(titleparts, '|ENDE|[1-4]%+||$') or mw.ustring.match(titleparts, '|ENDE|[1-4]%+||[~@][fglmrFGLMR]')) then category['curve'] = true if category['parallel lines'] and not (mw.ustring.match(titleparts, '|%-|')) then category['double'] = true end end if mw.ustring.match(titleparts, '|u+|') and not (mw.ustring.match(titleparts, '|KR[XZ]|') or mw.ustring.match(titleparts, '|T|')) then category['corner'] = true end elseif mw.ustring.match(tmp_match, '^[fg]?%+[lr]$') or mw.ustring.match(tmp_match, '^[lr]%+[fg]?$') or mw.ustring.match(tmp_match, '^[lr]%+[lr]$') then category['curve'] = true elseif mw.ustring.match(tmp_match, '^[fg]%+$') and not (category['continuation'] or category['line endings']) then category['direction'] = true end end end if category['corner'] and not (category['3'] or category['k'] or category['shift'] or category['krw']) then category['uw'] = true end if category['tunnel'] and not mw.ustring.match(titleparts, '|K|') and not ((category['continuation'] or category['line endings']) and not category['uw']) and not category['split'] then if mw.ustring.match(titleparts, '[A-ZÜ][A-ZÜ]+[^%-%+~]*|[ae][fgae]?|') then category['portal'] = true end end if category['crossing'] and category['junction'] then category['crossing+junction'] = true category['crossing'], category['junction'] = nil, nil end if category['corner'] then if category['curve'] then category['curve+corner'] = true category['curve'], category['corner'] = nil, nil elseif mw.ustring.match(titleparts, '|%+[ck][1-4][1-4]?[1-4]?[1-4]?|') then category['straight+corner'] = true category['corner'] = nil end end local order = { 'Category:BSicon', 'railway', 'road–rail', 'road', 'water', 'set u', 'set f', 'set g', 'set mixed', 'set azure', 'set black', 'set blue', 'set brown', 'set carrot', 'set cerulean', 'set cyan', 'set deepsky', 'set denim', 'set fuchsia', 'set golden', 'set green', 'set grey', 'set jade', 'set lavender', 'set lime', 'set maroon', 'set ochre', 'set olive', 'set orange', 'set pink', 'set purple', 'set red', 'set ruby', 'set saffron', 'set sky', 'set steel', 'set teal', 'set violet', 'set white', 'set yellow', 'mixed', 'set u-f', 'set u-g', 'set black-orange', 'set green-yellow', 'set saffron-azure', 'set yellow-blue', 'generic road', 'RP4', 'RP2', 'RP1', 'RD', 'RA', 'RB', 'RE', 'RG', 'RM', 'RR', 'RY', 'quarter-width', 'half-width', 'three-quarter-width', 'double-width', 'parallel lines', 'legende', 'formations', 'mask', 'elevated', 'tunnel', 'portal', 'cutting', 'embankment', 'interruption', 'narrow', '3',			'k', 'uw', 'double', 'shift', 'one quarter', 'two quarters', 'three quarters', 'four quarters', 'krw', -- deprecated? 'five quarters', 'six quarters', 'seven quarters', 'eight quarters', 'crossing', 'crossing+junction', 'crossing+corner', 'junction', 'straight+junction', 'split', 'wye', 'line endings', 'continuation', 'loop', 'border', -- determine exact order 'customs', -- determine exact order 'milepost', 'stations and stops', 'suburban', 'BHF', 'HST', 'DST', 'BST', 'INT', 'ACC', 'SBHF', 'SHST', 'S+BHF', 'S+HST', 'HSTACC', 'INTACC', 'hub', 'express', 'interchange', 'CPIC', 'direction', 'curve', 'corner', 'straight+curve', 'straight+corner', 'curve+corner', }		for k, v in ipairs(order) do			if category[v] then table.insert(result, v) end end result = table.concat(result, '/') -- return (mw.dumpObject(result) or )..(mw.dumpObject(titleparts) or )..(mw.dumpObject(test) or '') end return result end

return p